diff options
Diffstat (limited to 'page.js')
-rw-r--r-- | page.js | 186 |
1 files changed, 186 insertions, 0 deletions
@@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-only + +"use strict"; + +(() => { + console.log("TeX Type started."); + + function cmdToUnicode(name, args) { + if (name === "^") + return Array.from(args[0]).reduce( + (acc, c) => acc + (window.unicodeMaps.toSuperscript[c] ?? "^" + c), + "" + ); + if (name === "_") + return Array.from(args[0]).reduce( + (acc, c) => acc + (window.unicodeMaps.toSubscript[c] ?? "_" + c), + "" + ); + + let sym = { + "\\x": "×", + "\\times": "×", + "\\.": "⋅", + "\\cdot": "⋅", + "\\pm": "±", + "\\mp": "∓", + "\\in": "∈", + "\\/in": "∉", + "\\\\": "\u200b\\", + "\\{": "\u200b{", + "\\}": "\u200b}", + "\\^": "\u200b^", + "\\_": "\u200b_", + "<===": "⇐", + "==>": "⇒", + "<=>": "⇔", + "<==": "⟸", + "=>": "⟹", + "\\implies": "⟹", + "<>": "⟺", + "\\iff": "⟺", + "<=": "⩽", + "\\le": "⩽", + ">=": "⩾", + "\\ge": "⩾", + }[name]; + if (sym) return sym; + + if (["\\bf", "\\mathbf"].includes(name)) + return Array.from(args[0]).reduce( + (acc, chr) => acc + (window.unicodeMaps.toBold[chr] ?? chr), + "" + ); + + console.error("unreachable"); + } + + function getArgs(input, i, count) { + const args = []; + for (let argI = 0; argI < count; argI++) { + while (input.value[i] === " ") i++; + + if (!input.value[i]) break; + + if (input.value[i] === "\\") { + const start = i; + i = handleCmd(input, i); + if (failed) break; + args.push(input.value.slice(start, i)); + } else if (input.value[i] === "{") { + const argStart = ++i; + i = subCmds(input, i); + if (i === input.value.length) break; + const arg = input.value.slice(argStart, i++); + args.push(arg); + } else { + args.push(input.value[i++]); + } + } + return { args, argsEnd: i }; + } + + function trySubCmd(input, nameStart) { + let nameEnd; + if (input.value[nameStart] === "\\") { + const offset = input.value.slice(nameStart + 2).search(/\W/); + nameEnd = nameStart + 2 + (offset < 0 ? 0 : offset); + } else if ("^_".includes(input.value[nameStart])) nameEnd = nameStart + 1; + else if ( + "<>=".includes(input.value[nameStart]) && + ">=".includes(input.value[nameStart + 1]) + ) + if ("<>=".includes(input.value[nameStart + 2])) + if ("<>=".includes(input.value[nameStart + 3])) nameEnd = nameStart + 4; + else if (/\s/.test(input.value[nameStart + 3])) nameEnd = nameStart + 3; + else return { end: nameStart + 1, success: false }; + else if (/\s/.test(input.value[nameStart + 2])) nameEnd = nameStart + 2; + else return { end: nameStart + 1, success: false }; + else if (input.value[nameStart] === "\u200b") + return { end: nameStart + 2, success: true }; + else return { end: nameStart + 1, success: false }; + const name = input.value.slice(nameStart, nameEnd); + + const argsCount = { + "\\x": 0, + "\\times": 0, + "\\.": 0, + "\\cdot": 0, + "\\pm": 0, + "\\mp": 0, + "\\in": 0, + "\\/in": 0, + "\\\\": 0, + "\\{": 0, + "\\}": 0, + "\\^": 0, + "\\_": 0, + "<==": 0, + "=>": 0, + "<=>": 0, + "<===": 0, + "==>": 0, + "\\implies": 0, + "<>": 0, + "\\iff": 0, + "<=": 0, + "\\le": 0, + ">=": 0, + "\\ge": 0, + "\\bf": 1, + "\\mathbf": 1, + "^": 1, + _: 1, + }[name]; + if (argsCount == null) return { end: nameEnd, success: false }; + + const argsRet = getArgs(input, nameEnd, argsCount); + const args = argsRet.args; + const argsEnd = argsRet.argsEnd; + if (args.length < argsCount) return { end: argsEnd, success: false }; + + const unicode = cmdToUnicode(name, args); + input.value = + input.value.slice(0, nameStart) + unicode + input.value.slice(argsEnd); + + const caret = input.caret; + if (caret > nameStart) + input.caret = caret + nameStart - argsEnd + unicode.length; + + return { end: nameStart + unicode.length, success: true }; + } + + function subCmds(input, i) { + const starts = []; + const ends = []; + if (i == null) i = 0; + while (i < input.value.length) { + if (input.value[i] === "}") return i; + else i = trySubCmd(input, i).end; + } + return input.value.length; + } + + window.addEventListener("input", (event) => { + if (event.target.selectionStart != null) { + const input = { + value: event.target.value, + caret: event.target.selectionStart, + }; + subCmds(input); + event.target.value = input.value; + event.target.selectionStart = event.target.selectionEnd = input.caret; + } else if (event.target.contentEditable === "true") { + const textNode = window.getSelection().anchorNode; + const input = { + value: textNode.textContent, + caret: window.getSelection().getRangeAt(0).startOffset, + }; + subCmds(input); + textNode.textContent = input.value; + window + .getSelection() + .setBaseAndExtent(textNode, input.caret, textNode, input.caret); + } + }); +})(); |