react-online/src/index.ts

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();
}
}();