diff --git a/.eslintrc b/.eslintrc index 949b5a5..0f230c7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -36,6 +36,7 @@ "newline-after-var": "off", "newline-before-return": "off", "newline-per-chained-call": "off", + "no-alert": "off", "no-bitwise": "off", "no-console": ["error", { "allow": ["warn", "error"] }], "no-empty-function": "off", @@ -44,6 +45,8 @@ "no-plusplus": "off", "no-ternary": "off", "no-undefined": "off", + "no-undef-init": "off", + "no-unused-vars": ["error", { "varsIgnorePattern": "h" }], "no-use-before-define": "off", "no-warning-comments": "off", "object-curly-newline": ["error", { "consistent": true }], @@ -65,5 +68,6 @@ "react/jsx-indent": ["error", 2], "react/prop-types": "off", + "react/react-in-jsx-scope": "off" } } diff --git a/karma.conf.js b/karma.conf.js index 859cee0..46a1774 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -7,11 +7,13 @@ module.exports = function (config) { frameworks: ['mocha'], files: [ 'test/**/*.test.js', + 'test/**/*.test.jsx', 'test/**/*.html' ], preprocessors: { 'test/**/*.test.js': [ 'webpack' ], + 'test/**/*.test.jsx': [ 'webpack' ], 'test/**/*.html': ['html2js'] }, diff --git a/package-lock.json b/package-lock.json index ee8a41c..0a50bf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -735,12 +735,6 @@ "babel-helper-is-void-0": "0.2.0" } }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", - "dev": true - }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", @@ -981,16 +975,6 @@ "regexpu-core": "2.0.0" } }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "dev": true, - "requires": { - "babel-plugin-syntax-flow": "6.18.0", - "babel-runtime": "6.25.0" - } - }, "babel-plugin-transform-inline-consecutive-adds": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.2.0.tgz", @@ -1024,15 +1008,6 @@ "esutils": "2.0.2" } }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", - "dev": true, - "requires": { - "babel-runtime": "6.25.0" - } - }, "babel-plugin-transform-react-jsx": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", @@ -1044,26 +1019,6 @@ "babel-runtime": "6.25.0" } }, - "babel-plugin-transform-react-jsx-self": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", - "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", - "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" - } - }, - "babel-plugin-transform-react-jsx-source": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", - "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", - "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" - } - }, "babel-plugin-transform-regenerator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz", @@ -1165,15 +1120,6 @@ "babel-plugin-transform-regenerator": "6.24.1" } }, - "babel-preset-flow": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", - "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", - "dev": true, - "requires": { - "babel-plugin-transform-flow-strip-types": "6.22.0" - } - }, "babel-preset-minify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.2.0.tgz", @@ -1205,18 +1151,14 @@ "lodash.isplainobject": "4.0.6" } }, - "babel-preset-react": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", - "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "babel-preset-preact": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-preact/-/babel-preset-preact-1.1.0.tgz", + "integrity": "sha1-NaxlWnOkm4Q4FjzgU4Fld+GYCGE=", "dev": true, "requires": { "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-transform-react-display-name": "6.25.0", - "babel-plugin-transform-react-jsx": "6.24.1", - "babel-plugin-transform-react-jsx-self": "6.22.0", - "babel-plugin-transform-react-jsx-source": "6.22.0", - "babel-preset-flow": "6.23.0" + "babel-plugin-transform-react-jsx": "6.24.1" } }, "babel-register": { @@ -6273,6 +6215,12 @@ "uniqs": "2.0.0" } }, + "preact": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/preact/-/preact-8.2.6.tgz", + "integrity": "sha1-ACi0Ju+Y/Mp0Gjxhf/W4E7mpR8c=", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -6491,30 +6439,6 @@ } } }, - "react": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz", - "integrity": "sha1-zn348ZQbA28Cssyp29DLHw6FXi0=", - "dev": true, - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, - "react-dom": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0.tgz", - "integrity": "sha1-nMMHnD3NcNTG4BuEqrKn40wwP1g=", - "dev": true, - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", diff --git a/package.json b/package.json index ad25ffa..5606b79 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,9 @@ "description": "Vim vixen", "scripts": { "start": "webpack -w --debug --devtool inline-source-map", - "lint": "eslint --ext .jsx,.js src", "build": "NODE_ENV=production webpack --progress --display-error-details", "package": "npm run build && ./package.sh", - "lint": "eslint src", + "lint": "eslint --ext .jsx,.js src", "test": "karma start" }, "repository": { @@ -24,9 +23,8 @@ "babel-eslint": "^7.2.3", "babel-loader": "^7.1.1", "babel-minify-webpack-plugin": "^0.2.0", - "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-es2015": "^6.24.1", - "babel-preset-react": "^6.24.1", + "babel-preset-preact": "^1.1.0", "chai": "^4.1.1", "css-loader": "^0.28.4", "eslint": "^4.7.0", @@ -41,8 +39,7 @@ "karma-webpack": "^2.0.4", "mocha": "^3.5.0", "node-sass": "^4.5.3", - "react": "^16.0.0", - "react-dom": "^16.0.0", + "preact": "^8.2.6", "sass-loader": "^6.0.6", "style-loader": "^0.18.2", "webpack": "^3.5.3" diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js index c1b27c8..1d01fda 100644 --- a/src/settings/actions/setting.js +++ b/src/settings/actions/setting.js @@ -1,13 +1,14 @@ import actions from 'settings/actions'; import messages from 'shared/messages'; -import DefaultSettings from 'shared/default-settings'; +import DefaultSettings from 'shared/settings/default'; +import * as settingsValues from 'shared/settings/values'; const load = () => { return browser.storage.local.get('settings').then(({ settings }) => { - if (settings) { - return set(settings); + if (!settings) { + return set(DefaultSettings); } - return set(DefaultSettings); + return set(Object.assign({}, DefaultSettings, settings)); }, console.error); }; @@ -24,11 +25,19 @@ const save = (settings) => { }; const set = (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 { type: actions.SETTING_SET_SETTINGS, source: settings.source, json: settings.json, - value: JSON.parse(settings.json), + form: settings.form, + value, }; }; diff --git a/src/settings/components/form/blacklist-form.jsx b/src/settings/components/form/blacklist-form.jsx new file mode 100644 index 0000000..7ae9652 --- /dev/null +++ b/src/settings/components/form/blacklist-form.jsx @@ -0,0 +1,52 @@ +import './blacklist-form.scss'; +import AddButton from '../ui/add-button'; +import DeleteButton from '../ui/delete-button'; +import { h, Component } from 'preact'; + +class BlacklistForm extends Component { + + render() { + let value = this.props.value; + if (!value) { + value = []; + } + + return
+ { + value.map((url, index) => { + return
+ + +
; + }) + } + +
; + } + + bindValue(e) { + if (!this.props.onChange) { + return; + } + + let name = e.target.name; + let index = e.target.getAttribute('data-index'); + let next = this.props.value ? this.props.value.slice() : []; + + if (name === 'url') { + next[index] = e.target.value; + } else if (name === 'add') { + next.push(''); + } else if (name === 'delete') { + next.splice(index, 1); + } + + this.props.onChange(next); + } +} + +export default BlacklistForm; diff --git a/src/settings/components/form/blacklist-form.scss b/src/settings/components/form/blacklist-form.scss new file mode 100644 index 0000000..a230d0d --- /dev/null +++ b/src/settings/components/form/blacklist-form.scss @@ -0,0 +1,9 @@ +.form-blacklist-form { + &-row { + display: flex; + + .column-url { + flex: 1; + } + } +} diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx new file mode 100644 index 0000000..f64320c --- /dev/null +++ b/src/settings/components/form/keymaps-form.jsx @@ -0,0 +1,99 @@ +import './keymaps-form.scss'; +import { h, Component } from 'preact'; +import Input from '../ui/input'; + +const KeyMapFields = [ + [ + ['scroll.vertically?{"count":1}', 'Scroll down'], + ['scroll.vertically?{"count":-1}', 'Scroll up'], + ['scroll.horizonally?{"count":-1}', 'Scroll left'], + ['scroll.horizonally?{"count":1}', 'Scroll right'], + ['scroll.home', 'Scroll leftmost'], + ['scroll.end', 'Scroll last'], + ['scroll.pages?{"count":-0.5}', 'Scroll up by half of screen'], + ['scroll.pages?{"count":0.5}', 'Scroll up by half of screen'], + ['scroll.pages?{"count":-1}', 'Scroll up by a screen'], + ['scroll.pages?{"count":1}', 'Scroll up by a screen'], + ], [ + ['tabs.close', 'Close a tab'], + ['tabs.reopen', 'Reopen closed tab'], + ['tabs.next?{"count":1}', 'Select next Tab'], + ['tabs.prev?{"count":1}', 'Select prev Tab'], + ['tabs.first', 'Select first tab'], + ['tabs.last', 'Select last tab'], + ['tabs.reload?{"cache":true}', 'Reload current tab'], + ['tabs.pin.toggle', 'Toggle pinned state'], + ['tabs.duplicate', 'Dupplicate a tab'], + ], [ + ['follow.start?{"newTab":false}', 'Follow a link'], + ['follow.start?{"newTab":true}', 'Follow a link in new tab'], + ['navigate.history.prev', 'Go back in histories'], + ['navigate.history.next', 'Go forward in histories'], + ['navigate.link.next', 'Open next link'], + ['navigate.link.prev', 'Open previous link'], + ['navigate.parent', 'Go to parent directory'], + ['navigate.root', 'Go to root directory'], + ], [ + ['find.start', 'Start find mode'], + ['find.next', 'Find next word'], + ['find.prev', 'Find previous word'], + ], [ + ['command.show', 'Open console'], + ['command.show.open?{"alter":false}', 'Open URL'], + ['command.show.open?{"alter":true}', 'Alter URL'], + ['command.show.tabopen?{"alter":false}', 'Open URL in new Tab'], + ['command.show.tabopen?{"alter":true}', 'Alter URL in new Tab'], + ['command.show.winopen?{"alter":false}', 'Open URL in new window'], + ['command.show.winopen?{"alter":true}', 'Alter URL in new window'], + ['command.show.buffer', 'Open buffer command'], + ], [ + ['addon.toggle.enabled', 'Enable or disable'], + ['urls.yank', 'Copy current URL'], + ['zoom.in', 'Zoom-in'], + ['zoom.out', 'Zoom-out'], + ['zoom.neutral', 'Reset zoom level'], + ] +]; + +class KeymapsForm extends Component { + + render() { + let values = this.props.value; + if (!values) { + values = {}; + } + return
+ { + KeyMapFields.map((group, index) => { + return
+ { + group.map((field) => { + let name = field[0]; + let label = field[1]; + let value = values[name]; + return ; + }) + } +
; + }) + } +
; + } + + bindValue(e) { + if (!this.props.onChange) { + return; + } + + let next = Object.assign({}, this.props.value); + next[e.target.name] = e.target.value; + + this.props.onChange(next); + } +} + +export default KeymapsForm; diff --git a/src/settings/components/form/keymaps-form.scss b/src/settings/components/form/keymaps-form.scss new file mode 100644 index 0000000..3a83910 --- /dev/null +++ b/src/settings/components/form/keymaps-form.scss @@ -0,0 +1,9 @@ +.form-keymaps-form { + column-count: 3; + .keymap-fields-group { + margin-top: 24px; + } + .keymap-fields-group:first-of-type { + margin-top: 0; + } +} diff --git a/src/settings/components/form/search-form.jsx b/src/settings/components/form/search-form.jsx new file mode 100644 index 0000000..e85761f --- /dev/null +++ b/src/settings/components/form/search-form.jsx @@ -0,0 +1,78 @@ +import './search-form.scss'; +import { h, Component } from 'preact'; +import AddButton from '../ui/add-button'; +import DeleteButton from '../ui/delete-button'; + +class SearchForm extends Component { + + render() { + let value = this.props.value; + if (!value) { + value = { default: '', engines: []}; + } + if (!value.engines) { + value.engines = []; + } + + return
+
+
Name
+
URL
+
Default
+
+ { + value.engines.map((engine, index) => { + return
+ + +
+ + +
+
; + }) + } + +
; + } + + bindValue(e) { + if (!this.props.onChange) { + return; + } + + let value = this.props.value; + let name = e.target.name; + let index = e.target.getAttribute('data-index'); + let next = Object.assign({}, { + default: value.default, + engines: value.engines ? value.engines.slice() : [], + }); + + if (name === 'name') { + next.engines[index][0] = e.target.value; + next.default = this.props.value.engines[index][0]; + } else if (name === 'url') { + next.engines[index][1] = e.target.value; + } else if (name === 'default') { + next.default = this.props.value.engines[index][0]; + } else if (name === 'add') { + next.engines.push(['', '']); + } else if (name === 'delete') { + next.engines.splice(index, 1); + } + + this.props.onChange(next); + } +} + +export default SearchForm; diff --git a/src/settings/components/form/search-form.scss b/src/settings/components/form/search-form.scss new file mode 100644 index 0000000..26b2f44 --- /dev/null +++ b/src/settings/components/form/search-form.scss @@ -0,0 +1,28 @@ +.form-search-form { + @mixin row-base { + display: flex; + + .column-name { + flex: 1; + min-width: 0; + } + .column-url { + flex: 5; + min-width: 0; + } + .column-option { + text-align: right; + flex-basis: 5rem; + } + } + + &-header { + @include row-base; + + font-weight: bold; + } + + &-row { + @include row-base; + } +} diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index 4418942..3961982 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -1,16 +1,27 @@ import './site.scss'; -import React from 'react'; -import PropTypes from 'prop-types'; +import { h, Component } from 'preact'; +import Input from './ui/input'; +import SearchForm from './form/search-form'; +import KeymapsForm from './form/keymaps-form'; +import BlacklistForm from './form/blacklist-form'; import * as settingActions from 'settings/actions/setting'; import * as validator from 'shared/validators/setting'; +import * as settingsValues from 'shared/settings/values'; -class SettingsComponent extends React.Component { +const DO_YOU_WANT_TO_CONTINUE = + 'Some settings in JSON can be lose on migrating. ' + + 'Do you want to continue ?'; + +class SettingsComponent extends Component { constructor(props, context) { super(props, context); this.state = { settings: { json: '', + }, + errors: { + json: '', } }; this.context.store.subscribe(this.stateChanged.bind(this)); @@ -26,66 +37,140 @@ class SettingsComponent extends React.Component { settings: { source: settings.source, json: settings.json, + form: settings.form, } }); } + renderFormFields() { + return
+
+ Keybindings + this.bindForm('keymaps', value)} + /> +
+
+ Search Engines + this.bindForm('search', value)} + /> +
+
+ Blacklist + this.bindForm('blacklist', value)} + /> +
+
; + } + + renderJsonFields() { + return
+ +
; + } + render() { + let fields = null; + if (this.state.settings.source === 'form') { + fields = this.renderFormFields(); + } else if (this.state.settings.source === 'json') { + fields = this.renderJsonFields(); + } return (

Configure Vim-Vixen

+ -

Load settings from:

- - - -