From d0f9be35c6efd0b338bfaa321aa64173b79f6698 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 13 Jul 2020 01:39:59 +0200 Subject: [PATCH] Support for dynamic keybindings linked to actions/scripts --- package-lock.json | 173 ++++++++++++++++++++++++++++++++++++++- package.json | 2 + src/background.js | 22 +++-- src/listeners/command.js | 26 ++++++ src/listeners/connect.js | 139 +++++++++++++++++++++++++++++++ src/listeners/message.js | 120 ++------------------------- src/manifest.json | 64 ++++++++++++++- src/options/Run.vue | 80 ++++++++++++++---- src/utils.js | 114 +++++++++++++++++++++++--- 9 files changed, 590 insertions(+), 150 deletions(-) create mode 100644 src/listeners/command.js create mode 100644 src/listeners/connect.js diff --git a/package-lock.json b/package-lock.json index dc386b1..dca3306 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1664,7 +1664,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1915,6 +1914,27 @@ "object.assign": "^4.1.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -2566,6 +2586,11 @@ } } }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -4822,6 +4847,11 @@ "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" }, + "highlight.js": { + "version": "9.18.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", + "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5643,6 +5673,14 @@ "verror": "1.10.0" } }, + "katex": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.6.0.tgz", + "integrity": "sha1-EkGOCRIcBckgQbazuftrqyE8tvM=", + "requires": { + "match-at": "^0.1.0" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -5719,6 +5757,14 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "linkify-it": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz", + "integrity": "sha1-B3NSbDF8j9E71TTuHRgP+Iq/iBo=", + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -5984,6 +6030,85 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-6.1.1.tgz", + "integrity": "sha1-ztA39Ec+6fUVOsQU933IPJG6knw=", + "requires": { + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "~1.2.2", + "mdurl": "~1.0.1", + "uc.micro": "^1.0.1" + } + }, + "markdown-it-abbr": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz", + "integrity": "sha1-1mtTZFIcuz3Yqlna37ovtoZcj9g=" + }, + "markdown-it-deflist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.0.3.tgz", + "integrity": "sha512-/BNZ8ksW42bflm1qQLnRI09oqU2847Z7MVavrR0MORyKLtiUYOMpwtlAfMSZAQU9UCvaUZMpgVAqoS3vpToJxw==" + }, + "markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha1-m+4OmpkKljupbfaYDE/dsF37Tcw=" + }, + "markdown-it-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-2.0.0.tgz", + "integrity": "sha1-FOnE9o/xLPNU+jZa43gnboEEypQ=" + }, + "markdown-it-ins": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-2.0.0.tgz", + "integrity": "sha1-papqMPHi9x6Ul1Z8/f9A8f3mdIM=" + }, + "markdown-it-katex": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz", + "integrity": "sha1-17hqGuoLnWSW+rTnkZoY/e9YnDk=", + "requires": { + "katex": "^0.6.0" + } + }, + "markdown-it-mark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-2.0.0.tgz", + "integrity": "sha1-RqGqlHEFrtgYiXjgoBYXnkBPQsc=" + }, + "markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g=" + }, + "markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha1-y5yf+RpSVawI8/09YyhuFd8KH8M=" + }, + "markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==" + }, + "markdown-it-toc-and-anchor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-toc-and-anchor/-/markdown-it-toc-and-anchor-4.2.0.tgz", + "integrity": "sha512-DusSbKtg8CwZ92ztN7bOojDpP4h0+w7BVOPuA3PHDIaabMsERYpwsazLYSP/UlKedoQjOz21mwlai36TQ04EpA==", + "requires": { + "clone": "^2.1.0", + "uslug": "^1.0.4" + } + }, + "match-at": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/match-at/-/match-at-0.1.1.tgz", + "integrity": "sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -5995,6 +6120,11 @@ "safe-buffer": "^5.1.2" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", @@ -8345,8 +8475,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", @@ -9018,6 +9147,11 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "unescape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", @@ -9106,6 +9240,11 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==" + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -9217,6 +9356,14 @@ } } }, + "uslug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/uslug/-/uslug-1.0.4.tgz", + "integrity": "sha1-uaIvCRTgqGFAYz2swwLl9PpFBnc=", + "requires": { + "unorm": ">= 1.0.0" + } + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -9336,6 +9483,26 @@ "vue-style-loader": "^4.1.0" } }, + "vue-markdown": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/vue-markdown/-/vue-markdown-2.2.4.tgz", + "integrity": "sha512-hoTX/W1UIdHZrp/b0vpHSsJXAEfWsafaQLgtE2VX4gY8O/C3L2Gabqu95gyG429rL4ML1SwGv+xsPABX7yfFIQ==", + "requires": { + "highlight.js": "^9.12.0", + "markdown-it": "^6.0.1", + "markdown-it-abbr": "^1.0.3", + "markdown-it-deflist": "^2.0.1", + "markdown-it-emoji": "^1.1.1", + "markdown-it-footnote": "^2.0.0", + "markdown-it-ins": "^2.0.0", + "markdown-it-katex": "^2.0.3", + "markdown-it-mark": "^2.0.0", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-task-lists": "^2.0.1", + "markdown-it-toc-and-anchor": "^4.1.2" + } + }, "vue-prism-editor": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-0.6.1.tgz", diff --git a/package.json b/package.json index 1e703ef..422f2f9 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,10 @@ "@johmun/vue-tags-input": "^2.1.0", "@postlight/mercury-parser": "^2.2.0", "axios": "^0.19.0", + "babel-runtime": "^6.26.0", "prismjs": "^1.20.0", "vue": "^2.6.10", + "vue-markdown": "^2.2.4", "vue-prism-editor": "^0.6.1", "vuejs-auto-complete": "^0.9.0", "webextension-polyfill": "^0.3.1" diff --git a/src/background.js b/src/background.js index 985806e..8379b60 100644 --- a/src/background.js +++ b/src/background.js @@ -1,6 +1,8 @@ import utils from './utils'; -import Message from './listeners/message'; +import Command from './listeners/command'; +import Connect from './listeners/connect'; import Menu from './listeners/menu'; +import Message from './listeners/message'; global.browser = require('webextension-polyfill'); @@ -35,7 +37,9 @@ const app = { }, {}); }, - prepareMenu() { + async refreshMenu() { + await browser.contextMenus.removeAll(); + for (const [host] of Object.entries(this.hosts)) { const hostId = this.separator + host; browser.contextMenus.create({ @@ -64,19 +68,23 @@ const app = { } }, + initListeners() { + browser.contextMenus.onClicked.addListener(Menu.Listener); + browser.runtime.onConnect.addListener(Connect.Listener); + browser.runtime.onMessage.addListener(Message.Listener); + browser.commands.onCommand.addListener(Command.Listener); + }, + async refresh() { this.hosts = await utils.methods.getHosts(); this.actions = await utils.methods.getActions(); this.scripts = await utils.methods.getScripts(); - await browser.contextMenus.removeAll(); - - this.prepareMenu(); - browser.contextMenus.onClicked.addListener(Menu.Listener); - browser.runtime.onConnect.addListener(Message.Listener); + await this.refreshMenu(); }, async create() { await this.refresh(); + this.initListeners(); }, }; diff --git a/src/listeners/command.js b/src/listeners/command.js new file mode 100644 index 0000000..b402ff9 --- /dev/null +++ b/src/listeners/command.js @@ -0,0 +1,26 @@ +import utils from '../utils'; + +export default { + async Listener(command) { + const [commands, hosts, actions, scripts] = await Promise.all([utils.methods.getCommands(), utils.methods.getHosts(), utils.methods.getActions(), utils.methods.getScripts()]); + + if (command in commands) { + const actionName = commands[command]; + if (actionName in actions) { + const action = actions[actionName]; + const host = hosts[Object.values(action.hosts)[0]]; + return await utils.methods.run(action, host); + } + + if (actionName in scripts) { + const script = scripts[actionName]; + const host = hosts[Object.values(script.hosts)[0]]; + return await utils.methods.runScript(script.script, host); + } + + console.warn('No such action nor script', actionName); + } + }, +}; + +// vim:sw=2:ts=2:et: diff --git a/src/listeners/connect.js b/src/listeners/connect.js new file mode 100644 index 0000000..70b2d03 --- /dev/null +++ b/src/listeners/connect.js @@ -0,0 +1,139 @@ +import axios from 'axios'; +import Mercury from '@postlight/mercury-parser'; +import utils from '../utils'; + +const Service = (() => { + const actionService = async port => { + port.onMessage.addListener(async message => { + let ret = null; + switch (message.type) { + case 'run': + ret = await utils.methods.run(message.action, message.host); + port.postMessage(ret); + break; + } + }); + }; + + const urlService = async port => { + port.onMessage.addListener(async message => { + const tab = await utils.methods.getCurrentTab(); + switch (message.type) { + case 'get': + port.postMessage(tab.url); + break; + + case 'set': + await browser.tabs.sendMessage(tab.id, { type: 'setURL', url: message.url }, {}); + break; + + case 'open': + await browser.tabs.create({ + url: message.url, + }); + break; + } + }); + }; + + const domService = async port => { + port.onMessage.addListener(async message => { + const tab = await utils.methods.getCurrentTab(); + let dom = null; + + switch (message.type) { + case 'get': + dom = await browser.tabs.sendMessage(tab.id, { type: 'getDOM' }, {}); + port.postMessage(dom); + break; + + case 'set': + await browser.tabs.sendMessage(tab.id, { type: 'setDOM', html: message.html }, {}); + break; + } + }); + }; + + const notifyService = async port => { + port.onMessage.addListener(async message => { + switch (message.type) { + case 'run': + await utils.methods.notify(message.message, message.title, message.error); + break; + } + }); + }; + + const commandService = async port => { + port.onMessage.addListener(async message => { + switch (message.type) { + case 'get': + const commands = await browser.commands.getAll(); + console.log('Available commands', commands); + port.postMessage(commands); + break; + } + }); + }; + + const axiosService = async port => { + port.onMessage.addListener(async message => { + const method = axios[message.type.toLowerCase()]; + const response = await method(message.url, ...message.args); + port.postMessage({ + config: { + data: response.config.data, + headers: response.config.headers, + maxContentLength: response.config.maxContentLength, + method: response.config.method, + timeout: response.config.timeout, + url: response.config.url, + xsrfCookieName: response.config.xsrfCookieName, + xsrfHeaderName: response.config.xsrfHeaderName, + }, + headers: response.headers, + data: response.data, + status: response.status, + statusText: response.statusText, + }); + }); + }; + + const mercuryService = async port => { + port.onMessage.addListener(async message => { + let response = null; + switch (message.type) { + case 'parse': + response = await Mercury.parse(message.url, { + contentType: 'html', + html: message.html, + }); + + port.postMessage(response); + break; + } + }); + }; + + return { + action: actionService, + url: urlService, + dom: domService, + command: commandService, + notify: notifyService, + axios: axiosService, + mercury: mercuryService, + }; +})(); + +export default { + async Listener(port) { + if (port.name in Service) { + await Service[port.name](port); + } else { + console.warn(`No such message service: {port.name}`); + } + }, +}; + +// vim:sw=2:ts=2:et: diff --git a/src/listeners/message.js b/src/listeners/message.js index ad66a77..fbbe552 100644 --- a/src/listeners/message.js +++ b/src/listeners/message.js @@ -1,124 +1,20 @@ -import axios from 'axios'; -import Mercury from '@postlight/mercury-parser'; -import utils from '../utils'; - const Service = (() => { - const actionService = async port => { - port.onMessage.addListener(async message => { - let ret = null; - switch (message.type) { - case 'run': - ret = await utils.methods.run(message.action, message.host); - port.postMessage(ret); - break; - } - }); - }; - - const urlService = async port => { - port.onMessage.addListener(async message => { - const tab = await utils.methods.getCurrentTab(); - switch (message.type) { - case 'get': - port.postMessage(tab.url); - break; - - case 'set': - await browser.tabs.sendMessage(tab.id, { type: 'setURL', url: message.url }, {}); - break; - - case 'open': - await browser.tabs.create({ - url: message.url, - }); - break; - } - }); - }; - - const domService = async port => { - port.onMessage.addListener(async message => { - const tab = await utils.methods.getCurrentTab(); - let dom = null; - - switch (message.type) { - case 'get': - dom = await browser.tabs.sendMessage(tab.id, { type: 'getDOM' }, {}); - port.postMessage(dom); - break; - - case 'set': - await browser.tabs.sendMessage(tab.id, { type: 'setDOM', html: message.html }, {}); - break; - } - }); - }; - - const notifyService = async port => { - port.onMessage.addListener(async message => { - switch (message.type) { - case 'run': - await utils.methods.notify(message.message, message.title, message.error); - break; - } - }); - }; - - const axiosService = async port => { - port.onMessage.addListener(async message => { - const method = axios[message.type.toLowerCase()]; - const response = await method(message.url, ...message.args); - port.postMessage({ - config: { - data: response.config.data, - headers: response.config.headers, - maxContentLength: response.config.maxContentLength, - method: response.config.method, - timeout: response.config.timeout, - url: response.config.url, - xsrfCookieName: response.config.xsrfCookieName, - xsrfHeaderName: response.config.xsrfHeaderName, - }, - headers: response.headers, - data: response.data, - status: response.status, - statusText: response.statusText, - }); - }); - }; - - const mercuryService = async port => { - port.onMessage.addListener(async message => { - let response = null; - switch (message.type) { - case 'parse': - response = await Mercury.parse(message.url, { - contentType: 'html', - html: message.html, - }); - - port.postMessage(response); - break; - } - }); + const getCommands = async (message, sender, sendResponse) => { + const commands = await browser.commands.getAll(); + sendResponse(commands); }; return { - action: actionService, - url: urlService, - dom: domService, - notify: notifyService, - axios: axiosService, - mercury: mercuryService, + getCommands: getCommands, }; })(); export default { - async Listener(port) { - if (port.name in Service) { - await Service[port.name](port); + async Listener(message, sender, sendResponse) { + if (message.type in Service) { + await Service[message.type](message, sender, sendResponse); } else { - console.warn(`No such message service: {port.name}`); + console.warn('No such handled message type', message.type); } }, }; diff --git a/src/manifest.json b/src/manifest.json index de22eab..4435332 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -30,5 +30,67 @@ "matches": ["*://*/*"], "js": ["content.js"] } - ] + ], + "commands": { + "user-command-0": { + "suggested_key": { + "default": "Ctrl+Alt+0" + }, + "description": "User command 0" + }, + "user-command-1": { + "suggested_key": { + "default": "Ctrl+Alt+1" + }, + "description": "User command 1" + }, + "user-command-2": { + "suggested_key": { + "default": "Ctrl+Alt+2" + }, + "description": "User command 2" + }, + "user-command-3": { + "suggested_key": { + "default": "Ctrl+Alt+3" + }, + "description": "User command 3" + }, + "user-command-4": { + "suggested_key": { + "default": "Ctrl+Alt+4" + }, + "description": "User command 4" + }, + "user-command-5": { + "suggested_key": { + "default": "Ctrl+Alt+5" + }, + "description": "User command 5" + }, + "user-command-6": { + "suggested_key": { + "default": "Ctrl+Alt+6" + }, + "description": "User command 6" + }, + "user-command-7": { + "suggested_key": { + "default": "Ctrl+Alt+7" + }, + "description": "User command 7" + }, + "user-command-8": { + "suggested_key": { + "default": "Ctrl+Alt+8" + }, + "description": "User command 8" + }, + "user-command-9": { + "suggested_key": { + "default": "Ctrl+Alt+9" + }, + "description": "User command 9" + } + } } diff --git a/src/options/Run.vue b/src/options/Run.vue index 4c8839d..45dcedc 100644 --- a/src/options/Run.vue +++ b/src/options/Run.vue @@ -16,8 +16,8 @@
-   Plugins reference. Use $URL$ as argument value to denote the current - URL. You can also call remotely stored procedure through procedure.<procedure_name>. +   Plugins reference. Use $URL$ as argument value to denote the + current URL. You can also call remotely stored procedure through procedure.<procedure_name>.
@@ -28,12 +28,14 @@ :source="actionsAutocomplete" :disableInput="loading" :name="action.name || ''" - :initialValue="selectedAction ? selectedAction.name : null" - :initialDisplay="selectedAction ? selectedAction.name : null" + :initialValue="selectedAction ? selectedAction.action : null" + :initialDisplay="selectedAction ? selectedAction.action : null" @input="onActionChange" />
-
+
+ +
@@ -96,7 +98,7 @@
-
+
@@ -133,6 +135,14 @@ +
+ + +
+
@@ -148,6 +158,7 @@ import 'prismjs'; import 'prismjs/themes/prism.css'; import PrismEditor from 'vue-prism-editor'; +import VueMarkdown from 'vue-markdown'; import mixins from '../utils'; import Autocomplete from 'vuejs-auto-complete'; @@ -181,6 +192,7 @@ export default { VueTagsInput, MultipleHostSelector, PrismEditor, + VueMarkdown, }, data() { @@ -198,6 +210,8 @@ export default { selectedCategories: [], selectedHosts: null, actionMode: 'request', + commands: {}, + command: '', action: { name: null, args: [], @@ -290,6 +304,17 @@ export default { }, {}); }, + async updateCommands() { + try { + this.commands = (await browser.commands.getAll()).reduce((obj, command) => { + obj[command.name] = command; + return obj; + }, {}); + } catch (e) { + console.log('Could not get configured commands', e); + } + }, + async runAction() { this.loading = true; @@ -362,7 +387,24 @@ export default { this.storedActions = await this.getActions(); }, + async getCommand() { + const action = this.saveParams.name.trim(); + const commands = await this.getCommands(); + const [command] = Object.entries(commands).filter(([, act]) => act === action)[0] || []; + return command; + }, + async save(event) { + const action = event.target.displayName.value.trim(); + if (this.command && this.command.length) { + await this.saveCommand(this.command, action); + } else { + const command = await this.getCommand(); + if (command) { + await this.removeCommand(command); + } + } + return this.actionMode === 'request' ? await this.storeAction(event) : await this.storeScript(event); }, @@ -374,12 +416,12 @@ export default { const hosts = [...saveForm.querySelectorAll('input[data-type="host"]:checked')].map(el => el.value); if (!displayName.length) { - this.notify('Please specify an action name', 'No action name provided'); + await this.notify('Please specify an action name', 'No action name provided'); return; } if (!hosts.length) { - this.notify('Please specify at least one device where the action should run', 'No devices provided'); + await this.notify('Please specify at least one device where the action should run', 'No devices provided'); return; } @@ -404,12 +446,12 @@ export default { const hosts = [...saveForm.querySelectorAll('input[data-type="host"]:checked')].map(el => el.value); if (!displayName.length) { - this.notify('Please specify an action name', 'No action name provided'); + await this.notify('Please specify an action name', 'No action name provided'); return; } if (!hosts.length) { - this.notify('Please specify at least one device where the action should run', 'No devices provided'); + await this.notify('Please specify at least one device where the action should run', 'No devices provided'); return; } @@ -453,7 +495,7 @@ export default { this.selectedCategories = tags; }, - initAction() { + async initAction() { const action = this.selectedAction || this.selectedScript; if (!action) { return; @@ -465,10 +507,11 @@ export default { this.saveParams.iconClass = action.iconClass; this.selectedCategories = action.categories.map(cat => (typeof cat === 'string' ? { text: cat } : cat)); this.selectedHosts = action.hosts; + this.command = await this.getCommand(); if (this.selectedAction) { this.actionMode = 'request'; - this.action.name = action.name; + this.action.name = action.action; this.action.defaultArgs = Object.entries(action.args).reduce((obj, [name, value]) => { obj[name] = { value: value }; return obj; @@ -486,6 +529,7 @@ export default { this.loadPlugins(); this.loadActions(); this.initAction(); + this.updateCommands(); }, }; @@ -524,13 +568,15 @@ export default { .action-doc { width: 40em; margin: 0.5em auto auto 1em; - white-space: pre; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 1em; - padding: 1em; max-height: 10em; overflow: auto; background: rgba(250, 255, 240, 0.5); + + * { + margin: 1em; + } } } @@ -606,6 +652,12 @@ form { font-size: 2em; margin-left: 0.2em; } + +.command-selector { + label { + margin-right: 1em; + } +} diff --git a/src/utils.js b/src/utils.js index 3902a42..b6f1696 100644 --- a/src/utils.js +++ b/src/utils.js @@ -10,6 +10,10 @@ export default { }, methods: { + getExtensionId() { + return browser.i18n.getMessage('@@extension_id'); + }, + async notify(message, title = 'platypush', error = false) { let msg = ''; if (title && title.length) { @@ -39,10 +43,17 @@ export default { }, async getCurrentTab() { - const tabs = await browser.tabs.query({ - currentWindow: true, - active: true, - }); + let tabs = []; + + try { + tabs = await browser.tabs.query({ + currentWindow: true, + active: true, + }); + } catch (e) { + console.warn('Could not get active tab', e); + return; + } if (!tabs.length) { await this.notify('No active tab', '', true); @@ -54,22 +65,41 @@ export default { async getURL() { const tab = await this.getCurrentTab(); - return await browser.tabs.sendMessage(tab.id, { type: 'getURL' }); + try { + return await browser.tabs.sendMessage(tab.id, { type: 'getURL' }); + } catch (e) { + console.warn('Could not get URL', e); + } }, async getDOM() { - const tab = await this.getCurrentTab(); - return await browser.tabs.sendMessage(tab.id, { type: 'getDOM' }); + try { + const tab = await this.getCurrentTab(); + return await browser.tabs.sendMessage(tab.id, { type: 'getDOM' }); + } catch (e) { + console.warn('Could not get DOM', e); + } }, async setDOM(html) { const tab = await this.getCurrentTab(); - await browser.tabs.sendMessage(tab.id, { type: 'setDOM', html: html }); + try { + await browser.tabs.sendMessage(tab.id, { type: 'setDOM', html: html }); + } catch (e) { + console.warn('Could not set DOM', e); + } }, async getTargetElement() { const tab = await this.getCurrentTab(); - const target = await browser.tabs.sendMessage(tab.id, { type: 'getTargetElement' }); + let target = null; + try { + target = await browser.tabs.sendMessage(tab.id, { type: 'getTargetElement' }); + } catch (e) { + console.warn('Could not get current element', e); + return; + } + if (!target) { return; } @@ -91,14 +121,14 @@ export default { if (Array.isArray(action.args)) { args = action.args - .filter(arg => arg.value != null && arg.value.length) + .filter(arg => arg.value != null && (typeof arg.value !== 'string' || arg.value.length)) .reduce((obj, arg) => { obj[arg.name] = arg.value; return obj; }, {}); } else { args = Object.entries(args) - .filter(([, value]) => value != null && value.length) + .filter(([, value]) => value != null && (typeof value !== 'string' || value.length)) .reduce((obj, [name, value]) => { obj[name] = value; return obj; @@ -295,15 +325,72 @@ export default { await this.notify('You can find this script under the Local Actions menu', 'Script saved'); }, + async getCommands() { + this.loading = true; + + try { + const response = await browser.storage.local.get('commands'); + if (!response.commands) { + return {}; + } + + return JSON.parse(response.commands); + } finally { + this.loading = false; + } + }, + + async saveCommands(commands) { + this.loading = true; + try { + await browser.storage.local.set({ commands: JSON.stringify(commands) }); + } finally { + this.loading = false; + } + }, + + async saveCommand(command, action) { + const commands = await this.getCommands(); + if (command in commands) { + if (action === commands[command]) { + return; + } + + if (!confirm(`The action ${commands[command]} is already linked to this key binding. Do you want to overwrite it?`)) { + return; + } + } + + Object.entries(commands).forEach(([cmd, act]) => { + if (act === action) { + delete commands[cmd]; + } + }); + + commands[command] = action; + await this.saveCommands(commands); + }, + + async removeCommand(command, action) { + const commands = await this.getCommands(); + if (!(command in commands) || !confirm('Are you sure that you want to remove this key binding?')) { + return; + } + + delete commands[command]; + await this.saveCommands(commands); + }, + async loadConfig() { this.loading = true; try { - const [hosts, actions, scripts] = await Promise.all([this.getHosts(), this.getActions(), this.getScripts(false)]); + const [hosts, actions, scripts, commands] = await Promise.all([this.getHosts(), this.getActions(), this.getScripts(false), this.getCommands()]); return { hosts: hosts, actions: actions, scripts: scripts, + commands: commands, }; } finally { this.loading = false; @@ -315,9 +402,10 @@ export default { const hosts = config.hosts || {}; const actions = config.actions || {}; const scripts = config.scripts || {}; + const commands = config.commands || {}; try { - await Promise.all([this.saveHosts(hosts), this.saveActions(actions), this.saveScripts(scripts)]); + await Promise.all([this.saveHosts(hosts), this.saveActions(actions), this.saveScripts(scripts), this.saveCommands(commands)]); } finally { this.loading = false; }