182 lines
5.1 KiB
TypeScript
182 lines
5.1 KiB
TypeScript
import {defaultKeymap, indentWithTab} from "@codemirror/commands";
|
|
import {javascript} from "@codemirror/lang-javascript";
|
|
import {indentUnit} from "@codemirror/language";
|
|
import {keymap} from "@codemirror/view";
|
|
import {githubLight} from "@uiw/codemirror-theme-github";
|
|
import {basicSetup, EditorView} from "codemirror";
|
|
import compile from "./compile";
|
|
import "./styles.css";
|
|
|
|
const loading = document.getElementById("loading") as HTMLDivElement;
|
|
const textarea = document.getElementById("input-code") as HTMLDivElement;
|
|
const check = {
|
|
MaterialUI: document.getElementById("check-material-ui") as HTMLInputElement,
|
|
TailwindCSS: document.getElementById("check-tailwind-css") as HTMLInputElement,
|
|
};
|
|
const button = document.getElementById("click-run") as HTMLButtonElement;
|
|
const page = document.getElementById("iframe-page") as HTMLIFrameElement;
|
|
|
|
const editor = new EditorView({
|
|
parent: textarea,
|
|
extensions: [
|
|
basicSetup,
|
|
javascript({typescript: true, jsx: true}),
|
|
githubLight,
|
|
keymap.of([...defaultKeymap, indentWithTab]),
|
|
indentUnit.of(" "),
|
|
],
|
|
});
|
|
|
|
const basicCode = `
|
|
function App() {
|
|
return <p>Hello world!</p>;
|
|
}
|
|
|
|
const root = ReactDOM.createRoot(
|
|
document.getElementById("root")
|
|
);
|
|
root.render(
|
|
<App/>
|
|
);
|
|
`.trim();
|
|
|
|
function GetLibraries(checkMap: typeof check) {
|
|
const libraries = [];
|
|
if (checkMap.MaterialUI.checked) {
|
|
libraries.push(`<script src="/script/material-ui@5.15.1.min.js"></script>`);
|
|
libraries.push(`<link rel="preconnect" href="https://fonts.googleapis.com"/>`);
|
|
libraries.push(`<link rel="preconnect" href="https://fonts.gstatic.com"/>`);
|
|
libraries.push(`<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap"/>`);
|
|
libraries.push(`<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>`);
|
|
}
|
|
if (checkMap.TailwindCSS.checked) {
|
|
libraries.push(`<script src="/script/tailwind@3.4.0.min.js"></script>`);
|
|
}
|
|
return libraries;
|
|
}
|
|
|
|
function applyChange() {
|
|
const code = `
|
|
try {
|
|
${editor.state.doc.toString()}
|
|
} catch (err) {
|
|
const error = err && err?.toString() || err;
|
|
(document.getElementById("error") as HTMLPreElement).textContent = error
|
|
}
|
|
`;
|
|
let result;
|
|
let error;
|
|
try {
|
|
result = compile(code);
|
|
} catch (err) {
|
|
error = err && err?.toString() || err;
|
|
}
|
|
const libraries = GetLibraries(check);
|
|
|
|
let html;
|
|
if (result) {
|
|
html = `
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>React 演练场</title>
|
|
<script src="/script/react@18.2.0.min.js"></script>
|
|
<script src="/script/react-dom@18.2.0.min.js"></script>
|
|
${libraries.join("\n ")}
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<pre id="error"></pre>
|
|
<script>${result} </script>
|
|
</body>
|
|
</html>
|
|
`.trim();
|
|
} else if (error) {
|
|
html = `
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>React 演练场</title>
|
|
</head>
|
|
<body>
|
|
<pre>${error}</pre>
|
|
</body>
|
|
</html>
|
|
`.trim();
|
|
}
|
|
|
|
if (html) {
|
|
const contentDocument = page.contentDocument!;
|
|
contentDocument.open();
|
|
contentDocument.write(html);
|
|
contentDocument.close();
|
|
}
|
|
}
|
|
|
|
button.addEventListener("click", async (e) => {
|
|
e.preventDefault();
|
|
|
|
applyChange();
|
|
});
|
|
|
|
window.addEventListener("keydown", (e) => {
|
|
if (e.ctrlKey && e.key === "s") {
|
|
e.preventDefault();
|
|
|
|
applyChange();
|
|
}
|
|
})
|
|
|
|
void async function () {
|
|
const url = new URL(window.location.href);
|
|
const searchParams = url.searchParams;
|
|
|
|
const code = searchParams.get("code");
|
|
let script;
|
|
if (code) {
|
|
const scriptUrl = `/code/${code}.tsx`;
|
|
try {
|
|
const result = await fetch(scriptUrl);
|
|
if (result.ok) {
|
|
script = await result.text();
|
|
const configJson = script.match(/^\/\/\s*config\s*=\s*`(.*?)`/)?.[1];
|
|
let config;
|
|
try {
|
|
config = JSON.parse(configJson!);
|
|
} catch (e) {
|
|
}
|
|
if (config) {
|
|
script = script.replace(/^\/\/\s*config\s*=\s*`.*?`\s*\n/, "");
|
|
config?.libraries?.forEach((library: string) => {
|
|
if (Object.keys(check).includes(library)) {
|
|
check[library as keyof typeof check].checked = true;
|
|
}
|
|
});
|
|
}
|
|
script = script.replace(/^\/\/\s*@ts-nocheck\s*\n/, "");
|
|
script = script.trim();
|
|
}
|
|
} catch (e) {
|
|
}
|
|
} else {
|
|
script = basicCode;
|
|
}
|
|
setTimeout(() => {
|
|
loading.remove();
|
|
}, 500);
|
|
if (script) {
|
|
editor.dispatch({
|
|
changes: {
|
|
from: 0,
|
|
to: editor.state.doc.length,
|
|
insert: script,
|
|
},
|
|
});
|
|
button.click();
|
|
}
|
|
}();
|