1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
|
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
const alternatives = {
"Enter": ["NumpadEnter"]
}
let keywords, keywordKey, shortcutTypeAdv, shortcutModifier, shortcuts;
let advShortcutModifierIsDown = false;
let advShortcutString = "";
let popoverShown = false;
// -----------------------------------------------------------------------------
async function insertHtmlFragment(message) {
// A normal space causes the selection to ignore the space.
let space = message.extraSpace ? " " : "";
document.execCommand('insertHtml', false, `${message.insertHtml}${space}`);
await handlerCursorTags();
}
async function insertTextFragment(message) {
let space = message.extraSpace ? " " : "";
document.execCommand('insertText', false, `${message.insertText}${space}`);
await handlerCursorTags();
}
function requestInsertTemplate(text) {
return messenger.runtime.sendMessage({ command: "insertTemplate", group: text[0], text: text[1] });
}
async function getSelection(mode) {
let selection = window.getSelection();
if (mode == "TEXT") {
return selection.toString();
}
// https://stackoverflow.com/questions/5083682/get-selected-html-in-browser-via-javascript
if (selection.rangeCount > 0) {
// It may be beneficial to include the surrounding node
// to copy the format
// let wrapperNode = selection.anchorNode.parentElement.tagName;
let range = selection.getRangeAt(0);
let clonedSelection = range.cloneContents();
//let container = document.createElement(wrapperNode);
//container.appendChild(clonedSelection);
let div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
}
return "";
}
async function handlerCursorTags() {
const CURSOR = '[[CURSOR]]'
try {
let items = window.document.evaluate("//*", document, null, XPathResult.ANY_TYPE, null);
let foundElements = [];
let nextItem;
do {
if (nextItem && nextItem.childNodes.length > 0) {
for (let node of nextItem.childNodes) {
if (node.nodeType == 3 && node.nodeValue.includes(CURSOR)) {
foundElements.push(node);
}
}
}
nextItem = items.iterateNext();
}
while (nextItem)
if (foundElements.length == 0) {
return;
}
let selection = window.getSelection();
for (let foundElement of foundElements) {
let startPos = -1;
do {
if (startPos != -1) {
let range = document.createRange();
range.setStart(foundElement, startPos);
range.setEnd(foundElement, startPos + CURSOR.length);
selection.removeAllRanges();
selection.addRange(range);
// execCommand() does not collapse the leading and trailing
// spaces which selection.deleteFromDocument() does. All the
// text altering functions from selection and range seem to
// collapse spaces.
document.execCommand('delete');
//selection.deleteFromDocument();
//selection.getRangeAt(0).deleteContents();
}
startPos = foundElement.nodeValue.indexOf(CURSOR);
} while (startPos != -1)
}
} catch (ex) {
console.debug(ex);
}
}
// -----------------------------------------------------------------------------
function hasMatchingModifier(e, modifier) {
return (
e.altKey && modifier == "alt" ||
e.ctrlKey && modifier == "control" ||
e.metaKey && modifier == "meta"
)
}
function isMatchingModifier(e, modifier) {
return (
e.key == "Alt" && modifier == "alt" ||
e.key == "Control" && modifier == "control" ||
e.key == "Meta" && modifier == "meta"
)
}
function isRealNumberKey(e) {
return e.key.length == 1 && /^[0-9]$/i.test(e.key);
}
function keywordListener(e) {
if (e.code == keywordKey || alternatives[keywordKey]?.includes(e.code)) {
let selection = window.getSelection();
if (!(selection.rangeCount > 0)) {
return;
}
// This gives us a range object of the currently selected text.
let initialSelectionRange = selection.getRangeAt(0).cloneRange();
// Get the text from the beginning of the current node to the end of the
// selection/cursor. We assume the keyword is not split between two nodes.
let range = initialSelectionRange.cloneRange();
range.setStart(range.startContainer, 0);
let lastWord = range.toString().split(" ").pop();
if (!lastWord || !keywords.hasOwnProperty(lastWord)) {
return;
}
// We found a valid keyword, eat the keypress.
e.stopPropagation();
e.preventDefault();
// Extend selection from the end of the current selection/cursor to the
// beginning of the current word.
selection.collapseToEnd();
selection.modify("extend", "backward", "word");
// Verify that the entire lastWord is selected, and extend the selection
// if needed. This is needed since #hi is not fully extended as # is not
// considered to be part of the word.
while (selection.toString().length < lastWord.length) {
selection.modify("extend", "backward", "character");
}
// The following line will remove the keyword before we replace it. If we
// do not do that, we see the keyword being selected and then replaced.
// It does look interesting, but I keep it as it was before.
document.execCommand('delete');
//selection.deleteFromDocument();
//selection.getRangeAt(0).deleteContents();
requestInsertTemplate(keywords[lastWord])
}
}
function shortcutKeyDown(e) {
if (!hasMatchingModifier(e, shortcutModifier)) {
return;
}
if (shortcutTypeAdv) {
advShortcutModifierIsDown = true;
if (isRealNumberKey(e)) {
advShortcutString += e.key;
// Eat keys if we acted upon them.
e.stopPropagation();
e.preventDefault();
}
} else if (isRealNumberKey(e) && shortcuts[e.key] && !e.repeat) {
requestInsertTemplate(shortcuts[e.key]);
// Eat keys if we acted upon them.
e.stopPropagation();
e.preventDefault();
}
}
async function shortcutKeyUp(e) {
if (advShortcutModifierIsDown && shortcutTypeAdv && isMatchingModifier(e, shortcutModifier)) {
if (advShortcutString != "" && typeof shortcuts[advShortcutString] != "undefined") {
requestInsertTemplate(shortcuts[advShortcutString]);
}
advShortcutModifierIsDown = false;
advShortcutString = "";
}
}
async function getLatestPrefs() {
const storage = await import(browser.runtime.getURL("/modules/storage.mjs"));
keywordKey = await storage.getPref("keywordKey");
shortcutTypeAdv = await storage.getPref("shortcutTypeAdv");
shortcutModifier = await storage.getPref("shortcutModifier");
let rv = await messenger.runtime.sendMessage({ command: "getKeywordsAndShortcuts" });
keywords = rv.keywords;
shortcuts = rv.shortcuts;
}
// -----------------------------------------------------------------------------
async function setup() {
const storage = await import(browser.runtime.getURL("/modules/storage.mjs"));
await getLatestPrefs();
window.addEventListener("keydown", shortcutKeyDown, true);
window.addEventListener("keyup", shortcutKeyUp, true);
window.addEventListener("keydown", keywordListener, false);
new storage.StorageListener(
{
watchedPrefs: ["templates", "keywordKey", "shortcutTypeAdv", "shortcutModifier"],
listener: (changes) => {
getLatestPrefs();
}
}
)
}
messenger.runtime.onMessage.addListener((message, sender) => {
if (message.insertText) {
return insertTextFragment(message);
}
if (message.insertHtml) {
return insertHtmlFragment(message);
}
if (message.alertLabel) {
return Promise.resolve(window.alert(message.alertLabel));
}
if (message.getSelection) {
return getSelection(message.getSelection)
}
if (message.isPopoverShown) {
return Promise.resolve(popoverShown);
}
if (message.setPopoverShown) {
popoverShown = message.popoverShownValue;
return Promise.resolve();
}
return false;
});
setup();
console.log("Quicktext compose script loaded");
|