diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | demo.html | 5 | ||||
-rw-r--r-- | lib/extension.js | 26 | ||||
-rw-r--r-- | manifest.json | 10 | ||||
-rw-r--r-- | options.html | 70 | ||||
-rw-r--r-- | options.js | 12 | ||||
-rw-r--r-- | page.js | 348 | ||||
-rw-r--r-- | popup.html | 12 | ||||
-rw-r--r-- | popup.js | 6 |
9 files changed, 323 insertions, 172 deletions
@@ -16,6 +16,12 @@ handlers. It works on the following, to a certain extent: - Pronto - Skype, but only halfway +## Configuration + +Click on the extension icon in the toolbar or in its extensions menu to open the +options page. This page can also be accessed from <about:addons>, +<chrome://extensions>, etc. + ## Alternative Font ligatures could be used instead, but then all readers would have to install @@ -24,6 +24,11 @@ </head> <body> <textarea autofocus></textarea> + <script> + async function subEnabled() { + return true; + } + </script> <script src="init-unicode-maps.js"></script> <script src="unicode-maps-to-bold.js"></script> <script src="unicode-maps-to-subscript.js"></script> diff --git a/lib/extension.js b/lib/extension.js new file mode 100644 index 0000000..e74c877 --- /dev/null +++ b/lib/extension.js @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only + +"use strict"; + +const defaultSettings = { + enableRegExp: ".*", +}; + +window.browser = window.browser ?? window.chrome; + +async function setSetting(setting, val) { + await browser.storage.sync.set({ [setting]: val }); +} + +async function getSetting(setting) { + return ( + (await browser.storage.sync.get(setting))[setting] ?? + defaultSettings[setting] + ); +} + +async function subEnabled() { + return new RegExp( + "^" + (await getSetting("enableRegExp")).replace(/\s+/g, "") + "$" + ).test(window.location.host); +} diff --git a/manifest.json b/manifest.json index 4dbdacb..3bf0b83 100644 --- a/manifest.json +++ b/manifest.json @@ -3,9 +3,11 @@ "name": "TeX Type", "version": "0.1.0", "description": "Converts commands (some TeX) in textboxes to unicode as one types.", + "permissions": ["storage"], "content_scripts": [{ "matches": ["*://*/*"], "js": [ + "lib/extension.js", "init-unicode-maps.js", "unicode-maps-to-bold.js", "unicode-maps-to-subscript.js", @@ -13,6 +15,14 @@ "page.js" ] }], + "action": { + "default_popup": "popup.html", + "default_title": "TeX Type Settings" + }, + "options_ui": { + "page": "options.html", + "open_in_tab": true + }, "browser_specific_settings": { "gecko": { "id": "tex-type@pml4t.net" diff --git a/options.html b/options.html new file mode 100644 index 0000000..8fb6b5f --- /dev/null +++ b/options.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<!-- SPDX-License-Identifier: GPL-2.0-only --> + +<html> + <head> + <meta charset="utf-8" /> + <style> + html, + body { + height: 100%; + margin: 0; + padding: 0; + } + + body { + background-color: black; + box-sizing: border-box; + color: white; + display: flex; + flex-direction: column; + font-family: sans-serif; + gap: 8px; + padding: 8px; + } + + textarea { + background-color: inherit; + border: 2px solid gray; + color: inherit; + padding: 8px; + } + + textarea:focus { + border: 2px solid white; + outline: none; + } + + #enable-reg-exp { + flex: 1; + resize: none; + } + + button { + border: 2px solid transparent; + color: inherit; + outline: none; + padding: 4px; + } + + button:focus { + border: 2px solid white; + outline: none; + } + + button.inviting { + background-color: green; + } + </style> + </head> + <body> + <label for="enable-reg-exp"> + Regular expression matching domain names of sites on which to enable + substitution. Whitespace is ignored. + </label> + <textarea id="enable-reg-exp"></textarea> + <button id="enable-reg-exp-save" class="inviting">Save</button> + <script src="lib/extension.js"></script> + <script src="options.js"></script> + </body> +</html> diff --git a/options.js b/options.js new file mode 100644 index 0000000..3262181 --- /dev/null +++ b/options.js @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only + +"use strict"; + +(async () => { + const input = document.getElementById("enable-reg-exp"); + input.value = await getSetting("enableRegExp"); + const saveButton = document.getElementById("enable-reg-exp-save"); + saveButton.addEventListener("click", () => + setSetting("enableRegExp", input.value) + ); +})(); @@ -2,185 +2,189 @@ "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"); - } +(async () => { + if (await subEnabled()) { + 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++]); + 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 }; } - 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; + 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 (/\s/.test(input.value[nameStart + 2])) nameEnd = nameStart + 2; + else if (input.value[nameStart] === "\u200b") + return { end: nameStart + 2, success: true }; 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; + 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 }; } - 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); + 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); + } + }); + } })(); diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..044ad4f --- /dev/null +++ b/popup.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<!-- SPDX-License-Identifier: GPL-2.0-only --> + +<html> + <head> + <meta charset="utf-8" /> + </head> + <body> + <script src="lib/extension.js"></script> + <script src="popup.js"></script> + </body> +</html> diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..c07a031 --- /dev/null +++ b/popup.js @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only + +"use strict"; + +browser.runtime.openOptionsPage(); +window.close(); |