diff --git a/src/lib/trie.js b/src/lib/trie.js index 95b5ba8..962eb2e 100644 --- a/src/lib/trie.js +++ b/src/lib/trie.js @@ -39,16 +39,15 @@ export default class Trie { */ advance = input => { const next = this.curNode[input] - switch (typeof next) { - case 'undefined': + if (typeof next == 'undefined') { this.curNode = this.root return this.root[input] === undefined ? Trie.INVALID : this.advance(input) - case 'object': + } else if (next._TrieNode === true) { this.curNode = next return Trie.INTERNAL - default: + } else { this.curNode = this.root return next } diff --git a/src/modes/command/client/commands/misc.js b/src/modes/command/client/commands/misc.js index 20c9567..f332a5f 100644 --- a/src/modes/command/client/commands/misc.js +++ b/src/modes/command/client/commands/misc.js @@ -28,3 +28,15 @@ export function passAllKeys (event) { event.passKeyType = 'all' return 'Pass' } + +export function customCommand (event, data) { + console.log('Custom command', data) + + let global_eval = eval; // This causes the eval to happen in the global scope. + let func = global_eval(` + (event) => { + ${data.source} + } + `); + return func(event); +} diff --git a/src/modes/command/client/trie.js b/src/modes/command/client/trie.js index 80d342a..4b23845 100644 --- a/src/modes/command/client/trie.js +++ b/src/modes/command/client/trie.js @@ -25,6 +25,7 @@ export function advanceInputTrie (event) { default: event.preventDefault() event.stopImmediatePropagation() - return commands[command](event) || 'Same' + let implementation = commands[command.command] + return implementation(event, command) || 'Same' } } diff --git a/src/options/CustomCommands/config.json b/src/options/CustomCommands/config.json new file mode 100644 index 0000000..d4f232e --- /dev/null +++ b/src/options/CustomCommands/config.json @@ -0,0 +1,10 @@ +{ + "name": "Custom Commands", + "options": [ + { + "type": "customcommands", + "key": "customCommands", + "default": [] + } + ] +} diff --git a/src/options/CustomCommands/default.json b/src/options/CustomCommands/default.json new file mode 100644 index 0000000..8aca381 --- /dev/null +++ b/src/options/CustomCommands/default.json @@ -0,0 +1,11 @@ +{ + "name": "Custom Commands", + "profiles": [ + { + "name": "default", + "options": { + "customCommands": [] + } + } + ] +} diff --git a/src/options/CustomCommands/index.js b/src/options/CustomCommands/index.js new file mode 100644 index 0000000..3c3a1db --- /dev/null +++ b/src/options/CustomCommands/index.js @@ -0,0 +1,8 @@ +import { getAttributes } from 'lib/util' + +export default (options, config) => { + const backgroundOptions = {} + const clientOptions = {} + const errors = {} + return { backgroundOptions, clientOptions, errors } +} diff --git a/src/options/Keybindings/config.json b/src/options/Keybindings/config.json index 42e7b8f..f238b11 100644 --- a/src/options/Keybindings/config.json +++ b/src/options/Keybindings/config.json @@ -485,6 +485,10 @@ "label": "Activate Clipboard in Incognito Window", "key": "clipboardIncognitoWindow", "default": [] + }, + { + "type": "header", + "label": "Custom" } ] } diff --git a/src/options/Keybindings/index.js b/src/options/Keybindings/index.js index feec208..4555c00 100644 --- a/src/options/Keybindings/index.js +++ b/src/options/Keybindings/index.js @@ -30,16 +30,21 @@ export default (options, config) => { } function keybindingsPerMode (options, config) { - const keybindings = {} + const keybindings = { command: [], pass: [] } config.forEach(item => { if (item.type === 'keybinding') { const { mode = 'command', key } = item - if (keybindings[mode]) { - keybindings[mode][key] = options[key] - } else { - keybindings[mode] = { [key]: options[key] } - } + keybindings[mode].push({ bindings: options[key], command: key }) } }) + options.customCommands.forEach((item, i) => { + const { mode = 'command', source, bindings } = item + keybindings[mode].push({ + bindings, + command: "customCommand", + source, + preventDefault: true, + }) + }) return keybindings } diff --git a/src/options/Keybindings/trie.js b/src/options/Keybindings/trie.js index a0cc259..fe72827 100644 --- a/src/options/Keybindings/trie.js +++ b/src/options/Keybindings/trie.js @@ -4,8 +4,8 @@ import { validKeyboardEvent, keyboardEventString } from 'lib/keys' * Given an object mapping commands to their key bindings, * returns the trie representation. */ -export function generateCommandTrie (commandMap) { - return JSONTrie(bindingsList(commandMap)) +export function generateCommandTrie (commandList) { + return JSONTrie(bindingsList(commandList)) } /** @@ -13,13 +13,13 @@ export function generateCommandTrie (commandMap) { * returns an array of (commands, binding). * A command may have more than one binding, or none at all. */ -function bindingsList (bindingsMap) { +function bindingsList (commandList) { const out = [] - for (const [command, bindings] of Object.entries(bindingsMap)) { - if (bindings === undefined) { - throw Error(`A profile is missing bindings for command ${command}`) + for (const command of commandList) { + if (command.bindings === undefined) { + throw Error(`A profile is missing bindings for command ${command.command}`) } - for (const binding of bindings) { + for (const binding of command.bindings) { const keySequence = binding.map(key => { try { validKeyboardEvent(key) @@ -46,11 +46,15 @@ function JSONTrie (bindings) { return trie } +function isTrieNode (thing) { + return typeof thing == 'object' && thing._TrieNode === true +} + function addToTrie (trie, i, key, value, binding) { if (key.length === 0) { throw Error(`${value} has a 0 length key binding`) } else if (i === key.length - 1) { - if (trie.hasOwnProperty(key[i])) { + if (isTrieNode(trie[key[i]])) { throw { message: `${firstLeafValue( trie[key[i]] @@ -63,7 +67,7 @@ function addToTrie (trie, i, key, value, binding) { trie[key[i]] = value } } else { - if (trie.hasOwnProperty(key[i])) { + if (isTrieNode(trie[key[i]])) { if (typeof trie[key[i]] === 'object') { addToTrie(trie[key[i]], i + 1, key, value) } else { @@ -75,7 +79,7 @@ function addToTrie (trie, i, key, value, binding) { } } } else { - addToTrie((trie[key[i]] = {}), i + 1, key, value) + addToTrie((trie[key[i]] = {_TrieNode: true}), i + 1, key, value) } } } diff --git a/src/pages/options/Content/OptionsCard/OptionsList/OptionItem/OptionWidgets/CustomCommands/index.js b/src/pages/options/Content/OptionsCard/OptionsList/OptionItem/OptionWidgets/CustomCommands/index.js new file mode 100644 index 0000000..09a427c --- /dev/null +++ b/src/pages/options/Content/OptionsCard/OptionsList/OptionItem/OptionWidgets/CustomCommands/index.js @@ -0,0 +1,60 @@ +import { Component, h } from 'preact' +import TextArea from '../TextArea' +import Keybinding from '../Keybinding' + +const DEFAULT = { + source: 'document.body.style.backgroundColor = "blue"', + bindings: [], +} + +class CustomCommand extends Component { + render ({ command, values, onChange }) { + return ( +
  • +