From c047e59fb72e0950426cb6ec72dfe355779fc26c Mon Sep 17 00:00:00 2001 From: Roy-Orbison Date: Wed, 1 Sep 2021 10:05:02 +0930 Subject: [PATCH] Use the built-in keyboard commands system Registration and conflict resolution is simplified. Change default to one that's unused by Firefox itself. Simplifies code but requires an extra permission to find current tab. --- webex/background.js | 15 ++- webex/content.js | 59 +++++------ webex/manifest.json | 29 ++++-- webex/options.html | 3 +- webex/options.js | 5 - webex/shortcut.js | 239 -------------------------------------------- 6 files changed, 64 insertions(+), 286 deletions(-) delete mode 100644 webex/shortcut.js diff --git a/webex/background.js b/webex/background.js index 66d3692..9e769a4 100644 --- a/webex/background.js +++ b/webex/background.js @@ -19,7 +19,7 @@ function assertNoResponse(response) { } function notifyError(error) { - browser.notifications.create({ + browser.notifications?.create({ type: "basic", title: "Textern", message: "Error: " + error + "." @@ -126,3 +126,16 @@ function onMessage(message, sender, respond) { } browser.runtime.onMessage.addListener(onMessage); + +browser.commands.onCommand.addListener(function(command) { + if (command === 'textern') { + browser.tabs.query({ + currentWindow: true, + active: true + }).then(function(tabs) { + if (tabs?.length && tabs[0].id != browser.tabs.TAB_ID_NONE) { + browser.tabs.sendMessage(tabs[0].id, {type: 'shortcut'}).then(assertNoResponse, logError); + } + }); + } +}); diff --git a/webex/content.js b/webex/content.js index 99ebea7..e43873b 100644 --- a/webex/content.js +++ b/webex/content.js @@ -17,7 +17,7 @@ function assertNoResponse(response) { } function notifyError(error) { - browser.notifications.create({ + browser.notifications?.create({ type: "basic", title: "Textern", message: "Error: " + error + "." @@ -91,8 +91,7 @@ function slackSetText(e, text) { } } -function registerText(event) { - var e = event.target; +function registerText(e) { if (e.nodeName == "TEXTAREA") { var id = watchElement(e); /* don't use href directly to not bring in e.g. url params */ @@ -171,10 +170,33 @@ function setText(id, text) { fadeBackground(e); } +function getActiveElement(element = document.activeElement) { + if (element.nodeName === 'IFRAME' || element.nodeName === 'FRAME') + return null; // skip to allow child documents to handle shortcut + + const shadowRoot = element.shadowRoot + const contentDocument = element.contentDocument + + if (shadowRoot && shadowRoot.activeElement) { + return getActiveElement(shadowRoot.activeElement) + } + + if (contentDocument && contentDocument.activeElement) { + return getActiveElement(contentDocument.activeElement) + } + + return element +} + function onMessage(message, sender, respond) { if (sender.id != "textern@jlebon.com") return; - if (message.type == "set_text") + if (message.type == 'shortcut') { + const activeElement = getActiveElement(); + if (activeElement) + registerText(activeElement); + } + else if (message.type == "set_text") setText(message.id, message.text); else { console.log(`Unknown message type: ${message.type}`); @@ -182,32 +204,3 @@ function onMessage(message, sender, respond) { } browser.runtime.onMessage.addListener(onMessage); - -var currentShortcut = undefined; -function registerShortcut(force) { - browser.storage.local.get({shortcut: "Ctrl+Shift+D"}).then(val => { - if ((val.shortcut == currentShortcut) && !force) - return; /* no change */ - if (currentShortcut != undefined) - shortcut.remove(currentShortcut); - currentShortcut = val.shortcut; - shortcut.add(currentShortcut, registerText); - }); -} - -registerShortcut(true); - -/* meh, we just re-apply the shortcut -- XXX: should check what actually changed */ -browser.storage.onChanged.addListener(function(changes, areaName) { - registerShortcut(false); -}); - -/* we also want to make sure we re-register whenever the number of iframes changes */ -var lastNumFrames = window.frames.length; -const observer = new MutationObserver(function() { - if (window.frames.length != lastNumFrames) { - registerShortcut(true); - lastNumFrames = window.frames.length; - } -}); -observer.observe(document, {childList: true, subtree: true}); diff --git a/webex/manifest.json b/webex/manifest.json index ea7a639..0bd3de4 100644 --- a/webex/manifest.json +++ b/webex/manifest.json @@ -3,7 +3,7 @@ "description": "Edit text in your favourite external editor!", "homepage_url": "https://github.com/jlebon/textern", "manifest_version": 2, - "version": "0.8", + "version": "0.9b1", "browser_specific_settings": { "gecko": { @@ -17,15 +17,32 @@ }, "content_scripts": [ - { - "matches": [""], - "js": ["shortcut.js", "content.js"] - } + { + "matches": [""], + "js": [ + "content.js" + ], + "all_frames": true + } ], + "commands": { + "textern": { + "suggested_key": { + "default": "Shift+Alt+D" + }, + "description": "Edit externally" + } + }, + "options_ui": { "page": "options.html" }, - "permissions": ["notifications", "nativeMessaging", "storage"] + "permissions": [ + "tabs", + "notifications", + "nativeMessaging", + "storage" + ] } diff --git a/webex/options.html b/webex/options.html index a42b9a9..ad92b81 100644 --- a/webex/options.html +++ b/webex/options.html @@ -13,8 +13,6 @@ - - @@ -22,6 +20,7 @@ +

Shortcut can be configured on the Manage Extension Shortcuts settings page.

diff --git a/webex/options.js b/webex/options.js index 7063b3b..b10a7a2 100644 --- a/webex/options.js +++ b/webex/options.js @@ -14,7 +14,6 @@ function saveOptions(e) { e.preventDefault(); browser.storage.local.set({ editor: document.querySelector("#editor").value, - shortcut: document.querySelector("#shortcut").value, extension: document.querySelector("#extension").value, backupdir: document.querySelector("#backupdir").value }); @@ -32,10 +31,6 @@ function restoreOptions() { result.editor || "[\"gedit\", \"+%l:%c\"]"; }, onError); - browser.storage.local.get("shortcut").then(result => { - document.querySelector("#shortcut").value = result.shortcut || "Ctrl+Shift+D"; - }, onError); - browser.storage.local.get("extension").then(result => { document.querySelector("#extension").value = result.extension || "txt"; }, onError); diff --git a/webex/shortcut.js b/webex/shortcut.js deleted file mode 100644 index bdbfb75..0000000 --- a/webex/shortcut.js +++ /dev/null @@ -1,239 +0,0 @@ -/** - * http://www.openjs.com/scripts/events/keyboard_shortcuts/ - * Version : 2.01.B - * By Binny V A - * License : BSD - * - * Adapted by Jonathan Lebon . - */ -shortcut = { - 'all_shortcuts':{},//All the shortcuts are stored in this array - 'add': function(shortcut_combination,callback,opt) { - //Provide a set of default options - var default_options = { - 'type':'keydown', - 'propagate':false, - 'disable_in_input':false, - 'target':window, - 'keycode':false - } - if(!opt) opt = default_options; - else { - for(var dfo in default_options) { - if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo]; - } - } - - var ele = opt.target; - if(typeof opt.target == 'string') ele = document.getElementById(opt.target); - var ths = this; - shortcut_combination = shortcut_combination.toLowerCase(); - - //The function to be called at keypress - var func = function(e) { - if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields - var element; - if(e.target) element=e.target; - else if(e.srcElement) element=e.srcElement; - if(element.nodeType==3) element=element.parentNode; - - if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return; - } - - //Find Which key is pressed - if (e.keyCode) code = e.keyCode; - else if (e.which) code = e.which; - var character = String.fromCharCode(code).toLowerCase(); - - if(code == 188) character=","; //If the user presses , when the type is onkeydown - if(code == 190) character="."; //If the user presses , when the type is onkeydown - - var keys = shortcut_combination.split("+"); - //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked - var kp = 0; - - //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken - var shift_nums = { - "`":"~", - "1":"!", - "2":"@", - "3":"#", - "4":"$", - "5":"%", - "6":"^", - "7":"&", - "8":"*", - "9":"(", - "0":")", - "-":"_", - "=":"+", - ";":":", - "'":"\"", - ",":"<", - ".":">", - "/":"?", - "\\":"|" - } - //Special Keys - and their codes - var special_keys = { - 'esc':27, - 'escape':27, - 'tab':9, - 'space':32, - 'return':13, - 'enter':13, - 'backspace':8, - - 'scrolllock':145, - 'scroll_lock':145, - 'scroll':145, - 'capslock':20, - 'caps_lock':20, - 'caps':20, - 'numlock':144, - 'num_lock':144, - 'num':144, - - 'pause':19, - 'break':19, - - 'insert':45, - 'home':36, - 'delete':46, - 'end':35, - - 'pageup':33, - 'page_up':33, - 'pu':33, - - 'pagedown':34, - 'page_down':34, - 'pd':34, - - 'left':37, - 'up':38, - 'right':39, - 'down':40, - - 'f1':112, - 'f2':113, - 'f3':114, - 'f4':115, - 'f5':116, - 'f6':117, - 'f7':118, - 'f8':119, - 'f9':120, - 'f10':121, - 'f11':122, - 'f12':123 - } - - var modifiers = { - shift: { wanted:false, pressed:false}, - ctrl : { wanted:false, pressed:false}, - alt : { wanted:false, pressed:false}, - meta : { wanted:false, pressed:false} //Meta is Mac specific - }; - - if(e.ctrlKey) modifiers.ctrl.pressed = true; - if(e.shiftKey) modifiers.shift.pressed = true; - if(e.altKey) modifiers.alt.pressed = true; - if(e.metaKey) modifiers.meta.pressed = true; - - for(var i=0; k=keys[i],i 1) { //If it is a special key - if(special_keys[k] == code) kp++; - - } else if(opt['keycode']) { - if(opt['keycode'] == code) kp++; - - } else { //The special keys did not match - if(character == k) kp++; - else { - if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase - character = shift_nums[character]; - if(character == k) kp++; - } - } - } - } - - if(kp == keys.length && - modifiers.ctrl.pressed == modifiers.ctrl.wanted && - modifiers.shift.pressed == modifiers.shift.wanted && - modifiers.alt.pressed == modifiers.alt.wanted && - modifiers.meta.pressed == modifiers.meta.wanted) { - callback(e); - - if(!opt['propagate']) { //Stop the event - //e.cancelBubble is supported by IE - this will kill the bubbling process. - e.cancelBubble = true; - e.returnValue = false; - - //e.stopPropagation works in Firefox. - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - return false; - } - } - } - this.all_shortcuts[shortcut_combination] = { - 'callback':func, - 'target':ele, - 'event': opt['type'] - }; - //Attach the function with the event - - ele.addEventListener(opt['type'], func, true); - - // special-case window: in that case, we want to make sure to attach to - // all the nested frames too - if (ele === window) { - for (i = 0; i < window.frames.length; i++) { - try { - window.frames[i].addEventListener(opt['type'], func, true); - } catch(err) { - console.log(`Failed to add shortcut on window frame ${i}: ${err}`); - } - } - } - }, - - //Remove the shortcut - just specify the shortcut and I will remove the binding - 'remove':function(shortcut_combination) { - shortcut_combination = shortcut_combination.toLowerCase(); - var binding = this.all_shortcuts[shortcut_combination]; - delete(this.all_shortcuts[shortcut_combination]) - if(!binding) return; - var type = binding['event']; - var ele = binding['target']; - var callback = binding['callback']; - - ele.removeEventListener(type, callback, true); - if (ele === window) { - for (i = 0; i < window.frames.length; i++) { - try { - window.frames[i].removeEventListener(type, callback, true); - } catch(err) {} - } - } - } -}