commit
f5dfdb0bd7
25 changed files with 691 additions and 201 deletions
79
src/background/actions/command.js
Normal file
79
src/background/actions/command.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import actions from '../actions';
|
||||||
|
import * as tabs from 'background/tabs';
|
||||||
|
import * as parsers from 'shared/commands/parsers';
|
||||||
|
import * as properties from 'shared/settings/properties';
|
||||||
|
|
||||||
|
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 setCommand = (args) => {
|
||||||
|
if (!args[0]) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let [name, value] = parsers.parseSetOption(args[0], properties.types);
|
||||||
|
return {
|
||||||
|
type: actions.SETTING_SET_PROPERTY,
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const exec = (line, settings) => {
|
||||||
|
let [name, args] = parsers.parseCommandLine(line);
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case 'o':
|
||||||
|
case 'open':
|
||||||
|
return openCommand(parsers.normalizeUrl(args, settings.search));
|
||||||
|
case 't':
|
||||||
|
case 'tabopen':
|
||||||
|
return tabopenCommand(parsers.normalizeUrl(args, settings.search));
|
||||||
|
case 'w':
|
||||||
|
case 'winopen':
|
||||||
|
return winopenCommand(parsers.normalizeUrl(args, settings.search));
|
||||||
|
case 'b':
|
||||||
|
case 'buffer':
|
||||||
|
return bufferCommand(args);
|
||||||
|
case 'set':
|
||||||
|
return setCommand(args);
|
||||||
|
case '':
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
throw new Error(name + ' command is not defined');
|
||||||
|
};
|
||||||
|
|
||||||
|
export { exec };
|
5
src/background/actions/index.js
Normal file
5
src/background/actions/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export default {
|
||||||
|
// Settings
|
||||||
|
SETTING_SET_SETTINGS: 'setting.set.settings',
|
||||||
|
SETTING_SET_PROPERTY: 'setting.set.property',
|
||||||
|
};
|
21
src/background/actions/setting.js
Normal file
21
src/background/actions/setting.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import actions from '../actions';
|
||||||
|
import * as settingsStorage from 'shared/settings/storage';
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
return settingsStorage.loadValue().then((value) => {
|
||||||
|
return {
|
||||||
|
type: actions.SETTING_SET_SETTINGS,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setProperty = (name, value) => {
|
||||||
|
return {
|
||||||
|
type: actions.SETTING_SET_PROPERTY,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { load, setProperty };
|
|
@ -1,6 +1,7 @@
|
||||||
import messages from 'shared/messages';
|
import messages from 'shared/messages';
|
||||||
import * as operationActions from 'background/actions/operation';
|
import * as operationActions from 'background/actions/operation';
|
||||||
import * as settingsActions from 'settings/actions/setting';
|
import * as commandActions from 'background/actions/command';
|
||||||
|
import * as settingActions from 'background/actions/setting';
|
||||||
import * as tabActions from 'background/actions/tab';
|
import * as tabActions from 'background/actions/tab';
|
||||||
import * as commands from 'shared/commands';
|
import * as commands from 'shared/commands';
|
||||||
|
|
||||||
|
@ -35,18 +36,17 @@ export default class BackgroundComponent {
|
||||||
return this.store.dispatch(
|
return this.store.dispatch(
|
||||||
tabActions.openToTab(message.url, sender.tab), sender);
|
tabActions.openToTab(message.url, sender.tab), sender);
|
||||||
case messages.CONSOLE_ENTER_COMMAND:
|
case messages.CONSOLE_ENTER_COMMAND:
|
||||||
return commands.exec(message.text, settings.value).catch((e) => {
|
this.store.dispatch(
|
||||||
return browser.tabs.sendMessage(sender.tab.id, {
|
commandActions.exec(message.text, settings.value),
|
||||||
type: messages.CONSOLE_SHOW_ERROR,
|
sender
|
||||||
text: e.message,
|
);
|
||||||
});
|
return this.broadcastSettingsChanged();
|
||||||
});
|
|
||||||
case messages.SETTINGS_QUERY:
|
case messages.SETTINGS_QUERY:
|
||||||
return Promise.resolve(this.store.getState().setting.value);
|
return Promise.resolve(this.store.getState().setting.value);
|
||||||
case messages.CONSOLE_QUERY_COMPLETIONS:
|
case messages.CONSOLE_QUERY_COMPLETIONS:
|
||||||
return commands.complete(message.text, settings.value);
|
return commands.complete(message.text, settings.value);
|
||||||
case messages.SETTINGS_RELOAD:
|
case messages.SETTINGS_RELOAD:
|
||||||
this.store.dispatch(settingsActions.load());
|
this.store.dispatch(settingActions.load());
|
||||||
return this.broadcastSettingsChanged();
|
return this.broadcastSettingsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as settingsActions from 'settings/actions/setting';
|
import * as settingActions from 'background/actions/setting';
|
||||||
import messages from 'shared/messages';
|
import messages from 'shared/messages';
|
||||||
import BackgroundComponent from 'background/components/background';
|
import BackgroundComponent from 'background/components/background';
|
||||||
import reducers from 'background/reducers';
|
import reducers from 'background/reducers';
|
||||||
|
@ -16,4 +16,4 @@ const store = createStore(reducers, (e, sender) => {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const backgroundComponent = new BackgroundComponent(store);
|
const backgroundComponent = new BackgroundComponent(store);
|
||||||
|
|
||||||
store.dispatch(settingsActions.load());
|
store.dispatch(settingActions.load());
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import settingReducer from 'settings/reducers/setting';
|
import settingReducer from './setting';
|
||||||
|
|
||||||
// Make setting reducer instead of re-use
|
// Make setting reducer instead of re-use
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
|
|
24
src/background/reducers/setting.js
Normal file
24
src/background/reducers/setting.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import actions from 'background/actions';
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
value: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action = {}) {
|
||||||
|
switch (action.type) {
|
||||||
|
case actions.SETTING_SET_SETTINGS:
|
||||||
|
return {
|
||||||
|
value: action.value,
|
||||||
|
};
|
||||||
|
case actions.SETTING_SET_PROPERTY:
|
||||||
|
return {
|
||||||
|
value: Object.assign({}, state.value, {
|
||||||
|
properties: Object.assign({}, state.value.properties,
|
||||||
|
{ [action.name]: action.value })
|
||||||
|
})
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
import actions from 'settings/actions';
|
import actions from 'settings/actions';
|
||||||
import messages from 'shared/messages';
|
import messages from 'shared/messages';
|
||||||
import DefaultSettings from 'shared/settings/default';
|
import DefaultSettings from 'shared/settings/default';
|
||||||
|
import * as settingsStorage from 'shared/settings/storage';
|
||||||
import * as settingsValues from 'shared/settings/values';
|
import * as settingsValues from 'shared/settings/values';
|
||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
return browser.storage.local.get('settings').then(({ settings }) => {
|
return settingsStorage.loadRaw().then((settings) => {
|
||||||
if (!settings) {
|
return set(settings);
|
||||||
return set(DefaultSettings);
|
});
|
||||||
}
|
|
||||||
return set(Object.assign({}, DefaultSettings, settings));
|
|
||||||
}, console.error);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = (settings) => {
|
const save = (settings) => {
|
||||||
return browser.storage.local.set({
|
return settingsStorage.save(settings).then(() => {
|
||||||
settings,
|
|
||||||
}).then(() => {
|
|
||||||
return browser.runtime.sendMessage({
|
return browser.runtime.sendMessage({
|
||||||
type: messages.SETTINGS_RELOAD
|
type: messages.SETTINGS_RELOAD
|
||||||
}).then(() => {
|
|
||||||
return set(settings);
|
|
||||||
});
|
});
|
||||||
|
}).then(() => {
|
||||||
|
return set(settings);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
60
src/settings/components/form/properties-form.jsx
Normal file
60
src/settings/components/form/properties-form.jsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import './properties-form.scss';
|
||||||
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
|
class PropertiesForm extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let types = this.props.types;
|
||||||
|
let value = this.props.value;
|
||||||
|
if (!value) {
|
||||||
|
value = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className='form-properties-form'>
|
||||||
|
{
|
||||||
|
Object.keys(types).map((name) => {
|
||||||
|
let type = types[name];
|
||||||
|
let inputType = null;
|
||||||
|
if (type === 'string') {
|
||||||
|
inputType = 'text';
|
||||||
|
} else if (type === 'number') {
|
||||||
|
inputType = 'number';
|
||||||
|
} else if (type === 'boolean') {
|
||||||
|
inputType = 'checkbox';
|
||||||
|
}
|
||||||
|
return <div key={name} className='form-properties-form-row'>
|
||||||
|
<label>
|
||||||
|
<span className='column-name'>{name}</span>
|
||||||
|
<input type={inputType} name={name}
|
||||||
|
className='column-input'
|
||||||
|
value={value[name] ? value[name] : ''}
|
||||||
|
onChange={this.bindValue.bind(this)}
|
||||||
|
checked={value[name]}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindValue(e) {
|
||||||
|
if (!this.props.onChange) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = e.target.name;
|
||||||
|
let next = Object.assign({}, this.props.value);
|
||||||
|
if (e.target.type.toLowerCase() === 'checkbox') {
|
||||||
|
next[name] = e.target.checked;
|
||||||
|
} else if (e.target.type.toLowerCase() === 'number') {
|
||||||
|
next[name] = Number(e.target.value);
|
||||||
|
} else {
|
||||||
|
next[name] = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onChange(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PropertiesForm;
|
12
src/settings/components/form/properties-form.scss
Normal file
12
src/settings/components/form/properties-form.scss
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.form-properties-form {
|
||||||
|
&-row {
|
||||||
|
.column-name {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.column-input {
|
||||||
|
line-height: 2.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,10 @@ import Input from './ui/input';
|
||||||
import SearchForm from './form/search-form';
|
import SearchForm from './form/search-form';
|
||||||
import KeymapsForm from './form/keymaps-form';
|
import KeymapsForm from './form/keymaps-form';
|
||||||
import BlacklistForm from './form/blacklist-form';
|
import BlacklistForm from './form/blacklist-form';
|
||||||
|
import PropertiesForm from './form/properties-form';
|
||||||
|
import * as properties from 'shared/settings/properties';
|
||||||
import * as settingActions from 'settings/actions/setting';
|
import * as settingActions from 'settings/actions/setting';
|
||||||
import * as validator from 'shared/validators/setting';
|
import * as validator from 'shared/settings/validator';
|
||||||
import * as settingsValues from 'shared/settings/values';
|
import * as settingsValues from 'shared/settings/values';
|
||||||
|
|
||||||
const DO_YOU_WANT_TO_CONTINUE =
|
const DO_YOU_WANT_TO_CONTINUE =
|
||||||
|
@ -65,6 +67,14 @@ class SettingsComponent extends Component {
|
||||||
onChange={value => this.bindForm('blacklist', value)}
|
onChange={value => this.bindForm('blacklist', value)}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Properties</legend>
|
||||||
|
<PropertiesForm
|
||||||
|
types={properties.types}
|
||||||
|
value={this.state.settings.form.properties}
|
||||||
|
onChange={value => this.bindForm('properties', value)}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = 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 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 };
|
|
84
src/shared/commands/complete.js
Normal file
84
src/shared/commands/complete.js
Normal file
|
@ -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;
|
3
src/shared/commands/index.js
Normal file
3
src/shared/commands/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import complete from './complete';
|
||||||
|
|
||||||
|
export { complete };
|
59
src/shared/commands/parsers.js
Normal file
59
src/shared/commands/parsers.js
Normal file
|
@ -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 };
|
|
@ -58,6 +58,8 @@ export default {
|
||||||
"duckduckgo": "https://duckduckgo.com/?q={}",
|
"duckduckgo": "https://duckduckgo.com/?q={}",
|
||||||
"twitter": "https://twitter.com/search?q={}",
|
"twitter": "https://twitter.com/search?q={}",
|
||||||
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
|
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
|
|
15
src/shared/settings/properties.js
Normal file
15
src/shared/settings/properties.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const types = {
|
||||||
|
// TODO describe property types here
|
||||||
|
// mystr: 'string',
|
||||||
|
// mynum: 'number',
|
||||||
|
// mybool: 'boolean',
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
// TODO describe property defaults values
|
||||||
|
// mystr: 'hello',
|
||||||
|
// mynum: 123,
|
||||||
|
// mybool: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { types, defaults };
|
31
src/shared/settings/storage.js
Normal file
31
src/shared/settings/storage.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
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 value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = (settings) => {
|
||||||
|
return browser.storage.local.set({
|
||||||
|
settings,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { loadRaw, loadValue, save };
|
|
@ -1,6 +1,7 @@
|
||||||
import operations from 'shared/operations';
|
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) => {
|
const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => {
|
||||||
return operations[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) => {
|
const validate = (settings) => {
|
||||||
validateInvalidTopKeys(settings);
|
validateInvalidTopKeys(settings);
|
||||||
if (settings.keymaps) {
|
if (settings.keymaps) {
|
||||||
|
@ -56,6 +68,9 @@ const validate = (settings) => {
|
||||||
if (settings.search) {
|
if (settings.search) {
|
||||||
validateSearch(settings.search);
|
validateSearch(settings.search);
|
||||||
}
|
}
|
||||||
|
if (settings.properties) {
|
||||||
|
validateProperties(settings.properties);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { validate };
|
export { validate };
|
|
@ -1,3 +1,5 @@
|
||||||
|
import * as properties from './properties';
|
||||||
|
|
||||||
const operationFromFormName = (name) => {
|
const operationFromFormName = (name) => {
|
||||||
let [type, argStr] = name.split('?');
|
let [type, argStr] = name.split('?');
|
||||||
let args = {};
|
let args = {};
|
||||||
|
@ -44,9 +46,12 @@ const valueFromForm = (form) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let blacklist = form.blacklist;
|
return {
|
||||||
|
keymaps,
|
||||||
return { keymaps, search, blacklist };
|
search,
|
||||||
|
blacklist: form.blacklist,
|
||||||
|
properties: form.properties
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonFromValue = (value) => {
|
const jsonFromValue = (value) => {
|
||||||
|
@ -78,9 +83,14 @@ const formFromValue = (value, allowedOps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => {
|
const jsonFromForm = (form) => {
|
||||||
|
|
37
test/background/reducers/setting.test.js
Normal file
37
test/background/reducers/setting.test.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
import actions from 'background/actions';
|
||||||
|
import settingReducer from 'background/reducers/setting';
|
||||||
|
|
||||||
|
describe("setting reducer", () => {
|
||||||
|
it('return the initial state', () => {
|
||||||
|
let state = settingReducer(undefined, {});
|
||||||
|
expect(state).to.have.deep.property('value', {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return next state for SETTING_SET_SETTINGS', () => {
|
||||||
|
let action = {
|
||||||
|
type: actions.SETTING_SET_SETTINGS,
|
||||||
|
value: { key: 123 },
|
||||||
|
};
|
||||||
|
let state = settingReducer(undefined, action);
|
||||||
|
expect(state).to.have.deep.property('value', { key: 123 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return next state for SETTING_SET_PROPERTY', () => {
|
||||||
|
let state = {
|
||||||
|
value: {
|
||||||
|
properties: { smoothscroll: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let action = {
|
||||||
|
type: actions.SETTING_SET_PROPERTY,
|
||||||
|
name: 'encoding',
|
||||||
|
value: 'utf-8',
|
||||||
|
};
|
||||||
|
state = settingReducer(state, action);
|
||||||
|
|
||||||
|
console.log(state);
|
||||||
|
expect(state.value.properties).to.have.property('smoothscroll', true);
|
||||||
|
expect(state.value.properties).to.have.property('encoding', 'utf-8');
|
||||||
|
});
|
||||||
|
});
|
86
test/settings/components/form/properties-form.test.jsx
Normal file
86
test/settings/components/form/properties-form.test.jsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { h, render } from 'preact';
|
||||||
|
import PropertiesForm from 'settings/components/form/properties-form'
|
||||||
|
|
||||||
|
describe("settings/form/PropertiesForm", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('render', () => {
|
||||||
|
it('renders PropertiesForm', () => {
|
||||||
|
let types = {
|
||||||
|
mystr: 'string',
|
||||||
|
mynum: 'number',
|
||||||
|
mybool: 'boolean',
|
||||||
|
empty: 'string',
|
||||||
|
}
|
||||||
|
let value = {
|
||||||
|
mystr: 'abc',
|
||||||
|
mynum: 123,
|
||||||
|
mybool: true,
|
||||||
|
};
|
||||||
|
render(<PropertiesForm types={types} value={value} />, document.body);
|
||||||
|
|
||||||
|
let strInput = document.querySelector('input[name=mystr]');
|
||||||
|
let numInput = document.querySelector('input[name=mynum]');
|
||||||
|
let boolInput = document.querySelector('input[name=mybool]');
|
||||||
|
let emptyInput = document.querySelector('input[name=empty]');
|
||||||
|
|
||||||
|
expect(strInput.type).to.equals('text');
|
||||||
|
expect(strInput.value).to.equal('abc');
|
||||||
|
expect(numInput.type).to.equals('number');
|
||||||
|
expect(numInput.value).to.equal('123');
|
||||||
|
expect(boolInput.type).to.equals('checkbox');
|
||||||
|
expect(boolInput.checked).to.be.true;
|
||||||
|
expect(emptyInput.type).to.equals('text');
|
||||||
|
expect(emptyInput.value).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onChange', () => {
|
||||||
|
it('invokes onChange event on text changed', (done) => {
|
||||||
|
render(<PropertiesForm
|
||||||
|
types={{ 'myvalue': 'string' }}
|
||||||
|
value={{ 'myvalue': 'abc' }}
|
||||||
|
onChange={value => {
|
||||||
|
expect(value).to.have.property('myvalue', 'abcd');
|
||||||
|
done();
|
||||||
|
}}
|
||||||
|
/>, document.body);
|
||||||
|
|
||||||
|
let input = document.querySelector('input[name=myvalue]');
|
||||||
|
input.value = 'abcd'
|
||||||
|
input.dispatchEvent(new Event('change'))
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invokes onChange event on number changeed', (done) => {
|
||||||
|
render(<PropertiesForm
|
||||||
|
types={{ 'myvalue': 'number' }}
|
||||||
|
value={{ '': 123 }}
|
||||||
|
onChange={value => {
|
||||||
|
expect(value).to.have.property('myvalue', 1234);
|
||||||
|
done();
|
||||||
|
}}
|
||||||
|
/>, document.body);
|
||||||
|
|
||||||
|
let input = document.querySelector('input[name=myvalue]');
|
||||||
|
input.value = '1234'
|
||||||
|
input.dispatchEvent(new Event('change'))
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invokes onChange event on checkbox changed', (done) => {
|
||||||
|
render(<PropertiesForm
|
||||||
|
types={{ 'myvalue': 'boolean' }}
|
||||||
|
value={{ 'myvalue': false }}
|
||||||
|
onChange={value => {
|
||||||
|
expect(value).to.have.property('myvalue', true);
|
||||||
|
done();
|
||||||
|
}}
|
||||||
|
/>, document.body);
|
||||||
|
|
||||||
|
let input = document.querySelector('input[name=myvalue]');
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
85
test/shared/commands/parsers.test.js
Normal file
85
test/shared/commands/parsers.test.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
import * as parsers from 'shared/commands/parsers';
|
||||||
|
|
||||||
|
describe("shared/commands/parsers", () => {
|
||||||
|
describe("#parsers.parseSetOption", () => {
|
||||||
|
it('parse set string', () => {
|
||||||
|
let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' });
|
||||||
|
expect(key).to.equal('encoding');
|
||||||
|
expect(value).to.equal('utf-8');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parse set empty string', () => {
|
||||||
|
let [key, value] = parsers.parseSetOption('encoding=', { encoding: 'string' });
|
||||||
|
expect(key).to.equal('encoding');
|
||||||
|
expect(value).to.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parse set string', () => {
|
||||||
|
let [key, value] = parsers.parseSetOption('history=50', { history: 'number' });
|
||||||
|
expect(key).to.equal('history');
|
||||||
|
expect(value).to.equal(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parse set boolean', () => {
|
||||||
|
let [key, value] = parsers.parseSetOption('paste', { paste: 'boolean' });
|
||||||
|
expect(key).to.equal('paste');
|
||||||
|
expect(value).to.be.true;
|
||||||
|
|
||||||
|
[key, value] = parsers.parseSetOption('nopaste', { paste: 'boolean' });
|
||||||
|
expect(key).to.equal('paste');
|
||||||
|
expect(value).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws error on unknown property', () => {
|
||||||
|
expect(() => parsers.parseSetOption('charset=utf-8', {})).to.throw(Error, 'Unknown');
|
||||||
|
expect(() => parsers.parseSetOption('smoothscroll', {})).to.throw(Error, 'Unknown');
|
||||||
|
expect(() => parsers.parseSetOption('nosmoothscroll', {})).to.throw(Error, 'Unknown');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws error on invalid property', () => {
|
||||||
|
expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number');
|
||||||
|
expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid');
|
||||||
|
expect(() => parsers.parseSetOption('charset=', { charset: 'boolean' })).to.throw(Error, 'Invalid');
|
||||||
|
expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid');
|
||||||
|
expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#normalizeUrl', () => {
|
||||||
|
const config = {
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
google: 'https://google.com/search?q={}',
|
||||||
|
yahoo: 'https://yahoo.com/search?q={}',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('convertes search url', () => {
|
||||||
|
expect(parsers.normalizeUrl(['google', 'apple'], config))
|
||||||
|
.to.equal('https://google.com/search?q=apple');
|
||||||
|
expect(parsers.normalizeUrl(['yahoo', 'apple'], config))
|
||||||
|
.to.equal('https://yahoo.com/search?q=apple');
|
||||||
|
expect(parsers.normalizeUrl(['google', 'apple', 'banana'], config))
|
||||||
|
.to.equal('https://google.com/search?q=apple%20banana');
|
||||||
|
expect(parsers.normalizeUrl(['yahoo', 'C++CLI'], config))
|
||||||
|
.to.equal('https://yahoo.com/search?q=C%2B%2BCLI');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user default search engine', () => {
|
||||||
|
expect(parsers.normalizeUrl(['apple', 'banana'], config))
|
||||||
|
.to.equal('https://google.com/search?q=apple%20banana');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#parseCommandLine', () => {
|
||||||
|
it('parse command line as name and args', () => {
|
||||||
|
expect(parsers.parseCommandLine('open google apple')).to.deep.equal(['open', ['google', 'apple']]);
|
||||||
|
expect(parsers.parseCommandLine(' open google apple ')).to.deep.equal(['open', ['google', 'apple']]);
|
||||||
|
expect(parsers.parseCommandLine('')).to.deep.equal(['', []]);
|
||||||
|
expect(parsers.parseCommandLine(' ')).to.deep.equal(['', []]);
|
||||||
|
expect(parsers.parseCommandLine('exit')).to.deep.equal(['exit', []]);
|
||||||
|
expect(parsers.parseCommandLine(' exit ')).to.deep.equal(['exit', []]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { validate } from 'shared/validators/setting';
|
import { validate } from 'shared/settings/validator';
|
||||||
|
|
||||||
describe("setting validator", () => {
|
describe("setting validator", () => {
|
||||||
describe("unknown top keys", () => {
|
describe("unknown top keys", () => {
|
|
@ -7,13 +7,21 @@ describe("settings values", () => {
|
||||||
let json = `{
|
let json = `{
|
||||||
"keymaps": { "0": {"type": "scroll.home"}},
|
"keymaps": { "0": {"type": "scroll.home"}},
|
||||||
"search": { "default": "google", "engines": { "google": "https://google.com/search?q={}" }},
|
"search": { "default": "google", "engines": { "google": "https://google.com/search?q={}" }},
|
||||||
"blacklist": [ "*.slack.com"]
|
"blacklist": [ "*.slack.com"],
|
||||||
|
"properties": {
|
||||||
|
"mystr": "value",
|
||||||
|
"mynum": 123,
|
||||||
|
"mybool": true
|
||||||
|
}
|
||||||
}`;
|
}`;
|
||||||
let value = values.valueFromJson(json);
|
let value = values.valueFromJson(json);
|
||||||
|
|
||||||
expect(value.keymaps).to.deep.equal({ 0: {type: "scroll.home"}});
|
expect(value.keymaps).to.deep.equal({ 0: {type: "scroll.home"}});
|
||||||
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} });
|
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} });
|
||||||
expect(value.blacklist).to.deep.equal(["*.slack.com"]);
|
expect(value.blacklist).to.deep.equal(["*.slack.com"]);
|
||||||
|
expect(value.properties).to.have.property('mystr', 'value');
|
||||||
|
expect(value.properties).to.have.property('mynum', 123);
|
||||||
|
expect(value.properties).to.have.property('mybool', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,6 +37,11 @@ describe("settings values", () => {
|
||||||
engines: [['google', 'https://google.com/search?q={}']],
|
engines: [['google', 'https://google.com/search?q={}']],
|
||||||
},
|
},
|
||||||
blacklist: ['*.slack.com'],
|
blacklist: ['*.slack.com'],
|
||||||
|
"properties": {
|
||||||
|
"mystr": "value",
|
||||||
|
"mynum": 123,
|
||||||
|
"mybool": true,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let value = values.valueFromForm(form);
|
let value = values.valueFromForm(form);
|
||||||
|
|
||||||
|
@ -37,6 +50,9 @@ describe("settings values", () => {
|
||||||
expect(JSON.stringify(value.search)).to.deep.equal(JSON.stringify({ default: "google", engines: { google: "https://google.com/search?q={}"} }));
|
expect(JSON.stringify(value.search)).to.deep.equal(JSON.stringify({ default: "google", engines: { google: "https://google.com/search?q={}"} }));
|
||||||
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} });
|
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} });
|
||||||
expect(value.blacklist).to.deep.equal(["*.slack.com"]);
|
expect(value.blacklist).to.deep.equal(["*.slack.com"]);
|
||||||
|
expect(value.properties).to.have.property('mystr', 'value');
|
||||||
|
expect(value.properties).to.have.property('mynum', 123);
|
||||||
|
expect(value.properties).to.have.property('mybool', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('convert from empty form', () => {
|
it('convert from empty form', () => {
|
||||||
|
@ -45,6 +61,7 @@ describe("settings values", () => {
|
||||||
expect(value).to.not.have.key('keymaps');
|
expect(value).to.not.have.key('keymaps');
|
||||||
expect(value).to.not.have.key('search');
|
expect(value).to.not.have.key('search');
|
||||||
expect(value).to.not.have.key('blacklist');
|
expect(value).to.not.have.key('blacklist');
|
||||||
|
expect(value).to.not.have.key('properties');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('override keymaps', () => {
|
it('override keymaps', () => {
|
||||||
|
@ -96,7 +113,12 @@ describe("settings values", () => {
|
||||||
0: { type: 'scroll.home' },
|
0: { type: 'scroll.home' },
|
||||||
},
|
},
|
||||||
search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }},
|
search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }},
|
||||||
blacklist: [ '*.slack.com']
|
blacklist: [ '*.slack.com'],
|
||||||
|
properties: {
|
||||||
|
"mystr": "value",
|
||||||
|
"mynum": 123,
|
||||||
|
"mybool": true,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let allowed = ['scroll.vertically?{"count":1}', 'scroll.home' ];
|
let allowed = ['scroll.vertically?{"count":1}', 'scroll.home' ];
|
||||||
let form = values.formFromValue(value, allowed);
|
let form = values.formFromValue(value, allowed);
|
||||||
|
@ -109,6 +131,9 @@ describe("settings values", () => {
|
||||||
expect(form.search).to.have.deep.property('engines', [['google', 'https://google.com/search?q={}']]);
|
expect(form.search).to.have.deep.property('engines', [['google', 'https://google.com/search?q={}']]);
|
||||||
expect(form.blacklist).to.have.lengthOf(1);
|
expect(form.blacklist).to.have.lengthOf(1);
|
||||||
expect(form.blacklist).to.include('*.slack.com');
|
expect(form.blacklist).to.include('*.slack.com');
|
||||||
|
expect(form.properties).to.have.property('mystr', 'value');
|
||||||
|
expect(form.properties).to.have.property('mynum', 123);
|
||||||
|
expect(form.properties).to.have.property('mybool', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Reference in a new issue