aboutsummaryrefslogtreecommitdiff
path: root/src/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/commands.js169
-rw-r--r--src/shared/commands/complete.js84
-rw-r--r--src/shared/commands/index.js3
-rw-r--r--src/shared/commands/parsers.js59
-rw-r--r--src/shared/operations.js3
-rw-r--r--src/shared/settings/default.js71
-rw-r--r--src/shared/settings/properties.js16
-rw-r--r--src/shared/settings/storage.js33
-rw-r--r--src/shared/settings/validator.js (renamed from src/shared/validators/setting.js)17
-rw-r--r--src/shared/settings/values.js32
-rw-r--r--src/shared/utils/keys.js1
11 files changed, 238 insertions, 250 deletions
diff --git a/src/shared/commands.js b/src/shared/commands.js
deleted file mode 100644
index 8edeb5c..0000000
--- a/src/shared/commands.js
+++ /dev/null
@@ -1,169 +0,0 @@
-import * as tabs from 'background/tabs';
-import * as histories from 'background/histories';
-
-const normalizeUrl = (args, searchConfig) => {
- let concat = args.join(' ');
- try {
- return new URL(concat).href;
- } catch (e) {
- if (concat.includes('.') && !concat.includes(' ')) {
- return 'http://' + concat;
- }
- let query = encodeURI(concat);
- let template = searchConfig.engines[
- searchConfig.default
- ];
- for (let key in searchConfig.engines) {
- if (args[0] === key) {
- query = args.slice(1).join(' ');
- template = searchConfig.engines[key];
- }
- }
- return template.replace('{}', query);
- }
-};
-
-const openCommand = (url) => {
- return browser.tabs.query({
- active: true, currentWindow: true
- }).then((gotTabs) => {
- if (gotTabs.length > 0) {
- return browser.tabs.update(gotTabs[0].id, { url: url });
- }
- });
-};
-
-const tabopenCommand = (url) => {
- return browser.tabs.create({ url: url });
-};
-
-const winopenCommand = (url) => {
- return browser.windows.create({ url });
-};
-
-const bufferCommand = (keywords) => {
- if (keywords.length === 0) {
- return Promise.resolve([]);
- }
- let keywordsStr = keywords.join(' ');
- return browser.tabs.query({
- active: true, currentWindow: true
- }).then((gotTabs) => {
- if (gotTabs.length > 0) {
- if (isNaN(keywordsStr)) {
- return tabs.selectByKeyword(gotTabs[0], keywordsStr);
- }
- let index = parseInt(keywordsStr, 10) - 1;
- return tabs.selectAt(index);
- }
- });
-};
-
-const getOpenCompletions = (command, keywords, searchConfig) => {
- return histories.getCompletions(keywords).then((pages) => {
- let historyItems = pages.map((page) => {
- return {
- caption: page.title,
- content: command + ' ' + page.url,
- url: page.url
- };
- });
- let engineNames = Object.keys(searchConfig.engines);
- let engineItems = engineNames.filter(name => name.startsWith(keywords))
- .map(name => ({
- caption: name,
- content: command + ' ' + name
- }));
-
- let completions = [];
- if (engineItems.length > 0) {
- completions.push({
- name: 'Search Engines',
- items: engineItems
- });
- }
- if (historyItems.length > 0) {
- completions.push({
- name: 'History',
- items: historyItems
- });
- }
- return completions;
- });
-};
-
-const doCommand = (line, settings) => {
- let words = line.trim().split(/ +/);
- let name = words.shift();
-
- switch (name) {
- case 'o':
- case 'open':
- return openCommand(normalizeUrl(words, settings.search));
- case 't':
- case 'tabopen':
- return tabopenCommand(normalizeUrl(words, settings.search));
- case 'w':
- case 'winopen':
- return winopenCommand(normalizeUrl(words, settings.search));
- case 'b':
- case 'buffer':
- return bufferCommand(words);
- case '':
- return Promise.resolve();
- }
- throw new Error(name + ' command is not defined');
-};
-
-const getCompletions = (line, settings) => {
- let typedWords = line.trim().split(/ +/);
- let typing = '';
- if (!line.endsWith(' ')) {
- typing = typedWords.pop();
- }
-
- if (typedWords.length === 0) {
- return Promise.resolve([]);
- }
- let name = typedWords.shift();
- let keywords = typedWords.concat(typing).join(' ');
-
- switch (name) {
- case 'o':
- case 'open':
- case 't':
- case 'tabopen':
- case 'w':
- case 'winopen':
- return getOpenCompletions(name, keywords, settings.search);
- case 'b':
- case 'buffer':
- return tabs.getCompletions(keywords).then((gotTabs) => {
- let items = gotTabs.map((tab) => {
- return {
- caption: tab.title,
- content: name + ' ' + tab.title,
- url: tab.url,
- icon: tab.favIconUrl
- };
- });
- return [
- {
- name: 'Buffers',
- items: items
- }
- ];
- });
- }
- return Promise.resolve([]);
-};
-
-const exec = (line, settings) => {
- return doCommand(line, settings);
-};
-
-const complete = (line, settings) => {
- return getCompletions(line, settings);
-};
-
-export { exec, complete };
diff --git a/src/shared/commands/complete.js b/src/shared/commands/complete.js
new file mode 100644
index 0000000..0bdbab8
--- /dev/null
+++ b/src/shared/commands/complete.js
@@ -0,0 +1,84 @@
+import * as tabs from 'background/tabs';
+import * as histories from 'background/histories';
+
+const getOpenCompletions = (command, keywords, searchConfig) => {
+ return histories.getCompletions(keywords).then((pages) => {
+ let historyItems = pages.map((page) => {
+ return {
+ caption: page.title,
+ content: command + ' ' + page.url,
+ url: page.url
+ };
+ });
+ let engineNames = Object.keys(searchConfig.engines);
+ let engineItems = engineNames.filter(name => name.startsWith(keywords))
+ .map(name => ({
+ caption: name,
+ content: command + ' ' + name
+ }));
+
+ let completions = [];
+ if (engineItems.length > 0) {
+ completions.push({
+ name: 'Search Engines',
+ items: engineItems
+ });
+ }
+ if (historyItems.length > 0) {
+ completions.push({
+ name: 'History',
+ items: historyItems
+ });
+ }
+ return completions;
+ });
+};
+
+const getCompletions = (line, settings) => {
+ let typedWords = line.trim().split(/ +/);
+ let typing = '';
+ if (!line.endsWith(' ')) {
+ typing = typedWords.pop();
+ }
+
+ if (typedWords.length === 0) {
+ return Promise.resolve([]);
+ }
+ let name = typedWords.shift();
+ let keywords = typedWords.concat(typing).join(' ');
+
+ switch (name) {
+ case 'o':
+ case 'open':
+ case 't':
+ case 'tabopen':
+ case 'w':
+ case 'winopen':
+ return getOpenCompletions(name, keywords, settings.search);
+ case 'b':
+ case 'buffer':
+ return tabs.getCompletions(keywords).then((gotTabs) => {
+ let items = gotTabs.map((tab) => {
+ return {
+ caption: tab.title,
+ content: name + ' ' + tab.title,
+ url: tab.url,
+ icon: tab.favIconUrl
+ };
+ });
+ return [
+ {
+ name: 'Buffers',
+ items: items
+ }
+ ];
+ });
+ }
+ return Promise.resolve([]);
+};
+
+const complete = (line, settings) => {
+ return getCompletions(line, settings);
+};
+
+export default complete;
diff --git a/src/shared/commands/index.js b/src/shared/commands/index.js
new file mode 100644
index 0000000..78cb4df
--- /dev/null
+++ b/src/shared/commands/index.js
@@ -0,0 +1,3 @@
+import complete from './complete';
+
+export { complete };
diff --git a/src/shared/commands/parsers.js b/src/shared/commands/parsers.js
new file mode 100644
index 0000000..fb37d2a
--- /dev/null
+++ b/src/shared/commands/parsers.js
@@ -0,0 +1,59 @@
+const normalizeUrl = (args, searchConfig) => {
+ let concat = args.join(' ');
+ try {
+ return new URL(concat).href;
+ } catch (e) {
+ if (concat.includes('.') && !concat.includes(' ')) {
+ return 'http://' + concat;
+ }
+ let query = concat;
+ let template = searchConfig.engines[
+ searchConfig.default
+ ];
+ for (let key in searchConfig.engines) {
+ if (args[0] === key) {
+ query = args.slice(1).join(' ');
+ template = searchConfig.engines[key];
+ }
+ }
+ return template.replace('{}', encodeURIComponent(query));
+ }
+};
+
+const mustNumber = (v) => {
+ let num = Number(v);
+ if (isNaN(num)) {
+ throw new Error('Not number: ' + v);
+ }
+ return num;
+};
+
+const parseSetOption = (word, types) => {
+ let [key, value] = word.split('=');
+ if (value === undefined) {
+ value = !key.startsWith('no');
+ key = value ? key : key.slice(2);
+ }
+ let type = types[key];
+ if (!type) {
+ throw new Error('Unknown property: ' + key);
+ }
+ if (type === 'boolean' && typeof value !== 'boolean' ||
+ type !== 'boolean' && typeof value === 'boolean') {
+ throw new Error('Invalid argument: ' + word);
+ }
+
+ switch (type) {
+ case 'string': return [key, value];
+ case 'number': return [key, mustNumber(value)];
+ case 'boolean': return [key, value];
+ }
+};
+
+const parseCommandLine = (line) => {
+ let words = line.trim().split(/ +/);
+ let name = words.shift();
+ return [name, words];
+};
+
+export { normalizeUrl, parseCommandLine, parseSetOption };
diff --git a/src/shared/operations.js b/src/shared/operations.js
index 235793a..19466df 100644
--- a/src/shared/operations.js
+++ b/src/shared/operations.js
@@ -50,8 +50,9 @@ export default {
ZOOM_OUT: 'zoom.out',
ZOOM_NEUTRAL: 'zoom.neutral',
- // Url yank
+ // Url yank/paste
URLS_YANK: 'urls.yank',
+ URLS_PASTE: 'urls.paste',
// Find
FIND_START: 'find.start',
diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js
index a9c363b..e81df2b 100644
--- a/src/shared/settings/default.js
+++ b/src/shared/settings/default.js
@@ -15,8 +15,6 @@ export default {
"j": { "type": "scroll.vertically", "count": 1 },
"h": { "type": "scroll.horizonally", "count": -1 },
"l": { "type": "scroll.horizonally", "count": 1 },
- "<C-Y>": { "type": "scroll.vertically", "count": -1 },
- "<C-E>": { "type": "scroll.vertically", "count": 1 },
"<C-U>": { "type": "scroll.pages", "count": -0.5 },
"<C-D>": { "type": "scroll.pages", "count": 0.5 },
"<C-B>": { "type": "scroll.pages", "count": -1 },
@@ -47,6 +45,8 @@ export default {
"gu": { "type": "navigate.parent" },
"gU": { "type": "navigate.root" },
"y": { "type": "urls.yank" },
+ "p": { "type": "urls.paste", "newTab": false },
+ "P": { "type": "urls.paste", "newTab": true },
"/": { "type": "find.start" },
"n": { "type": "find.next" },
"N": { "type": "find.prev" },
@@ -62,71 +62,8 @@ export default {
"twitter": "https://twitter.com/search?q={}",
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
}
+ },
+ "properties": {
}
}`,
-
- 'form': {
- 'keymaps': {
- 'scroll.vertically?{"count":1}': 'j',
- 'scroll.vertically?{"count":-1}': 'k',
- 'scroll.horizonally?{"count":-1}': 'h',
- 'scroll.horizonally?{"count":1}': 'l',
- 'scroll.home': '0',
- 'scroll.end': '$',
- 'scroll.pages?{"count":-0.5}': '<C-U>',
- 'scroll.pages?{"count":0.5}': '<C-D>',
- 'scroll.pages?{"count":-1}': '<C-B>',
- 'scroll.pages?{"count":1}': '<C-F>',
-
- 'tabs.close': 'd',
- 'tabs.reopen': 'u',
- 'tabs.next?{"count":1}': 'J',
- 'tabs.prev?{"count":1}': 'K',
- 'tabs.first': 'g0',
- 'tabs.last': 'g$',
- 'tabs.reload?{"cache":true}': 'r',
- 'tabs.pin.toggle': 'zp',
- 'tabs.duplicate': 'zd',
-
- 'follow.start?{"newTab":false}': 'f',
- 'follow.start?{"newTab":true}': 'F',
- 'navigate.history.prev': 'H',
- 'navigate.history.next': 'L',
- 'navigate.link.next': ']]',
- 'navigate.link.prev': '[[',
- 'navigate.parent': 'gu',
- 'navigate.root': 'gU',
-
- 'find.start': '/',
- 'find.next': 'n',
- 'find.prev': 'N',
-
- 'command.show': ':',
- 'command.show.open?{"alter":false}': 'o',
- 'command.show.open?{"alter":true}': 'O',
- 'command.show.tabopen?{"alter":false}': 't',
- 'command.show.tabopen?{"alter":true}': 'T',
- 'command.show.winopen?{"alter":false}': 'w',
- 'command.show.winopen?{"alter":true}': 'W',
- 'command.show.buffer': 'b',
-
- 'addon.toggle.enabled': '<S-Esc>',
- 'urls.yank': 'y',
- 'zoom.in': 'zi',
- 'zoom.out': 'zo',
- 'zoom.neutral': 'zz',
- },
- 'search': {
- 'default': 'google',
- 'engines': [
- ['google', 'https,//google.com/search?q={}'],
- ['yahoo', 'https,//search.yahoo.com/search?p={}'],
- ['bing', 'https,//www.bing.com/search?q={}'],
- ['duckduckgo', 'https,//duckduckgo.com/?q={}'],
- ['twitter', 'https,//twitter.com/search?q={}'],
- ['wikipedia', 'https,//en.wikipedia.org/w/index.php?search={}'],
- ]
- },
- 'blacklist': [],
- }
};
diff --git a/src/shared/settings/properties.js b/src/shared/settings/properties.js
new file mode 100644
index 0000000..37dc881
--- /dev/null
+++ b/src/shared/settings/properties.js
@@ -0,0 +1,16 @@
+// describe types of a propety as:
+// mystr: 'string',
+// mynum: 'number',
+// mybool: 'boolean',
+const types = {
+ hintchars: 'string',
+ smoothscroll: 'boolean',
+};
+
+// describe default values of a property
+const defaults = {
+ hintchars: 'abcdefghijklmnopqrstuvwxyz',
+ smoothscroll: false,
+};
+
+export { types, defaults };
diff --git a/src/shared/settings/storage.js b/src/shared/settings/storage.js
new file mode 100644
index 0000000..25ebfcd
--- /dev/null
+++ b/src/shared/settings/storage.js
@@ -0,0 +1,33 @@
+import DefaultSettings from './default';
+import * as settingsValues from './values';
+
+const loadRaw = () => {
+ return browser.storage.local.get('settings').then(({ settings }) => {
+ if (!settings) {
+ return DefaultSettings;
+ }
+ return Object.assign({}, DefaultSettings, settings);
+ });
+};
+
+const loadValue = () => {
+ return loadRaw().then((settings) => {
+ let value = JSON.parse(DefaultSettings.json);
+ if (settings.source === 'json') {
+ value = settingsValues.valueFromJson(settings.json);
+ } else if (settings.source === 'form') {
+ value = settingsValues.valueFromForm(settings.form);
+ }
+ return Object.assign({},
+ settingsValues.valueFromJson(DefaultSettings.json),
+ value);
+ });
+};
+
+const save = (settings) => {
+ return browser.storage.local.set({
+ settings,
+ });
+};
+
+export { loadRaw, loadValue, save };
diff --git a/src/shared/validators/setting.js b/src/shared/settings/validator.js
index 949ab29..1589420 100644
--- a/src/shared/validators/setting.js
+++ b/src/shared/settings/validator.js
@@ -1,6 +1,7 @@
import operations from 'shared/operations';
+import * as properties from './properties';
-const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist'];
+const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties'];
const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => {
return operations[key];
});
@@ -48,6 +49,17 @@ const validateSearch = (search) => {
}
};
+const validateProperties = (props) => {
+ for (let name of Object.keys(props)) {
+ if (!properties.types[name]) {
+ throw new Error(`Unknown property name: "${name}"`);
+ }
+ if (typeof props[name] !== properties.types[name]) {
+ throw new Error(`Invalid type for property: "${name}"`);
+ }
+ }
+};
+
const validate = (settings) => {
validateInvalidTopKeys(settings);
if (settings.keymaps) {
@@ -56,6 +68,9 @@ const validate = (settings) => {
if (settings.search) {
validateSearch(settings.search);
}
+ if (settings.properties) {
+ validateProperties(settings.properties);
+ }
};
export { validate };
diff --git a/src/shared/settings/values.js b/src/shared/settings/values.js
index 4482fbb..bd03be2 100644
--- a/src/shared/settings/values.js
+++ b/src/shared/settings/values.js
@@ -1,4 +1,4 @@
-import DefaultSettings from './default';
+import * as properties from './properties';
const operationFromFormName = (name) => {
let [type, argStr] = name.split('?');
@@ -46,25 +46,28 @@ const valueFromForm = (form) => {
}
}
- let blacklist = form.blacklist;
-
- return { keymaps, search, blacklist };
+ return {
+ keymaps,
+ search,
+ blacklist: form.blacklist,
+ properties: form.properties
+ };
};
const jsonFromValue = (value) => {
return JSON.stringify(value, undefined, 2);
};
-const formFromValue = (value) => {
-
+const formFromValue = (value, allowedOps) => {
let keymaps = undefined;
+
if (value.keymaps) {
- let allowedOps = new Set(Object.keys(DefaultSettings.form.keymaps));
+ let allowedSet = new Set(allowedOps);
keymaps = {};
for (let keys of Object.keys(value.keymaps)) {
let op = operationToFormName(value.keymaps[keys]);
- if (allowedOps.has(op)) {
+ if (allowedSet.has(op)) {
keymaps[op] = keys;
}
}
@@ -80,18 +83,23 @@ const formFromValue = (value) => {
}
}
- let blacklist = value.blacklist;
+ let formProperties = Object.assign({}, properties.defaults, value.properties);
- return { keymaps, search, blacklist };
+ return {
+ keymaps,
+ search,
+ blacklist: value.blacklist,
+ properties: formProperties,
+ };
};
const jsonFromForm = (form) => {
return jsonFromValue(valueFromForm(form));
};
-const formFromJson = (json) => {
+const formFromJson = (json, allowedOps) => {
let value = valueFromJson(json);
- return formFromValue(value);
+ return formFromValue(value, allowedOps);
};
export {
diff --git a/src/shared/utils/keys.js b/src/shared/utils/keys.js
index fba8ce3..8a86dfb 100644
--- a/src/shared/utils/keys.js
+++ b/src/shared/utils/keys.js
@@ -18,6 +18,7 @@ const fromKeyboardEvent = (e) => {
return {
key: modifierdKeyName(e.key),
+ repeat: e.repeat,
shiftKey: shift,
ctrlKey: e.ctrlKey,
altKey: e.altKey,