Merge remote-tracking branch 'origin/master' into patch-1
This commit is contained in:
commit
9da2f5fd78
119 changed files with 14640 additions and 1632 deletions
|
@ -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 };
|
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 };
|
|
@ -32,6 +32,7 @@ export default {
|
|||
CONSOLE_SHOW_ERROR: 'console.show.error',
|
||||
CONSOLE_SHOW_INFO: 'console.show.info',
|
||||
CONSOLE_SHOW_FIND: 'console.show.find',
|
||||
CONSOLE_HIDE: 'console.hide',
|
||||
|
||||
FOLLOW_START: 'follow.start',
|
||||
FOLLOW_REQUEST_COUNT_TARGETS: 'follow.request.count.targets',
|
||||
|
@ -44,6 +45,8 @@ export default {
|
|||
|
||||
FIND_NEXT: 'find.next',
|
||||
FIND_PREV: 'find.prev',
|
||||
FIND_GET_KEYWORD: 'find.get.keyword',
|
||||
FIND_SET_KEYWORD: 'find.set.keyword',
|
||||
|
||||
OPEN_URL: 'open.url',
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export default {
|
||||
// Hide console, or cancel some user actions
|
||||
CANCEL: 'cancel',
|
||||
|
||||
// Addons
|
||||
ADDON_ENABLE: 'addon.enable',
|
||||
ADDON_DISABLE: 'addon.disable',
|
||||
|
@ -31,13 +34,18 @@ export default {
|
|||
NAVIGATE_PARENT: 'navigate.parent',
|
||||
NAVIGATE_ROOT: 'navigate.root',
|
||||
|
||||
// Focus
|
||||
FOCUS_INPUT: 'focus.input',
|
||||
|
||||
// Tabs
|
||||
TAB_CLOSE: 'tabs.close',
|
||||
TAB_CLOSE_FORCE: 'tabs.close.force',
|
||||
TAB_REOPEN: 'tabs.reopen',
|
||||
TAB_PREV: 'tabs.prev',
|
||||
TAB_NEXT: 'tabs.next',
|
||||
TAB_FIRST: 'tabs.first',
|
||||
TAB_LAST: 'tabs.last',
|
||||
TAB_PREV_SEL: 'tabs.prevsel',
|
||||
TAB_RELOAD: 'tabs.reload',
|
||||
TAB_PIN: 'tabs.pin',
|
||||
TAB_UNPIN: 'tabs.unpin',
|
||||
|
@ -49,8 +57,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',
|
||||
|
|
|
@ -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 },
|
||||
|
@ -25,6 +23,7 @@ export default {
|
|||
"G": { "type": "scroll.bottom" },
|
||||
"$": { "type": "scroll.end" },
|
||||
"d": { "type": "tabs.close" },
|
||||
"!d": { "type": "tabs.close.force" },
|
||||
"u": { "type": "tabs.reopen" },
|
||||
"K": { "type": "tabs.prev", "count": 1 },
|
||||
"J": { "type": "tabs.next", "count": 1 },
|
||||
|
@ -32,6 +31,7 @@ export default {
|
|||
"gt": { "type": "tabs.next", "count": 1 },
|
||||
"g0": { "type": "tabs.first" },
|
||||
"g$": { "type": "tabs.last" },
|
||||
"<C-6>": { "type": "tabs.prevsel" },
|
||||
"r": { "type": "tabs.reload", "cache": false },
|
||||
"R": { "type": "tabs.reload", "cache": true },
|
||||
"zp": { "type": "tabs.pin.toggle" },
|
||||
|
@ -39,15 +39,18 @@ export default {
|
|||
"zi": { "type": "zoom.in" },
|
||||
"zo": { "type": "zoom.out" },
|
||||
"zz": { "type": "zoom.neutral" },
|
||||
"f": { "type": "follow.start", "newTab": false },
|
||||
"F": { "type": "follow.start", "newTab": true },
|
||||
"f": { "type": "follow.start", "newTab": false, "background": false },
|
||||
"F": { "type": "follow.start", "newTab": true, "background": false },
|
||||
"H": { "type": "navigate.history.prev" },
|
||||
"L": { "type": "navigate.history.next" },
|
||||
"[[": { "type": "navigate.link.prev" },
|
||||
"]]": { "type": "navigate.link.next" },
|
||||
"gu": { "type": "navigate.parent" },
|
||||
"gU": { "type": "navigate.root" },
|
||||
"gi": { "type": "focus.input" },
|
||||
"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" },
|
||||
|
@ -63,6 +66,8 @@ export default {
|
|||
"twitter": "https://twitter.com/search?q={}",
|
||||
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
}
|
||||
}`
|
||||
}`,
|
||||
};
|
18
src/shared/settings/properties.js
Normal file
18
src/shared/settings/properties.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
// describe types of a propety as:
|
||||
// mystr: 'string',
|
||||
// mynum: 'number',
|
||||
// mybool: 'boolean',
|
||||
const types = {
|
||||
hintchars: 'string',
|
||||
smoothscroll: 'boolean',
|
||||
adjacenttab: 'boolean',
|
||||
};
|
||||
|
||||
// describe default values of a property
|
||||
const defaults = {
|
||||
hintchars: 'abcdefghijklmnopqrstuvwxyz',
|
||||
smoothscroll: false,
|
||||
adjacenttab: true,
|
||||
};
|
||||
|
||||
export { types, defaults };
|
36
src/shared/settings/storage.js
Normal file
36
src/shared/settings/storage.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
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);
|
||||
}
|
||||
if (!value.properties) {
|
||||
value.properties = {};
|
||||
}
|
||||
return Object.assign({},
|
||||
settingsValues.valueFromJson(DefaultSettings.json),
|
||||
value);
|
||||
});
|
||||
};
|
||||
|
||||
const save = (settings) => {
|
||||
return browser.storage.local.set({
|
||||
settings,
|
||||
});
|
||||
};
|
||||
|
||||
export { loadRaw, loadValue, save };
|
|
@ -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 };
|
108
src/shared/settings/values.js
Normal file
108
src/shared/settings/values.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
import * as properties from './properties';
|
||||
|
||||
const operationFromFormName = (name) => {
|
||||
let [type, argStr] = name.split('?');
|
||||
let args = {};
|
||||
if (argStr) {
|
||||
args = JSON.parse(argStr);
|
||||
}
|
||||
return Object.assign({ type }, args);
|
||||
};
|
||||
|
||||
const operationToFormName = (op) => {
|
||||
let type = op.type;
|
||||
let args = Object.assign({}, op);
|
||||
delete args.type;
|
||||
|
||||
if (Object.keys(args).length === 0) {
|
||||
return type;
|
||||
}
|
||||
return op.type + '?' + JSON.stringify(args);
|
||||
};
|
||||
|
||||
const valueFromJson = (json) => {
|
||||
return JSON.parse(json);
|
||||
};
|
||||
|
||||
const valueFromForm = (form) => {
|
||||
let keymaps = undefined;
|
||||
if (form.keymaps) {
|
||||
keymaps = {};
|
||||
for (let name of Object.keys(form.keymaps)) {
|
||||
let keys = form.keymaps[name];
|
||||
keymaps[keys] = operationFromFormName(name);
|
||||
}
|
||||
}
|
||||
|
||||
let search = undefined;
|
||||
if (form.search) {
|
||||
search = { default: form.search.default };
|
||||
|
||||
if (form.search.engines) {
|
||||
search.engines = {};
|
||||
for (let [name, url] of form.search.engines) {
|
||||
search.engines[name] = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
keymaps,
|
||||
search,
|
||||
blacklist: form.blacklist,
|
||||
properties: form.properties
|
||||
};
|
||||
};
|
||||
|
||||
const jsonFromValue = (value) => {
|
||||
return JSON.stringify(value, undefined, 2);
|
||||
};
|
||||
|
||||
const formFromValue = (value, allowedOps) => {
|
||||
let keymaps = undefined;
|
||||
|
||||
if (value.keymaps) {
|
||||
let allowedSet = new Set(allowedOps);
|
||||
|
||||
keymaps = {};
|
||||
for (let keys of Object.keys(value.keymaps)) {
|
||||
let op = operationToFormName(value.keymaps[keys]);
|
||||
if (allowedSet.has(op)) {
|
||||
keymaps[op] = keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let search = undefined;
|
||||
if (value.search) {
|
||||
search = { default: value.search.default };
|
||||
if (value.search.engines) {
|
||||
search.engines = Object.keys(value.search.engines).map((name) => {
|
||||
return [name, value.search.engines[name]];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let formProperties = Object.assign({}, properties.defaults, value.properties);
|
||||
|
||||
return {
|
||||
keymaps,
|
||||
search,
|
||||
blacklist: value.blacklist,
|
||||
properties: formProperties,
|
||||
};
|
||||
};
|
||||
|
||||
const jsonFromForm = (form) => {
|
||||
return jsonFromValue(valueFromForm(form));
|
||||
};
|
||||
|
||||
const formFromJson = (json, allowedOps) => {
|
||||
let value = valueFromJson(json);
|
||||
return formFromValue(value, allowedOps);
|
||||
};
|
||||
|
||||
export {
|
||||
valueFromJson, valueFromForm, jsonFromValue, formFromValue,
|
||||
jsonFromForm, formFromJson
|
||||
};
|
|
@ -1,18 +1,15 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
class Provider extends React.PureComponent {
|
||||
class Provider extends Component {
|
||||
getChildContext() {
|
||||
return { store: this.props.store };
|
||||
}
|
||||
|
||||
render() {
|
||||
return React.Children.only(this.props.children);
|
||||
return <div>
|
||||
{ this.props.children }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
Provider.childContextTypes = {
|
||||
store: PropTypes.any,
|
||||
};
|
||||
|
||||
export default Provider;
|
||||
|
|
|
@ -81,4 +81,28 @@ const viewportRect = (e) => {
|
|||
};
|
||||
};
|
||||
|
||||
export { isContentEditable, viewportRect };
|
||||
const isVisible = (element) => {
|
||||
let rect = element.getBoundingClientRect();
|
||||
let style = window.getComputedStyle(element);
|
||||
|
||||
if (style.overflow !== 'visible' && (rect.width === 0 || rect.height === 0)) {
|
||||
return false;
|
||||
}
|
||||
if (rect.right < 0 && rect.bottom < 0) {
|
||||
return false;
|
||||
}
|
||||
if (window.innerWidth < rect.left && window.innerHeight < rect.top) {
|
||||
return false;
|
||||
}
|
||||
if (element.nodeName === 'INPUT' && element.type.toLowerCase() === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let { display, visibility } = window.getComputedStyle(element);
|
||||
if (display === 'none' || visibility === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export { isContentEditable, viewportRect, isVisible };
|
||||
|
|
|
@ -18,6 +18,7 @@ const fromKeyboardEvent = (e) => {
|
|||
|
||||
return {
|
||||
key: modifierdKeyName(e.key),
|
||||
repeat: e.repeat,
|
||||
shiftKey: shift,
|
||||
ctrlKey: e.ctrlKey,
|
||||
altKey: e.altKey,
|
||||
|
|
Reference in a new issue