Implemented context menu
This commit is contained in:
parent
9b562f3a89
commit
b2cb616929
6 changed files with 181 additions and 40 deletions
|
@ -1,3 +1,92 @@
|
|||
import utils from './utils';
|
||||
|
||||
global.browser = require('webextension-polyfill');
|
||||
|
||||
const menu = {
|
||||
hosts: {},
|
||||
actions: {},
|
||||
scripts: {},
|
||||
categories: {},
|
||||
separator: '//',
|
||||
|
||||
categoriesByHost(host) {
|
||||
return Object.entries({ ...(this.actions || {}), ...(this.scripts || {}) }).reduce((obj, [actionName, action]) => {
|
||||
if (action.hosts.indexOf(host) < 0) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const appendAction = category => {
|
||||
if (!(category in obj)) {
|
||||
obj[category] = {};
|
||||
}
|
||||
|
||||
obj[category][actionName] = action;
|
||||
};
|
||||
|
||||
if (!(action.categories && action.categories.length)) {
|
||||
appendAction('');
|
||||
} else {
|
||||
action.categories.forEach(category => appendAction(category));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
this.hosts = await utils.methods.getHosts();
|
||||
this.actions = await utils.methods.getActions();
|
||||
this.scripts = await utils.methods.getScripts();
|
||||
browser.contextMenus.removeAll();
|
||||
|
||||
for (const [host] of Object.entries(this.hosts)) {
|
||||
const hostId = this.separator + host;
|
||||
browser.contextMenus.create({
|
||||
id: hostId,
|
||||
title: host,
|
||||
});
|
||||
|
||||
const categories = this.categoriesByHost(host);
|
||||
for (const [categoryName, category] of Object.entries(categories)) {
|
||||
const categoryId = hostId + this.separator + (categoryName.length ? categoryName : '[NONE]');
|
||||
browser.contextMenus.create({
|
||||
id: categoryId,
|
||||
parentId: hostId,
|
||||
title: categoryName.length ? categoryName : '[No Category]',
|
||||
});
|
||||
|
||||
for (const [action] of Object.entries(category)) {
|
||||
const actionId = categoryId + this.separator + action;
|
||||
browser.contextMenus.create({
|
||||
id: actionId,
|
||||
parentId: categoryId,
|
||||
title: action,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
browser.contextMenus.onClicked.addListener(async (info, tab) => {
|
||||
const [host, , action] = info.menuItemId.split(this.separator).slice(1);
|
||||
const target = await utils.methods.getTargetElement();
|
||||
|
||||
if (action in this.actions) {
|
||||
utils.methods.run(this.actions[action], this.hosts[host]);
|
||||
} else {
|
||||
utils.methods.runScript(this.scripts[action].script, this.hosts[host], tab, target);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async create() {
|
||||
await this.refresh();
|
||||
},
|
||||
};
|
||||
|
||||
const onCreate = () => {
|
||||
menu.create();
|
||||
};
|
||||
|
||||
onCreate();
|
||||
|
||||
// vim:sw=2:ts=2:et:
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
global.browser = require('webextension-polyfill');
|
||||
|
||||
const context = {
|
||||
targetElement: null,
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
switch (message.type) {
|
||||
case 'getURL':
|
||||
|
@ -13,7 +17,15 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||
case 'setDOM':
|
||||
document.getElementsByTagName('html')[0].innerHTML = message.html;
|
||||
break;
|
||||
|
||||
case 'getTargetElement':
|
||||
sendResponse(context.targetElement ? context.targetElement.outerHTML : null);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('contextmenu', event => {
|
||||
context.targetElement = event.target;
|
||||
});
|
||||
|
||||
// vim:sw=2:ts=2:et:
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"48": "icons/icon-48.png",
|
||||
"64": "icons/icon-64.png"
|
||||
},
|
||||
"permissions": ["activeTab", "storage", "notifications", "clipboardRead", "clipboardWrite", "<all_urls>"],
|
||||
"permissions": ["activeTab", "storage", "notifications", "clipboardRead", "clipboardWrite", "contextMenus", "<all_urls>"],
|
||||
|
||||
"browser_action": {
|
||||
"default_title": "platypush",
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<template>
|
||||
<div class="page backup">
|
||||
<h2>Extension Configuration</h2>
|
||||
|
||||
<div class="container">
|
||||
<div class="head">
|
||||
<h2>Extension Configuration</h2>
|
||||
<form class="loader" ref="loader" @submit.prevent="loadURL">
|
||||
<div class="head">
|
||||
<div class="loader-head">
|
||||
Load configuration
|
||||
<input type="radio" id="_file" value="file" v-model="extConfigType" />
|
||||
<label for="_file">From file</label>
|
||||
|
@ -12,25 +11,25 @@
|
|||
<label for="_url">From URL</label>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<div class="loader-body">
|
||||
<input type="file" name="file" placeholder="Configuration file" accept="application/json, text/x-json, text/plain" @change="uploadFile" v-if="extConfigType === 'file'" />
|
||||
<input type="text" name="url" placeholder="Configuration URL" v-model="extURL" v-if="extConfigType === 'url'" />
|
||||
<input type="submit" value="Load" v-if="extConfigType === 'url'" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="content" ref="content" @submit.prevent="save">
|
||||
<div class="textarea">
|
||||
<PrismEditor name="text" v-model="config" :code="loading ? 'Loading...' : config" language="js" :emitEvents="true" />
|
||||
<!-- <textarea name="text" ref="text" v-model="config" v-text="loading ? 'Loading...' : config" @focus="onFocus" :disabled="loading" /> -->
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="button" :disabled="savedConfig === config" @click="reload"><i class="fas fa-undo" /> Undo</button>
|
||||
<button type="submit" :disabled="savedConfig === config"><i class="fas fa-save" /> Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form class="content" ref="content" @submit.prevent="save">
|
||||
<div class="textarea">
|
||||
<PrismEditor name="text" v-model="config" :code="loading ? 'Loading...' : config" language="js" :emitEvents="true" />
|
||||
<!-- <textarea name="text" ref="text" v-model="config" v-text="loading ? 'Loading...' : config" @focus="onFocus" :disabled="loading" /> -->
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="button" :disabled="savedConfig === config" @click="reload"><i class="fas fa-undo" /> Undo</button>
|
||||
<button type="submit" :disabled="savedConfig === config"><i class="fas fa-save" /> Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -131,27 +130,56 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
textarea,
|
||||
.loader {
|
||||
width: 60%;
|
||||
min-width: 30em;
|
||||
max-width: 50em;
|
||||
}
|
||||
$head-height: 10em;
|
||||
$buttons-height: 5em;
|
||||
|
||||
textarea {
|
||||
height: 50em;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
.page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 !important;
|
||||
|
||||
.head,
|
||||
.content {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.head {
|
||||
height: $head-height;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: calc(100% - #{$head-height});
|
||||
|
||||
.textarea {
|
||||
height: calc(100% - #{$buttons-height});
|
||||
overflow: auto;
|
||||
|
||||
pre {
|
||||
border: 1px dotted #888;
|
||||
border-radius: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
height: $buttons-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.head {
|
||||
.loader-head {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.body {
|
||||
.loader-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
|
|
@ -145,7 +145,7 @@ export default {
|
|||
host: Object,
|
||||
scriptTemplate: {
|
||||
type: String,
|
||||
default: `async (app, host, browser, window) => {
|
||||
default: `async (app, host, browser, tab, target, ...args) => {
|
||||
// Run some action on the host
|
||||
const status = await app.run({ name: 'music.mpd.pause' }, host);
|
||||
|
||||
|
|
30
src/utils.js
30
src/utils.js
|
@ -46,15 +46,27 @@ export default {
|
|||
await browser.tabs.sendMessage(tab.id, { type: 'setDOM', html: html });
|
||||
},
|
||||
|
||||
async run(action, host) {
|
||||
const url = (host.ssl ? 'https' : 'http') + '://' + host.address + ':' + host.port + '/execute';
|
||||
async getTargetElement() {
|
||||
const tab = await this.getCurrentTab();
|
||||
const target = await browser.tabs.sendMessage(tab.id, { type: 'getTargetElement' });
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new DOMParser().parseFromString(target, 'text/html').documentElement.getElementsByTagName('body')[0].firstChild;
|
||||
},
|
||||
|
||||
async run(action, host, url) {
|
||||
const execURL = (host.ssl ? 'https' : 'http') + '://' + host.address + ':' + host.port + '/execute';
|
||||
const config = {};
|
||||
let args = action.args || {};
|
||||
let currentURL = null;
|
||||
let currentURL = url;
|
||||
|
||||
try {
|
||||
currentURL = await this.getURL();
|
||||
} catch (e) {}
|
||||
if (!url) {
|
||||
try {
|
||||
currentURL = await this.getURL();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (Array.isArray(action.args)) {
|
||||
args = action.args
|
||||
|
@ -84,7 +96,7 @@ export default {
|
|||
|
||||
try {
|
||||
const msg = await axios.post(
|
||||
url,
|
||||
execURL,
|
||||
{
|
||||
type: 'request',
|
||||
action: action.name,
|
||||
|
@ -105,7 +117,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async runScript(script, host) {
|
||||
async runScript(script, host, tab, target, ...args) {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
|
@ -114,7 +126,7 @@ export default {
|
|||
script = eval(this.script);
|
||||
}
|
||||
|
||||
return await script(this, host, browser, window);
|
||||
return await script(this, host, browser, tab, target, ...args);
|
||||
} catch (e) {
|
||||
this.notify(e.message, 'Script error');
|
||||
throw e;
|
||||
|
|
Loading…
Reference in a new issue