import { CaretParser, CaretNode, CharToken, EditorState, TokensTag, VNode, h, Transaction, Cursor, t, } from "@caret-js/core"; import { defaultParselets, FractionToken, FunctionArgColorsSyntaxHighlighter, FunctionDefinitionNode, ItalicVariablesSyntaxHighlighter, ParenthesesToken, RadicalToken, SubSupToken, toText, VariableNode, } from "@caret-js/math"; import { Strand } from "@caret-js/core"; import "./main.css"; const app = document.getElementById("app") as HTMLDivElement; const inputArea = document.getElementById("input-area") as HTMLDivElement; const tokensArea = document.getElementById("tokens-area") as HTMLDivElement; const parsedArea = document.getElementById("parsed-area") as HTMLDivElement; // const diagramArea = document.getElementById( // "parsed-diagram-area" // ) as HTMLDivElement; let editorState = new EditorState(); function updatePreview() { let parseResult: CaretNode; try { const parser = new CaretParser( editorState, [], [...defaultParselets({ funcNames: new Set(["f"]) })] ); parseResult = parser.parse(); } catch (e) { console.error("Error parsing document:", e); return; } // Syntax highlighting const syntaxHighlighters = [ new ItalicVariablesSyntaxHighlighter(), new FunctionArgColorsSyntaxHighlighter(), ]; for (const highlighter of syntaxHighlighters) { highlighter.process(editorState, parseResult); } for (const [strand, strandPath] of editorState.content.traverseStrands()) { for (const token of strand.tokens) { let combinedStyles: Record = {}; for (const highlighter of syntaxHighlighters) { const styles = highlighter.getStyles(token); combinedStyles = { ...combinedStyles, ...styles }; } if (token instanceof CharToken) { const styleString = Object.entries(combinedStyles) .map(([key, value]) => `${key}: ${value}`) .join("; "); token.TEMPORARY_DEBUG_STYLE_STRING = styleString; } } } inputArea.innerHTML = ""; inputArea.appendChild(renderVNode(editorState.renderToDebugHTML())); // Token-based MathML // inputArea.appendChild( // renderVNode( // h("div", {}, t("Token-based MathML:"), editorState.renderToDebugMathML()) // ) // ); // Node-based MathML // inputArea.appendChild( // renderVNode( // h( // "div", // {}, // t("Node-based MathML:"), // h("math", {}, parseResult.toDebugMathML()) // ) // ) // ); // Math to text // const text = toText(parseResult); // inputArea.appendChild( // renderVNode(h("div", {}, t("Math to text: "), t(text ?? ""))) // ); // document.getElementById("math-to-text-readout")!.innerHTML = text ?? ""; // document.getElementById("math-to-text-readout")!.innerHTML = // text && parseResult instanceof CaretNode // ? `${text} or in MathML ${renderVNodeToHTMLString( // h("math", {}, parseResult.toDebugMathML()) // )}` // : ""; tokensArea.innerHTML = `
${editorState.renderToDebugText()}
`; parsedArea.innerHTML = `
${parseResult.toDebugString()}
`; // diagramArea.innerHTML = `
${parseResult.toDebugHTML()}
`; } function renderVNode(node: VNode): Node { if ("text" in node) { return document.createTextNode((node as any).text); } const elem = node.namespaceURI ? document.createElementNS(node.namespaceURI, node.type) : document.createElement(node.type); for (const [key, value] of Object.entries(node.attributes)) { elem.setAttribute(key, value); } for (const child of node.children) { elem.appendChild(renderVNode(child)); } return elem; } function renderVNodeToHTMLString(node: VNode): string { if ("text" in node) { return (node as any).text; } const attrs = Object.entries(node.attributes) .map(([key, value]) => `${key}="${value}"`) .join(" "); const children = node.children .map((child) => renderVNodeToHTMLString(child)) .join(""); return `<${node.type}${attrs ? " " + attrs : ""}>${children}`; } document.addEventListener("keydown", (event) => { if (event.key === "ArrowRight") { event.preventDefault(); editorState = Transaction.moveCursor( editorState, "right", event.shiftKey ).newState; updatePreview(); return; } if (event.key === "ArrowLeft") { event.preventDefault(); editorState = Transaction.moveCursor( editorState, "left", event.shiftKey ).newState; updatePreview(); return; } if (event.key === "Backspace") { event.preventDefault(); editorState = Transaction.deleteAtSelection( editorState, "backward" ).newState; updatePreview(); return; } if (event.key === "Delete") { event.preventDefault(); editorState = Transaction.deleteAtSelection( editorState, "forward" ).newState; updatePreview(); return; } if (event.key === "a" && (event.ctrlKey || event.metaKey)) { event.preventDefault(); const transaction = Transaction.selectAll(editorState); editorState = transaction.newState; updatePreview(); return; } const typeableChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-*=.,".split( "" ); if (typeableChars.includes(event.key) && !event.ctrlKey && !event.metaKey) { event.preventDefault(); // Check for "sqrt" completion if (event.key === "t") { if (editorState.selection.isCollapsed()) { const cursor = editorState.selection.head; const strand = editorState.content.getStrand(cursor.strandPath); if (strand) { const priorThreeTokens = strand.tokens .slice(Math.max(0, cursor.pos - 3), cursor.pos) .map((t) => (t instanceof CharToken ? t.char : "")) .join(""); if (priorThreeTokens === "sqr") { // Replace "sqr" with a square root token const transaction = new Transaction( editorState, [ { strandPath: cursor.strandPath, fromPos: cursor.pos - 3, toPos: cursor.pos, newTokens: [new RadicalToken(null, new Strand([]))], }, ], new Cursor( [ ...cursor.strandPath, { tokenIndex: cursor.pos - 3, childIndex: 0 }, ], 0 ) ); editorState = transaction.newState; updatePreview(); return; } } } } // Check for "cbrt" completion if (event.key === "t") { if (editorState.selection.isCollapsed()) { const cursor = editorState.selection.head; const strand = editorState.content.getStrand(cursor.strandPath); if (strand) { const priorThreeTokens = strand.tokens .slice(Math.max(0, cursor.pos - 3), cursor.pos) .map((t) => (t instanceof CharToken ? t.char : "")) .join(""); if (priorThreeTokens === "cbr") { // Replace "cbr" with a cube root token const transaction = new Transaction( editorState, [ { strandPath: cursor.strandPath, fromPos: cursor.pos - 3, toPos: cursor.pos, newTokens: [ new RadicalToken( new Strand([new CharToken("3")]), new Strand([]) ), ], }, ], new Cursor( [ ...cursor.strandPath, { tokenIndex: cursor.pos - 3, childIndex: 1 }, ], 0 ) ); editorState = transaction.newState; updatePreview(); return; } } } } editorState = Transaction.insertAtSelection(editorState, [ new CharToken(event.key), ]).newState; updatePreview(); return; } if (event.key === "/" && !event.ctrlKey && !event.metaKey) { event.preventDefault(); editorState = Transaction.insertAtSelection(editorState, [ new FractionToken(new Strand([]), new Strand([])), ]).setNewSelection( new Cursor( [ ...editorState.selection.commonStrandPath, { tokenIndex: editorState.selection.commonStartPos, childIndex: 0 }, ], 0 ) ).newState; updatePreview(); return; } if (event.key === "(" && !event.ctrlKey && !event.metaKey) { event.preventDefault(); editorState = Transaction.insertAtSelection(editorState, [ new ParenthesesToken(new Strand([])), ]).setNewSelection( new Cursor( [ ...editorState.selection.head.strandPath, { tokenIndex: editorState.selection.head.pos, childIndex: 0 }, ], 0 ) ).newState; updatePreview(); return; } if (event.key === "^" && !event.ctrlKey && !event.metaKey) { event.preventDefault(); editorState = Transaction.insertAtSelection( editorState, ({ setCursor }) => [new SubSupToken(null, new Strand([setCursor()]))] ).setNewSelection(new Cursor()).newState; updatePreview(); return; } if (event.key === "_" && !event.ctrlKey && !event.metaKey) { event.preventDefault(); editorState = Transaction.insertAtSelection( editorState, ({ setCursor }) => [new SubSupToken(new Strand([setCursor()]), null)] ).newState; updatePreview(); return; } }); updatePreview();