Implemented context menu

This commit is contained in:
Fabio Manganiello 2020-06-29 02:21:00 +02:00
parent 9b562f3a89
commit b2cb616929
6 changed files with 181 additions and 40 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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",

View File

@ -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" /> &nbsp; Undo</button>
<button type="submit" :disabled="savedConfig === config"><i class="fas fa-save" /> &nbsp; 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" /> &nbsp; Undo</button>
<button type="submit" :disabled="savedConfig === config"><i class="fas fa-save" /> &nbsp; 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;

View File

@ -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);

View File

@ -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;