Merge pull request #578 from ueokande/move-to-typescript
Move to TypeScript
This commit is contained in:
commit
05ef6a8ca3
205 changed files with 5118 additions and 2775 deletions
18
.eslintrc
18
.eslintrc
|
@ -5,12 +5,17 @@
|
||||||
"browser" : true,
|
"browser" : true,
|
||||||
"webextensions": true
|
"webextensions": true
|
||||||
},
|
},
|
||||||
"plugins": ["react"],
|
"plugins": [
|
||||||
"parser": "babel-eslint",
|
"react",
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true
|
"jsx": true
|
||||||
}
|
},
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
"extends": [ "eslint:all", "plugin:react/recommended" ],
|
"extends": [ "eslint:all", "plugin:react/recommended" ],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
@ -30,6 +35,7 @@
|
||||||
"indent": ["error", 2],
|
"indent": ["error", 2],
|
||||||
"jsx-quotes": ["error", "prefer-single"],
|
"jsx-quotes": ["error", "prefer-single"],
|
||||||
"max-classes-per-file": "off",
|
"max-classes-per-file": "off",
|
||||||
|
"max-lines": "off",
|
||||||
"max-params": ["error", 5],
|
"max-params": ["error", 5],
|
||||||
"max-statements": ["error", 15],
|
"max-statements": ["error", 15],
|
||||||
"multiline-comment-style": "off",
|
"multiline-comment-style": "off",
|
||||||
|
@ -42,13 +48,14 @@
|
||||||
"no-console": ["error", { "allow": ["warn", "error"] }],
|
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
"no-empty-function": "off",
|
"no-empty-function": "off",
|
||||||
|
"no-extra-parens": "off",
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
"no-mixed-operators": "off",
|
"no-mixed-operators": "off",
|
||||||
"no-plusplus": "off",
|
"no-plusplus": "off",
|
||||||
"no-ternary": "off",
|
"no-ternary": "off",
|
||||||
"no-undefined": "off",
|
"no-undefined": "off",
|
||||||
"no-undef-init": "off",
|
"no-undef-init": "off",
|
||||||
"no-unused-vars": ["error", { "varsIgnorePattern": "h" }],
|
"no-unused-vars": "off",
|
||||||
"no-use-before-define": "off",
|
"no-use-before-define": "off",
|
||||||
"no-warning-comments": "off",
|
"no-warning-comments": "off",
|
||||||
"object-curly-newline": ["error", { "consistent": true }],
|
"object-curly-newline": ["error", { "consistent": true }],
|
||||||
|
@ -71,6 +78,7 @@
|
||||||
|
|
||||||
"react/jsx-indent": ["error", 2],
|
"react/jsx-indent": ["error", 2],
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react/react-in-jsx-scope": "off"
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,16 @@ module.exports = function (config) {
|
||||||
basePath: '',
|
basePath: '',
|
||||||
frameworks: ['mocha', 'sinon'],
|
frameworks: ['mocha', 'sinon'],
|
||||||
files: [
|
files: [
|
||||||
'test/main.js',
|
'test/main.ts',
|
||||||
'test/**/*.test.js',
|
'test/**/*.test.ts',
|
||||||
'test/**/*.test.jsx',
|
'test/**/*.test.tsx',
|
||||||
'test/**/*.html'
|
'test/**/*.html'
|
||||||
],
|
],
|
||||||
|
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'test/main.js': [ 'webpack', 'sourcemap' ],
|
'test/main.ts': [ 'webpack', 'sourcemap' ],
|
||||||
'test/**/*.test.js': [ 'webpack', 'sourcemap' ],
|
'test/**/*.test.ts': [ 'webpack', 'sourcemap' ],
|
||||||
'test/**/*.test.jsx': [ 'webpack', 'sourcemap' ],
|
'test/**/*.test.tsx': [ 'webpack', 'sourcemap' ],
|
||||||
'test/**/*.html': ['html2js']
|
'test/**/*.html': ['html2js']
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
373
package-lock.json
generated
373
package-lock.json
generated
|
@ -196,6 +196,42 @@
|
||||||
"esutils": "^2.0.0"
|
"esutils": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-create-class-features-plugin": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-function-name": "^7.1.0",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.0.0",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.0.0",
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0",
|
||||||
|
"@babel/helper-replace-supers": "^7.4.4",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.4.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-split-export-declaration": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/types": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-function-name": {
|
"@babel/helper-function-name": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
|
||||||
|
@ -216,12 +252,115 @@
|
||||||
"@babel/types": "^7.0.0"
|
"@babel/types": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-member-expression-to-functions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-optimise-call-expression": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-plugin-utils": {
|
"@babel/helper-plugin-utils": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
|
||||||
"integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
|
"integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@babel/helper-replace-supers": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.0.0",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.0.0",
|
||||||
|
"@babel/traverse": "^7.4.4",
|
||||||
|
"@babel/types": "^7.4.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/generator": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.4.4",
|
||||||
|
"jsesc": "^2.5.1",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"source-map": "^0.5.0",
|
||||||
|
"trim-right": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-split-export-declaration": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/parser": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/traverse": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"@babel/generator": "^7.4.4",
|
||||||
|
"@babel/helper-function-name": "^7.1.0",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.4.4",
|
||||||
|
"@babel/parser": "^7.4.4",
|
||||||
|
"@babel/types": "^7.4.4",
|
||||||
|
"debug": "^4.1.0",
|
||||||
|
"globals": "^11.1.0",
|
||||||
|
"lodash": "^4.17.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/types": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-split-export-declaration": {
|
"@babel/helper-split-export-declaration": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz",
|
||||||
|
@ -386,6 +525,16 @@
|
||||||
"integrity": "sha512-ATz6yX/L8LEnC3dtLQnIx4ydcPxhLcoy9Vl6re00zb2w5lG6itY6Vhnr1KFRPq/FHNsgl/gh2mjNN20f9iJTTA==",
|
"integrity": "sha512-ATz6yX/L8LEnC3dtLQnIx4ydcPxhLcoy9Vl6re00zb2w5lG6itY6Vhnr1KFRPq/FHNsgl/gh2mjNN20f9iJTTA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-proposal-class-properties": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-create-class-features-plugin": "^7.4.4",
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/plugin-syntax-jsx": {
|
"@babel/plugin-syntax-jsx": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz",
|
||||||
|
@ -395,6 +544,15 @@
|
||||||
"@babel/helper-plugin-utils": "^7.0.0"
|
"@babel/helper-plugin-utils": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-syntax-typescript": {
|
||||||
|
"version": "7.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz",
|
||||||
|
"integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/plugin-transform-react-display-name": {
|
"@babel/plugin-transform-react-display-name": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz",
|
||||||
|
@ -435,6 +593,16 @@
|
||||||
"@babel/plugin-syntax-jsx": "^7.2.0"
|
"@babel/plugin-syntax-jsx": "^7.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-transform-typescript": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-rwDvjaMTx09WC0rXGBRlYSSkEHOKRrecY6hEr3SVIPKII8DVWXtapNAfAyMC0dovuO+zYArcAuKeu3q9DNRfzA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0",
|
||||||
|
"@babel/plugin-syntax-typescript": "^7.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/preset-react": {
|
"@babel/preset-react": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz",
|
||||||
|
@ -448,6 +616,16 @@
|
||||||
"@babel/plugin-transform-react-jsx-source": "^7.0.0"
|
"@babel/plugin-transform-react-jsx-source": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/preset-typescript": {
|
||||||
|
"version": "7.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz",
|
||||||
|
"integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0",
|
||||||
|
"@babel/plugin-transform-typescript": "^7.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.4.4",
|
"version": "7.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
|
||||||
|
@ -579,6 +757,142 @@
|
||||||
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/chai": {
|
||||||
|
"version": "4.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
|
||||||
|
"integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/mocha": {
|
||||||
|
"version": "5.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz",
|
||||||
|
"integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/prop-types": {
|
||||||
|
"version": "15.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz",
|
||||||
|
"integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"version": "16.8.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.15.tgz",
|
||||||
|
"integrity": "sha512-dMhzw1rWK+wwJWvPp5Pk12ksSrm/z/C/+lOQbMZ7YfDQYnJ02bc0wtg4EJD9qrFhuxFrf/ywNgwTboucobJqQg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"csstype": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"version": "16.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz",
|
||||||
|
"integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-redux": {
|
||||||
|
"version": "7.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.0.8.tgz",
|
||||||
|
"integrity": "sha512-vIBC15E84ehN6RzdGwRVa41whp9e4CkfPm+WfD0r6y6vqBf4tQPKZeKEBfLLM8k79uSwQC7rh3rH/MFaN1IESQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.0",
|
||||||
|
"@types/react": "*",
|
||||||
|
"redux": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/redux-promise": {
|
||||||
|
"version": "0.5.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/redux-promise/-/redux-promise-0.5.28.tgz",
|
||||||
|
"integrity": "sha512-HNWAIjTeMcdAgl4wI2XQdlrJeJMS/TyohD8Yzf3Ebp0fPR4M9wg4/+EBrbbLAsBrGxmSkXdvy1H2ty21dhlS7A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"redux": "^3.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"redux": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.2.1",
|
||||||
|
"lodash-es": "^4.2.1",
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"symbol-observable": "^1.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@typescript-eslint/eslint-plugin": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-NUSz1aTlIzzTjFFVFyzrbo8oFjHg3K/M9MzYByqbMCxeFdErhLAcGITVfXzSz+Yvp5OOpMu3HkIttB0NyKl54Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/parser": "1.7.0",
|
||||||
|
"@typescript-eslint/typescript-estree": "1.7.0",
|
||||||
|
"eslint-utils": "^1.3.1",
|
||||||
|
"regexpp": "^2.0.1",
|
||||||
|
"requireindex": "^1.2.0",
|
||||||
|
"tsutils": "^3.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@typescript-eslint/parser": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-1QFKxs2V940372srm12ovSE683afqc1jB6zF/f8iKhgLz1yoSjYeGHipasao33VXKI+0a/ob9okeogGdKGvvlg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/typescript-estree": "1.7.0",
|
||||||
|
"eslint-scope": "^4.0.0",
|
||||||
|
"eslint-visitor-keys": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-scope": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esrecurse": "^4.1.0",
|
||||||
|
"estraverse": "^4.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@typescript-eslint/typescript-estree": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-K5uedUxVmlYrVkFbyV3htDipvLqTE3QMOUQEHYJaKtgzxj6r7c5Ca/DG1tGgFxX+fsbi9nDIrf4arq7Ib7H/Yw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash.unescape": "4.0.1",
|
||||||
|
"semver": "5.5.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.8.5",
|
"version": "1.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
|
||||||
|
@ -2255,6 +2569,12 @@
|
||||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"csstype": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"currently-unhandled": {
|
"currently-unhandled": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||||
|
@ -3948,14 +4268,12 @@
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -3975,8 +4293,7 @@
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
@ -4124,7 +4441,6 @@
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
@ -6077,6 +6393,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"karma-babel-preprocessor": {
|
||||||
|
"version": "8.0.0-beta.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/karma-babel-preprocessor/-/karma-babel-preprocessor-8.0.0-beta.0.tgz",
|
||||||
|
"integrity": "sha512-nv3GbDAKdonWuTJc+Kg4jEdRXzoP7uKKQ6HfTJb5PNTY+OJYKzrtUBUSez/wrutUFtztVT+MQxJHamd7MNCmBQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"karma-firefox-launcher": {
|
"karma-firefox-launcher": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz",
|
||||||
|
@ -6408,12 +6730,24 @@
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash-es": {
|
||||||
|
"version": "4.17.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
|
||||||
|
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.tail": {
|
"lodash.tail": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
|
||||||
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
|
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.unescape": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
|
||||||
|
"integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"log-symbols": {
|
"log-symbols": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||||
|
@ -8901,6 +9235,12 @@
|
||||||
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
|
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"requireindex": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"requires-port": {
|
"requires-port": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
@ -10326,6 +10666,15 @@
|
||||||
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
|
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tsutils": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tty-browserify": {
|
"tty-browserify": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||||
|
@ -10378,6 +10727,12 @@
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "3.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
|
||||||
|
"integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.3.25",
|
"version": "3.3.25",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.25.tgz",
|
||||||
|
@ -11100,6 +11455,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"web-ext-types": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-ext-types/-/web-ext-types-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-HKVibk040vuhpbOljcIYYYC8GP9w9REbHpquI3im/aoZqoDIRq9DnsHl4Zsg+4Fg3SBnWsnvlIr1rnspV4TdXQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"webextensions-api-fake": {
|
"webextensions-api-fake": {
|
||||||
"version": "0.7.4",
|
"version": "0.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/webextensions-api-fake/-/webextensions-api-fake-0.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/webextensions-api-fake/-/webextensions-api-fake-0.7.4.tgz",
|
||||||
|
|
16
package.json
16
package.json
|
@ -5,7 +5,8 @@
|
||||||
"start": "webpack --mode development -w --debug --devtool inline-source-map",
|
"start": "webpack --mode development -w --debug --devtool inline-source-map",
|
||||||
"build": "NODE_ENV=production webpack --mode production --progress --display-error-details",
|
"build": "NODE_ENV=production webpack --mode production --progress --display-error-details",
|
||||||
"package": "npm run build && script/package",
|
"package": "npm run build && script/package",
|
||||||
"lint": "eslint --ext .jsx,.js src",
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx src",
|
||||||
|
"type-checks": "tsc",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
"test:e2e": "mocha --timeout 8000 e2e"
|
"test:e2e": "mocha --timeout 8000 e2e"
|
||||||
},
|
},
|
||||||
|
@ -22,7 +23,17 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.4.4",
|
"@babel/cli": "^7.4.4",
|
||||||
"@babel/core": "^7.4.4",
|
"@babel/core": "^7.4.4",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"@babel/preset-typescript": "^7.3.3",
|
||||||
|
"@types/chai": "^4.1.7",
|
||||||
|
"@types/mocha": "^5.2.6",
|
||||||
|
"@types/prop-types": "^15.7.1",
|
||||||
|
"@types/react": "^16.8.15",
|
||||||
|
"@types/react-dom": "^16.8.4",
|
||||||
|
"@types/react-redux": "^7.0.8",
|
||||||
|
"@types/redux-promise": "^0.5.28",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^1.7.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.5",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
|
@ -32,6 +43,7 @@
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"jszip": "^3.2.1",
|
"jszip": "^3.2.1",
|
||||||
"karma": "^4.1.0",
|
"karma": "^4.1.0",
|
||||||
|
"karma-babel-preprocessor": "^8.0.0-beta.0",
|
||||||
"karma-firefox-launcher": "^1.1.0",
|
"karma-firefox-launcher": "^1.1.0",
|
||||||
"karma-html2js-preprocessor": "^1.1.0",
|
"karma-html2js-preprocessor": "^1.1.0",
|
||||||
"karma-mocha": "^1.3.0",
|
"karma-mocha": "^1.3.0",
|
||||||
|
@ -51,6 +63,8 @@
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"sinon-chrome": "^3.0.1",
|
"sinon-chrome": "^3.0.1",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
|
"typescript": "^3.4.5",
|
||||||
|
"web-ext-types": "^3.1.0",
|
||||||
"webextensions-api-fake": "^0.7.4",
|
"webextensions-api-fake": "^0.7.4",
|
||||||
"webpack": "^4.30.0",
|
"webpack": "^4.30.0",
|
||||||
"webpack-cli": "^3.3.1"
|
"webpack-cli": "^3.3.1"
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
||||||
|
|
||||||
export default class AddonEnabledController {
|
export default class AddonEnabledController {
|
||||||
|
private addonEnabledUseCase: AddonEnabledUseCase;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.addonEnabledUseCase = new AddonEnabledUseCase();
|
this.addonEnabledUseCase = new AddonEnabledUseCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
indicate(enabled) {
|
indicate(enabled: boolean): Promise<any> {
|
||||||
return this.addonEnabledUseCase.indicate(enabled);
|
return this.addonEnabledUseCase.indicate(enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,23 @@
|
||||||
import CompletionsUseCase from '../usecases/CompletionsUseCase';
|
import CompletionsUseCase from '../usecases/CompletionsUseCase';
|
||||||
import CommandUseCase from '../usecases/CommandUseCase';
|
import CommandUseCase from '../usecases/CommandUseCase';
|
||||||
import Completions from '../domains/Completions';
|
import CompletionGroup from '../domains/CompletionGroup';
|
||||||
|
|
||||||
const trimStart = (str) => {
|
const trimStart = (str: string): string => {
|
||||||
// NOTE String.trimStart is available on Firefox 61
|
// NOTE String.trimStart is available on Firefox 61
|
||||||
return str.replace(/^\s+/, '');
|
return str.replace(/^\s+/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class CommandController {
|
export default class CommandController {
|
||||||
|
private completionsUseCase: CompletionsUseCase;
|
||||||
|
|
||||||
|
private commandIndicator: CommandUseCase;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.completionsUseCase = new CompletionsUseCase();
|
this.completionsUseCase = new CompletionsUseCase();
|
||||||
this.commandIndicator = new CommandUseCase();
|
this.commandIndicator = new CommandUseCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCompletions(line) {
|
getCompletions(line: string): Promise<CompletionGroup[]> {
|
||||||
let trimmed = trimStart(line);
|
let trimmed = trimStart(line);
|
||||||
let words = trimmed.split(/ +/);
|
let words = trimmed.split(/ +/);
|
||||||
let name = words[0];
|
let name = words[0];
|
||||||
|
@ -45,11 +49,11 @@ export default class CommandController {
|
||||||
case 'set':
|
case 'set':
|
||||||
return this.completionsUseCase.querySet(name, keywords);
|
return this.completionsUseCase.querySet(name, keywords);
|
||||||
}
|
}
|
||||||
return Promise.resolve(Completions.empty());
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line complexity
|
// eslint-disable-next-line complexity
|
||||||
exec(line) {
|
exec(line: string): Promise<any> {
|
||||||
let trimmed = trimStart(line);
|
let trimmed = trimStart(line);
|
||||||
let words = trimmed.split(/ +/);
|
let words = trimmed.split(/ +/);
|
||||||
let name = words[0];
|
let name = words[0];
|
|
@ -1,15 +1,17 @@
|
||||||
import FindUseCase from '../usecases/FindUseCase';
|
import FindUseCase from '../usecases/FindUseCase';
|
||||||
|
|
||||||
export default class FindController {
|
export default class FindController {
|
||||||
|
private findUseCase: FindUseCase;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.findUseCase = new FindUseCase();
|
this.findUseCase = new FindUseCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyword() {
|
getKeyword(): Promise<string> {
|
||||||
return this.findUseCase.getKeyword();
|
return this.findUseCase.getKeyword();
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeyword(keyword) {
|
setKeyword(keyword: string): Promise<any> {
|
||||||
return this.findUseCase.setKeyword(keyword);
|
return this.findUseCase.setKeyword(keyword);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
import LinkUseCase from '../usecases/LinkUseCase';
|
|
||||||
|
|
||||||
export default class LinkController {
|
|
||||||
constructor() {
|
|
||||||
this.linkUseCase = new LinkUseCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
openToTab(url, tabId) {
|
|
||||||
this.linkUseCase.openToTab(url, tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
openNewTab(url, openerId, background) {
|
|
||||||
this.linkUseCase.openNewTab(url, openerId, background);
|
|
||||||
}
|
|
||||||
}
|
|
19
src/background/controllers/LinkController.ts
Normal file
19
src/background/controllers/LinkController.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import LinkUseCase from '../usecases/LinkUseCase';
|
||||||
|
|
||||||
|
export default class LinkController {
|
||||||
|
private linkUseCase: LinkUseCase;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.linkUseCase = new LinkUseCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
openToTab(url: string, tabId: number): Promise<void> {
|
||||||
|
return this.linkUseCase.openToTab(url, tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
openNewTab(
|
||||||
|
url: string, openerId: number, background: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
return this.linkUseCase.openNewTab(url, openerId, background);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
import MarkUseCase from '../usecases/MarkUseCase';
|
|
||||||
|
|
||||||
export default class MarkController {
|
|
||||||
constructor() {
|
|
||||||
this.markUseCase = new MarkUseCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
setGlobal(key, x, y) {
|
|
||||||
this.markUseCase.setGlobal(key, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
jumpGlobal(key) {
|
|
||||||
this.markUseCase.jumpGlobal(key);
|
|
||||||
}
|
|
||||||
}
|
|
17
src/background/controllers/MarkController.ts
Normal file
17
src/background/controllers/MarkController.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import MarkUseCase from '../usecases/MarkUseCase';
|
||||||
|
|
||||||
|
export default class MarkController {
|
||||||
|
private markUseCase: MarkUseCase;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.markUseCase = new MarkUseCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobal(key: string, x: number, y: number): Promise<any> {
|
||||||
|
return this.markUseCase.setGlobal(key, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
jumpGlobal(key: string): Promise<any> {
|
||||||
|
return this.markUseCase.jumpGlobal(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import operations from '../../shared/operations';
|
import * as operations from '../../shared/operations';
|
||||||
import FindUseCase from '../usecases/FindUseCase';
|
import FindUseCase from '../usecases/FindUseCase';
|
||||||
import ConsoleUseCase from '../usecases/ConsoleUseCase';
|
import ConsoleUseCase from '../usecases/ConsoleUseCase';
|
||||||
import TabUseCase from '../usecases/TabUseCase';
|
import TabUseCase from '../usecases/TabUseCase';
|
||||||
|
@ -6,6 +6,16 @@ import TabSelectUseCase from '../usecases/TabSelectUseCase';
|
||||||
import ZoomUseCase from '../usecases/ZoomUseCase';
|
import ZoomUseCase from '../usecases/ZoomUseCase';
|
||||||
|
|
||||||
export default class OperationController {
|
export default class OperationController {
|
||||||
|
private findUseCase: FindUseCase;
|
||||||
|
|
||||||
|
private consoleUseCase: ConsoleUseCase;
|
||||||
|
|
||||||
|
private tabUseCase: TabUseCase;
|
||||||
|
|
||||||
|
private tabSelectUseCase: TabSelectUseCase;
|
||||||
|
|
||||||
|
private zoomUseCase: ZoomUseCase;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.findUseCase = new FindUseCase();
|
this.findUseCase = new FindUseCase();
|
||||||
this.consoleUseCase = new ConsoleUseCase();
|
this.consoleUseCase = new ConsoleUseCase();
|
||||||
|
@ -15,7 +25,7 @@ export default class OperationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
exec(operation) {
|
exec(operation: operations.Operation): Promise<any> {
|
||||||
switch (operation.type) {
|
switch (operation.type) {
|
||||||
case operations.TAB_CLOSE:
|
case operations.TAB_CLOSE:
|
||||||
return this.tabUseCase.close(false);
|
return this.tabUseCase.close(false);
|
||||||
|
@ -72,6 +82,7 @@ export default class OperationController {
|
||||||
case operations.CANCEL:
|
case operations.CANCEL:
|
||||||
return this.consoleUseCase.hideConsole();
|
return this.consoleUseCase.hideConsole();
|
||||||
}
|
}
|
||||||
|
throw new Error('unknown operation: ' + operation.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
import SettingUseCase from '../usecases/SettingUseCase';
|
import SettingUseCase from '../usecases/SettingUseCase';
|
||||||
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
||||||
|
import Settings from '../../shared/Settings';
|
||||||
|
|
||||||
export default class SettingController {
|
export default class SettingController {
|
||||||
|
private settingUseCase: SettingUseCase;
|
||||||
|
|
||||||
|
private contentMessageClient: ContentMessageClient;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.settingUseCase = new SettingUseCase();
|
this.settingUseCase = new SettingUseCase();
|
||||||
this.contentMessageClient = new ContentMessageClient();
|
this.contentMessageClient = new ContentMessageClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSetting() {
|
getSetting(): Promise<Settings> {
|
||||||
return this.settingUseCase.get();
|
return this.settingUseCase.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload() {
|
async reload(): Promise<any> {
|
||||||
await this.settingUseCase.reload();
|
await this.settingUseCase.reload();
|
||||||
this.contentMessageClient.broadcastSettingsChanged();
|
this.contentMessageClient.broadcastSettingsChanged();
|
||||||
}
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import VersionUseCase from '../usecases/VersionUseCase';
|
import VersionUseCase from '../usecases/VersionUseCase';
|
||||||
|
|
||||||
export default class VersionController {
|
export default class VersionController {
|
||||||
|
private versionUseCase: VersionUseCase;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.versionUseCase = new VersionUseCase();
|
this.versionUseCase = new VersionUseCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
notify() {
|
notify(): Promise<void> {
|
||||||
this.versionUseCase.notify();
|
return this.versionUseCase.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
import VersionInteractor from '../usecases/version';
|
|
||||||
|
|
||||||
export default class VersionController {
|
|
||||||
constructor() {
|
|
||||||
this.versionInteractor = new VersionInteractor();
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyIfUpdated() {
|
|
||||||
browser.runtime.onInstalled.addListener(() => {
|
|
||||||
return this.versionInteractor.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,5 +8,4 @@ export default {
|
||||||
bdeletes: 'Close all tabs matched by keywords',
|
bdeletes: 'Close all tabs matched by keywords',
|
||||||
quit: 'Close the current tab',
|
quit: 'Close the current tab',
|
||||||
quitall: 'Close all tabs',
|
quitall: 'Close all tabs',
|
||||||
};
|
} as {[key: string]: string};
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
export default class CompletionGroup {
|
|
||||||
constructor(name, items) {
|
|
||||||
this.name0 = name;
|
|
||||||
this.items0 = items;
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return this.name0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get items() {
|
|
||||||
return this.items0;
|
|
||||||
}
|
|
||||||
}
|
|
7
src/background/domains/CompletionGroup.ts
Normal file
7
src/background/domains/CompletionGroup.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import CompletionItem from './CompletionItem';
|
||||||
|
|
||||||
|
export default interface CompletionGroup {
|
||||||
|
name: string;
|
||||||
|
items: CompletionItem[];
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
export default class CompletionItem {
|
|
||||||
constructor({ caption, content, url, icon }) {
|
|
||||||
this.caption0 = caption;
|
|
||||||
this.content0 = content;
|
|
||||||
this.url0 = url;
|
|
||||||
this.icon0 = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
get caption() {
|
|
||||||
return this.caption0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get content() {
|
|
||||||
return this.content0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get url() {
|
|
||||||
return this.url0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return this.icon0;
|
|
||||||
}
|
|
||||||
}
|
|
7
src/background/domains/CompletionItem.ts
Normal file
7
src/background/domains/CompletionItem.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default interface CompletionItem {
|
||||||
|
readonly caption?: string;
|
||||||
|
readonly content?: string;
|
||||||
|
readonly url?: string;
|
||||||
|
readonly icon?: string;
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
export default class Completions {
|
|
||||||
constructor(groups) {
|
|
||||||
this.g = groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
get groups() {
|
|
||||||
return this.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize() {
|
|
||||||
return this.groups.map(group => ({
|
|
||||||
name: group.name,
|
|
||||||
items: group.items.map(item => ({
|
|
||||||
caption: item.caption,
|
|
||||||
content: item.content,
|
|
||||||
url: item.url,
|
|
||||||
icon: item.icon,
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
static empty() {
|
|
||||||
return EMPTY_COMPLETIONS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let EMPTY_COMPLETIONS = new Completions([]);
|
|
|
@ -1,24 +0,0 @@
|
||||||
export default class GlobalMark {
|
|
||||||
constructor(tabId, url, x, y) {
|
|
||||||
this.tabId0 = tabId;
|
|
||||||
this.url0 = url;
|
|
||||||
this.x0 = x;
|
|
||||||
this.y0 = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tabId() {
|
|
||||||
return this.tabId0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get url() {
|
|
||||||
return this.url0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get x() {
|
|
||||||
return this.x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get y() {
|
|
||||||
return this.y0;
|
|
||||||
}
|
|
||||||
}
|
|
7
src/background/domains/GlobalMark.ts
Normal file
7
src/background/domains/GlobalMark.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default interface GlobalMark {
|
||||||
|
readonly tabId: number;
|
||||||
|
readonly url: string;
|
||||||
|
readonly x: number;
|
||||||
|
readonly y: number;
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
|
@ -1,51 +0,0 @@
|
||||||
import DefaultSettings from '../../shared/settings/default';
|
|
||||||
import * as settingsValues from '../../shared/settings/values';
|
|
||||||
|
|
||||||
export default class Setting {
|
|
||||||
constructor({ source, json, form }) {
|
|
||||||
this.obj = {
|
|
||||||
source, json, form
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get source() {
|
|
||||||
return this.obj.source;
|
|
||||||
}
|
|
||||||
|
|
||||||
get json() {
|
|
||||||
return this.obj.json;
|
|
||||||
}
|
|
||||||
|
|
||||||
get form() {
|
|
||||||
return this.obj.form;
|
|
||||||
}
|
|
||||||
|
|
||||||
value() {
|
|
||||||
let value = JSON.parse(DefaultSettings.json);
|
|
||||||
if (this.obj.source === 'json') {
|
|
||||||
value = settingsValues.valueFromJson(this.obj.json);
|
|
||||||
} else if (this.obj.source === 'form') {
|
|
||||||
value = settingsValues.valueFromForm(this.obj.form);
|
|
||||||
}
|
|
||||||
if (!value.properties) {
|
|
||||||
value.properties = {};
|
|
||||||
}
|
|
||||||
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize() {
|
|
||||||
return this.obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static deserialize(obj) {
|
|
||||||
return new Setting({ source: obj.source, json: obj.json, form: obj.form });
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultSettings() {
|
|
||||||
return new Setting({
|
|
||||||
source: DefaultSettings.source,
|
|
||||||
json: DefaultSettings.json,
|
|
||||||
form: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +1,34 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
export default class ConsoleClient {
|
export default class ConsoleClient {
|
||||||
showCommand(tabId, command) {
|
showCommand(tabId: number, command: string): Promise<any> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.CONSOLE_SHOW_COMMAND,
|
type: messages.CONSOLE_SHOW_COMMAND,
|
||||||
command,
|
command,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showFind(tabId) {
|
showFind(tabId: number): Promise<any> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.CONSOLE_SHOW_FIND
|
type: messages.CONSOLE_SHOW_FIND
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showInfo(tabId, message) {
|
showInfo(tabId: number, message: string): Promise<any> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.CONSOLE_SHOW_INFO,
|
type: messages.CONSOLE_SHOW_INFO,
|
||||||
text: message,
|
text: message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(tabId, message) {
|
showError(tabId: number, message: string): Promise<any> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.CONSOLE_SHOW_ERROR,
|
type: messages.CONSOLE_SHOW_ERROR,
|
||||||
text: message,
|
text: message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hide(tabId) {
|
hide(tabId: number): Promise<any> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.CONSOLE_HIDE,
|
type: messages.CONSOLE_HIDE,
|
||||||
});
|
});
|
|
@ -1,10 +1,10 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
export default class ContentMessageClient {
|
export default class ContentMessageClient {
|
||||||
async broadcastSettingsChanged() {
|
async broadcastSettingsChanged(): Promise<void> {
|
||||||
let tabs = await browser.tabs.query({});
|
let tabs = await browser.tabs.query({});
|
||||||
for (let tab of tabs) {
|
for (let tab of tabs) {
|
||||||
if (tab.url.startsWith('about:')) {
|
if (!tab.id || tab.url && tab.url.startsWith('about:')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
browser.tabs.sendMessage(tab.id, {
|
browser.tabs.sendMessage(tab.id, {
|
||||||
|
@ -13,20 +13,20 @@ export default class ContentMessageClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAddonEnabled(tabId) {
|
async getAddonEnabled(tabId: number): Promise<boolean> {
|
||||||
let { enabled } = await browser.tabs.sendMessage(tabId, {
|
let { enabled } = await browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.ADDON_ENABLED_QUERY,
|
type: messages.ADDON_ENABLED_QUERY,
|
||||||
});
|
}) as { enabled: boolean };
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAddonEnabled(tabId) {
|
toggleAddonEnabled(tabId: number): Promise<void> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.ADDON_TOGGLE_ENABLED,
|
type: messages.ADDON_TOGGLE_ENABLED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo(tabId, x, y) {
|
scrollTo(tabId: number, x: number, y: number): Promise<void> {
|
||||||
return browser.tabs.sendMessage(tabId, {
|
return browser.tabs.sendMessage(tabId, {
|
||||||
type: messages.TAB_SCROLL_TO,
|
type: messages.TAB_SCROLL_TO,
|
||||||
x,
|
x,
|
|
@ -1,4 +1,5 @@
|
||||||
import messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
|
import CompletionGroup from '../domains/CompletionGroup';
|
||||||
import CommandController from '../controllers/CommandController';
|
import CommandController from '../controllers/CommandController';
|
||||||
import SettingController from '../controllers/SettingController';
|
import SettingController from '../controllers/SettingController';
|
||||||
import FindController from '../controllers/FindController';
|
import FindController from '../controllers/FindController';
|
||||||
|
@ -8,6 +9,22 @@ import OperationController from '../controllers/OperationController';
|
||||||
import MarkController from '../controllers/MarkController';
|
import MarkController from '../controllers/MarkController';
|
||||||
|
|
||||||
export default class ContentMessageListener {
|
export default class ContentMessageListener {
|
||||||
|
private settingController: SettingController;
|
||||||
|
|
||||||
|
private commandController: CommandController;
|
||||||
|
|
||||||
|
private findController: FindController;
|
||||||
|
|
||||||
|
private addonEnabledController: AddonEnabledController;
|
||||||
|
|
||||||
|
private linkController: LinkController;
|
||||||
|
|
||||||
|
private backgroundOperationController: OperationController;
|
||||||
|
|
||||||
|
private markController: MarkController;
|
||||||
|
|
||||||
|
private consolePorts: {[tabId: number]: browser.runtime.Port};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.settingController = new SettingController();
|
this.settingController = new SettingController();
|
||||||
this.commandController = new CommandController();
|
this.commandController = new CommandController();
|
||||||
|
@ -20,20 +37,28 @@ export default class ContentMessageListener {
|
||||||
this.consolePorts = {};
|
this.consolePorts = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
run(): void {
|
||||||
browser.runtime.onMessage.addListener((message, sender) => {
|
browser.runtime.onMessage.addListener((
|
||||||
|
message: any, sender: browser.runtime.MessageSender,
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
let ret = this.onMessage(message, sender);
|
let ret = this.onMessage(message, sender.tab as browser.tabs.Tab);
|
||||||
if (!(ret instanceof Promise)) {
|
if (!(ret instanceof Promise)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return ret.catch((e) => {
|
return ret.catch((e) => {
|
||||||
|
if (!sender.tab || !sender.tab.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return browser.tabs.sendMessage(sender.tab.id, {
|
return browser.tabs.sendMessage(sender.tab.id, {
|
||||||
type: messages.CONSOLE_SHOW_ERROR,
|
type: messages.CONSOLE_SHOW_ERROR,
|
||||||
text: e.message,
|
text: e.message,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!sender.tab || !sender.tab.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return browser.tabs.sendMessage(sender.tab.id, {
|
return browser.tabs.sendMessage(sender.tab.id, {
|
||||||
type: messages.CONSOLE_SHOW_ERROR,
|
type: messages.CONSOLE_SHOW_ERROR,
|
||||||
text: e.message,
|
text: e.message,
|
||||||
|
@ -43,7 +68,9 @@ export default class ContentMessageListener {
|
||||||
browser.runtime.onConnect.addListener(this.onConnected.bind(this));
|
browser.runtime.onConnect.addListener(this.onConnected.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message, sender) {
|
onMessage(
|
||||||
|
message: messages.Message, senderTab: browser.tabs.Tab,
|
||||||
|
): Promise<any> | any {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.CONSOLE_QUERY_COMPLETIONS:
|
case messages.CONSOLE_QUERY_COMPLETIONS:
|
||||||
return this.onConsoleQueryCompletions(message.text);
|
return this.onConsoleQueryCompletions(message.text);
|
||||||
|
@ -59,7 +86,10 @@ export default class ContentMessageListener {
|
||||||
return this.onAddonEnabledResponse(message.enabled);
|
return this.onAddonEnabledResponse(message.enabled);
|
||||||
case messages.OPEN_URL:
|
case messages.OPEN_URL:
|
||||||
return this.onOpenUrl(
|
return this.onOpenUrl(
|
||||||
message.newTab, message.url, sender.tab.id, message.background);
|
message.newTab,
|
||||||
|
message.url,
|
||||||
|
senderTab.id as number,
|
||||||
|
message.background);
|
||||||
case messages.BACKGROUND_OPERATION:
|
case messages.BACKGROUND_OPERATION:
|
||||||
return this.onBackgroundOperation(message.operation);
|
return this.onBackgroundOperation(message.operation);
|
||||||
case messages.MARK_SET_GLOBAL:
|
case messages.MARK_SET_GLOBAL:
|
||||||
|
@ -67,56 +97,60 @@ export default class ContentMessageListener {
|
||||||
case messages.MARK_JUMP_GLOBAL:
|
case messages.MARK_JUMP_GLOBAL:
|
||||||
return this.onMarkJumpGlobal(message.key);
|
return this.onMarkJumpGlobal(message.key);
|
||||||
case messages.CONSOLE_FRAME_MESSAGE:
|
case messages.CONSOLE_FRAME_MESSAGE:
|
||||||
return this.onConsoleFrameMessage(sender.tab.id, message.message);
|
return this.onConsoleFrameMessage(
|
||||||
|
senderTab.id as number, message.message,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
throw new Error('unsupported message: ' + message.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onConsoleQueryCompletions(line) {
|
async onConsoleQueryCompletions(line: string): Promise<CompletionGroup[]> {
|
||||||
let completions = await this.commandController.getCompletions(line);
|
let completions = await this.commandController.getCompletions(line);
|
||||||
return Promise.resolve(completions.serialize());
|
return Promise.resolve(completions);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConsoleEnterCommand(text) {
|
onConsoleEnterCommand(text: string): Promise<any> {
|
||||||
return this.commandController.exec(text);
|
return this.commandController.exec(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSettingsQuery(): Promise<any> {
|
||||||
onSettingsQuery() {
|
|
||||||
return this.settingController.getSetting();
|
return this.settingController.getSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFindGetKeyword() {
|
onFindGetKeyword(): Promise<string> {
|
||||||
return this.findController.getKeyword();
|
return this.findController.getKeyword();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFindSetKeyword(keyword) {
|
onFindSetKeyword(keyword: string): Promise<any> {
|
||||||
return this.findController.setKeyword(keyword);
|
return this.findController.setKeyword(keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddonEnabledResponse(enabled) {
|
onAddonEnabledResponse(enabled: boolean): Promise<any> {
|
||||||
return this.addonEnabledController.indicate(enabled);
|
return this.addonEnabledController.indicate(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenUrl(newTab, url, openerId, background) {
|
onOpenUrl(
|
||||||
|
newTab: boolean, url: string, openerId: number, background: boolean,
|
||||||
|
): Promise<any> {
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
return this.linkController.openNewTab(url, openerId, background);
|
return this.linkController.openNewTab(url, openerId, background);
|
||||||
}
|
}
|
||||||
return this.linkController.openToTab(url, openerId);
|
return this.linkController.openToTab(url, openerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackgroundOperation(operation) {
|
onBackgroundOperation(operation: any): Promise<any> {
|
||||||
return this.backgroundOperationController.exec(operation);
|
return this.backgroundOperationController.exec(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMarkSetGlobal(key, x, y) {
|
onMarkSetGlobal(key: string, x: number, y: number): Promise<any> {
|
||||||
return this.markController.setGlobal(key, x, y);
|
return this.markController.setGlobal(key, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMarkJumpGlobal(key) {
|
onMarkJumpGlobal(key: string): Promise<any> {
|
||||||
return this.markController.jumpGlobal(key);
|
return this.markController.jumpGlobal(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConsoleFrameMessage(tabId, message) {
|
onConsoleFrameMessage(tabId: number, message: any): void {
|
||||||
let port = this.consolePorts[tabId];
|
let port = this.consolePorts[tabId];
|
||||||
if (!port) {
|
if (!port) {
|
||||||
return;
|
return;
|
||||||
|
@ -124,12 +158,14 @@ export default class ContentMessageListener {
|
||||||
port.postMessage(message);
|
port.postMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnected(port) {
|
onConnected(port: browser.runtime.Port): void {
|
||||||
if (port.name !== 'vimvixen-console') {
|
if (port.name !== 'vimvixen-console') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = port.sender.tab.id;
|
if (port.sender && port.sender.tab && port.sender.tab.id) {
|
||||||
this.consolePorts[id] = port;
|
let id = port.sender.tab.id;
|
||||||
|
this.consolePorts[id] = port;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
const db = {};
|
const db: {[key: string]: any} = {};
|
||||||
|
|
||||||
export default class MemoryStorage {
|
export default class MemoryStorage {
|
||||||
set(name, value) {
|
set(name: string, value: any): void {
|
||||||
let data = JSON.stringify(value);
|
let data = JSON.stringify(value);
|
||||||
if (typeof data === 'undefined') {
|
if (typeof data === 'undefined') {
|
||||||
throw new Error('value is not serializable');
|
throw new Error('value is not serializable');
|
||||||
|
@ -9,7 +9,7 @@ export default class MemoryStorage {
|
||||||
db[name] = data;
|
db[name] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(name) {
|
get(name: string): any {
|
||||||
let data = db[name];
|
let data = db[name];
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return undefined;
|
return undefined;
|
|
@ -1,12 +1,12 @@
|
||||||
export default class IndicatorPresenter {
|
export default class IndicatorPresenter {
|
||||||
indicate(enabled) {
|
indicate(enabled: boolean): Promise<void> {
|
||||||
let path = enabled
|
let path = enabled
|
||||||
? 'resources/enabled_32x32.png'
|
? 'resources/enabled_32x32.png'
|
||||||
: 'resources/disabled_32x32.png';
|
: 'resources/disabled_32x32.png';
|
||||||
return browser.browserAction.setIcon({ path });
|
return browser.browserAction.setIcon({ path });
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(listener) {
|
onClick(listener: (arg: browser.tabs.Tab) => void): void {
|
||||||
browser.browserAction.onClicked.addListener(listener);
|
browser.browserAction.onClicked.addListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
const NOTIFICATION_ID = 'vimvixen-update';
|
const NOTIFICATION_ID = 'vimvixen-update';
|
||||||
|
|
||||||
export default class NotifyPresenter {
|
export default class NotifyPresenter {
|
||||||
notify(title, message, onclick) {
|
async notify(
|
||||||
const listener = (id) => {
|
title: string,
|
||||||
|
message: string,
|
||||||
|
onclick: () => void,
|
||||||
|
): Promise<void> {
|
||||||
|
const listener = (id: string) => {
|
||||||
if (id !== NOTIFICATION_ID) {
|
if (id !== NOTIFICATION_ID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +17,7 @@ export default class NotifyPresenter {
|
||||||
};
|
};
|
||||||
browser.notifications.onClicked.addListener(listener);
|
browser.notifications.onClicked.addListener(listener);
|
||||||
|
|
||||||
return browser.notifications.create(NOTIFICATION_ID, {
|
await browser.notifications.create(NOTIFICATION_ID, {
|
||||||
'type': 'basic',
|
'type': 'basic',
|
||||||
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
|
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
|
||||||
title,
|
title,
|
|
@ -3,27 +3,29 @@ import MemoryStorage from '../infrastructures/MemoryStorage';
|
||||||
const CURRENT_SELECTED_KEY = 'tabs.current.selected';
|
const CURRENT_SELECTED_KEY = 'tabs.current.selected';
|
||||||
const LAST_SELECTED_KEY = 'tabs.last.selected';
|
const LAST_SELECTED_KEY = 'tabs.last.selected';
|
||||||
|
|
||||||
|
type Tab = browser.tabs.Tab;
|
||||||
|
|
||||||
export default class TabPresenter {
|
export default class TabPresenter {
|
||||||
open(url, tabId) {
|
open(url: string, tabId?: number): Promise<Tab> {
|
||||||
return browser.tabs.update(tabId, { url });
|
return browser.tabs.update(tabId, { url });
|
||||||
}
|
}
|
||||||
|
|
||||||
create(url, opts) {
|
create(url: string, opts?: object): Promise<Tab> {
|
||||||
return browser.tabs.create({ url, ...opts });
|
return browser.tabs.create({ url, ...opts });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrent() {
|
async getCurrent(): Promise<Tab> {
|
||||||
let tabs = await browser.tabs.query({
|
let tabs = await browser.tabs.query({
|
||||||
active: true, currentWindow: true
|
active: true, currentWindow: true
|
||||||
});
|
});
|
||||||
return tabs[0];
|
return tabs[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll() {
|
getAll(): Promise<Tab[]> {
|
||||||
return browser.tabs.query({ currentWindow: true });
|
return browser.tabs.query({ currentWindow: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLastSelectedId() {
|
async getLastSelectedId(): Promise<number | undefined> {
|
||||||
let cache = new MemoryStorage();
|
let cache = new MemoryStorage();
|
||||||
let tabId = await cache.get(LAST_SELECTED_KEY);
|
let tabId = await cache.get(LAST_SELECTED_KEY);
|
||||||
if (tabId === null || typeof tabId === 'undefined') {
|
if (tabId === null || typeof tabId === 'undefined') {
|
||||||
|
@ -32,25 +34,25 @@ export default class TabPresenter {
|
||||||
return tabId;
|
return tabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByKeyword(keyword, excludePinned = false) {
|
async getByKeyword(keyword: string, excludePinned = false): Promise<Tab[]> {
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true });
|
let tabs = await browser.tabs.query({ currentWindow: true });
|
||||||
return tabs.filter((t) => {
|
return tabs.filter((t) => {
|
||||||
return t.url.toLowerCase().includes(keyword.toLowerCase()) ||
|
return t.url && t.url.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||||
t.title && t.title.toLowerCase().includes(keyword.toLowerCase());
|
t.title && t.title.toLowerCase().includes(keyword.toLowerCase());
|
||||||
}).filter((t) => {
|
}).filter((t) => {
|
||||||
return !(excludePinned && t.pinned);
|
return !(excludePinned && t.pinned);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
select(tabId) {
|
select(tabId: number): Promise<Tab> {
|
||||||
return browser.tabs.update(tabId, { active: true });
|
return browser.tabs.update(tabId, { active: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(ids) {
|
remove(ids: number[]): Promise<void> {
|
||||||
return browser.tabs.remove(ids);
|
return browser.tabs.remove(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reopen() {
|
async reopen(): Promise<any> {
|
||||||
let window = await browser.windows.getCurrent();
|
let window = await browser.windows.getCurrent();
|
||||||
let sessions = await browser.sessions.getRecentlyClosed();
|
let sessions = await browser.sessions.getRecentlyClosed();
|
||||||
let session = sessions.find((s) => {
|
let session = sessions.find((s) => {
|
||||||
|
@ -59,39 +61,43 @@ export default class TabPresenter {
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (session.tab) {
|
if (session.tab && session.tab.sessionId) {
|
||||||
return browser.sessions.restore(session.tab.sessionId);
|
return browser.sessions.restore(session.tab.sessionId);
|
||||||
}
|
}
|
||||||
return browser.sessions.restore(session.window.sessionId);
|
if (session.window && session.window.sessionId) {
|
||||||
|
return browser.sessions.restore(session.window.sessionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reload(tabId, cache) {
|
reload(tabId: number, cache: boolean): Promise<void> {
|
||||||
return browser.tabs.reload(tabId, { bypassCache: cache });
|
return browser.tabs.reload(tabId, { bypassCache: cache });
|
||||||
}
|
}
|
||||||
|
|
||||||
setPinned(tabId, pinned) {
|
setPinned(tabId: number, pinned: boolean): Promise<Tab> {
|
||||||
return browser.tabs.update(tabId, { pinned });
|
return browser.tabs.update(tabId, { pinned });
|
||||||
}
|
}
|
||||||
|
|
||||||
duplicate(id) {
|
duplicate(id: number): Promise<Tab> {
|
||||||
return browser.tabs.duplicate(id);
|
return browser.tabs.duplicate(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getZoom(tabId) {
|
getZoom(tabId: number): Promise<number> {
|
||||||
return browser.tabs.getZoom(tabId);
|
return browser.tabs.getZoom(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
setZoom(tabId, factor) {
|
setZoom(tabId: number, factor: number): Promise<void> {
|
||||||
return browser.tabs.setZoom(tabId, factor);
|
return browser.tabs.setZoom(tabId, factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelected(listener) {
|
onSelected(
|
||||||
|
listener: (arg: { tabId: number, windowId: number}) => void,
|
||||||
|
): void {
|
||||||
browser.tabs.onActivated.addListener(listener);
|
browser.tabs.onActivated.addListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tabPresenter = new TabPresenter();
|
let tabPresenter = new TabPresenter();
|
||||||
tabPresenter.onSelected((tab) => {
|
tabPresenter.onSelected((tab: any) => {
|
||||||
let cache = new MemoryStorage();
|
let cache = new MemoryStorage();
|
||||||
|
|
||||||
let lastId = cache.get(CURRENT_SELECTED_KEY);
|
let lastId = cache.get(CURRENT_SELECTED_KEY);
|
|
@ -1,5 +1,5 @@
|
||||||
export default class WindowPresenter {
|
export default class WindowPresenter {
|
||||||
create(url) {
|
create(url: string): Promise<browser.windows.Window> {
|
||||||
return browser.windows.create({ url });
|
return browser.windows.create({ url });
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
export default class BookmarkRepository {
|
export default class BookmarkRepository {
|
||||||
async create(title, url) {
|
async create(
|
||||||
|
title: string, url: string
|
||||||
|
): Promise<browser.bookmarks.BookmarkTreeNode> {
|
||||||
let item = await browser.bookmarks.create({
|
let item = await browser.bookmarks.create({
|
||||||
type: 'bookmark',
|
type: 'bookmark',
|
||||||
title,
|
title,
|
|
@ -1,8 +0,0 @@
|
||||||
import * as urls from '../../shared/urls';
|
|
||||||
|
|
||||||
export default class BrowserSettingRepository {
|
|
||||||
async getHomepageUrls() {
|
|
||||||
let { value } = await browser.browserSettings.homepageOverride.get({});
|
|
||||||
return value.split('|').map(urls.normalizeUrl);
|
|
||||||
}
|
|
||||||
}
|
|
24
src/background/repositories/BrowserSettingRepository.ts
Normal file
24
src/background/repositories/BrowserSettingRepository.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import * as urls from '../../shared/urls';
|
||||||
|
|
||||||
|
declare namespace browser.browserSettings.homepageOverride {
|
||||||
|
|
||||||
|
type BrowserSettings = {
|
||||||
|
value: string;
|
||||||
|
levelOfControl: LevelOfControlType;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LevelOfControlType =
|
||||||
|
'not_controllable' |
|
||||||
|
'controlled_by_other_extensions' |
|
||||||
|
'controllable_by_this_extension' |
|
||||||
|
'controlled_by_this_extension';
|
||||||
|
|
||||||
|
function get(param: object): Promise<BrowserSettings>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BrowserSettingRepository {
|
||||||
|
async getHomepageUrls(): Promise<string[]> {
|
||||||
|
let { value } = await browser.browserSettings.homepageOverride.get({});
|
||||||
|
return value.split('|').map(urls.normalizeUrl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,13 @@
|
||||||
|
type Tab = browser.tabs.Tab;
|
||||||
|
type BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
|
||||||
|
|
||||||
export default class CompletionsRepository {
|
export default class CompletionsRepository {
|
||||||
async queryBookmarks(keywords) {
|
async queryBookmarks(keywords: string): Promise<BookmarkTreeNode[]> {
|
||||||
let items = await browser.bookmarks.search({ query: keywords });
|
let items = await browser.bookmarks.search({ query: keywords });
|
||||||
return items.filter((item) => {
|
return items.filter((item) => {
|
||||||
|
if (!item.url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let url = undefined;
|
let url = undefined;
|
||||||
try {
|
try {
|
||||||
url = new URL(item.url);
|
url = new URL(item.url);
|
||||||
|
@ -12,17 +18,17 @@ export default class CompletionsRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
queryHistories(keywords) {
|
queryHistories(keywords: string): Promise<browser.history.HistoryItem[]> {
|
||||||
return browser.history.search({
|
return browser.history.search({
|
||||||
text: keywords,
|
text: keywords,
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryTabs(keywords, excludePinned) {
|
async queryTabs(keywords: string, excludePinned: boolean): Promise<Tab[]> {
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true });
|
let tabs = await browser.tabs.query({ currentWindow: true });
|
||||||
return tabs.filter((t) => {
|
return tabs.filter((t) => {
|
||||||
return t.url.toLowerCase().includes(keywords.toLowerCase()) ||
|
return t.url && t.url.toLowerCase().includes(keywords.toLowerCase()) ||
|
||||||
t.title && t.title.toLowerCase().includes(keywords.toLowerCase());
|
t.title && t.title.toLowerCase().includes(keywords.toLowerCase());
|
||||||
}).filter((t) => {
|
}).filter((t) => {
|
||||||
return !(excludePinned && t.pinned);
|
return !(excludePinned && t.pinned);
|
|
@ -3,15 +3,17 @@ import MemoryStorage from '../infrastructures/MemoryStorage';
|
||||||
const FIND_KEYWORD_KEY = 'find-keyword';
|
const FIND_KEYWORD_KEY = 'find-keyword';
|
||||||
|
|
||||||
export default class FindRepository {
|
export default class FindRepository {
|
||||||
|
private cache: MemoryStorage;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cache = new MemoryStorage();
|
this.cache = new MemoryStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyword() {
|
getKeyword(): Promise<string> {
|
||||||
return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY));
|
return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeyword(keyword) {
|
setKeyword(keyword: string): Promise<any> {
|
||||||
this.cache.set(FIND_KEYWORD_KEY, keyword);
|
this.cache.set(FIND_KEYWORD_KEY, keyword);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
|
@ -4,21 +4,23 @@ import GlobalMark from '../domains/GlobalMark';
|
||||||
const MARK_KEY = 'mark';
|
const MARK_KEY = 'mark';
|
||||||
|
|
||||||
export default class MarkRepository {
|
export default class MarkRepository {
|
||||||
|
private cache: MemoryStorage;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cache = new MemoryStorage();
|
this.cache = new MemoryStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
getMark(key) {
|
getMark(key: string): Promise<GlobalMark | undefined> {
|
||||||
let marks = this.getOrEmptyMarks();
|
let marks = this.getOrEmptyMarks();
|
||||||
let data = marks[key];
|
let data = marks[key];
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
let mark = new GlobalMark(data.tabId, data.url, data.x, data.y);
|
let mark = { tabId: data.tabId, url: data.url, x: data.x, y: data.y };
|
||||||
return Promise.resolve(mark);
|
return Promise.resolve(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMark(key, mark) {
|
setMark(key: string, mark: GlobalMark): Promise<any> {
|
||||||
let marks = this.getOrEmptyMarks();
|
let marks = this.getOrEmptyMarks();
|
||||||
marks[key] = { tabId: mark.tabId, url: mark.url, x: mark.x, y: mark.y };
|
marks[key] = { tabId: mark.tabId, url: mark.url, x: mark.x, y: mark.y };
|
||||||
this.cache.set(MARK_KEY, marks);
|
this.cache.set(MARK_KEY, marks);
|
|
@ -1,12 +1,12 @@
|
||||||
import Setting from '../domains/Setting';
|
import SettingData from '../../shared/SettingData';
|
||||||
|
|
||||||
export default class SettingRepository {
|
export default class SettingRepository {
|
||||||
async load() {
|
async load(): Promise<SettingData | null> {
|
||||||
let { settings } = await browser.storage.local.get('settings');
|
let { settings } = await browser.storage.local.get('settings');
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Setting.deserialize(settings);
|
return SettingData.valueOf(settings as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import MemoryStorage from '../infrastructures/MemoryStorage';
|
|
||||||
|
|
||||||
const CACHED_SETTING_KEY = 'setting';
|
|
||||||
|
|
||||||
export default class SettingRepository {
|
|
||||||
constructor() {
|
|
||||||
this.cache = new MemoryStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return Promise.resolve(this.cache.get(CACHED_SETTING_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
update(value) {
|
|
||||||
return this.cache.set(CACHED_SETTING_KEY, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setProperty(name, value) {
|
|
||||||
let current = await this.get();
|
|
||||||
current.properties[name] = value;
|
|
||||||
return this.update(current);
|
|
||||||
}
|
|
||||||
}
|
|
51
src/background/repositories/SettingRepository.ts
Normal file
51
src/background/repositories/SettingRepository.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import MemoryStorage from '../infrastructures/MemoryStorage';
|
||||||
|
import Settings from '../../shared/Settings';
|
||||||
|
import * as PropertyDefs from '../../shared/property-defs';
|
||||||
|
|
||||||
|
const CACHED_SETTING_KEY = 'setting';
|
||||||
|
|
||||||
|
export default class SettingRepository {
|
||||||
|
private cache: MemoryStorage;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.cache = new MemoryStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
get(): Promise<Settings> {
|
||||||
|
return Promise.resolve(this.cache.get(CACHED_SETTING_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
update(value: Settings): void {
|
||||||
|
return this.cache.set(CACHED_SETTING_KEY, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setProperty(
|
||||||
|
name: string, value: string | number | boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
let def = PropertyDefs.defs.find(d => name === d.name);
|
||||||
|
if (!def) {
|
||||||
|
throw new Error('unknown property: ' + name);
|
||||||
|
}
|
||||||
|
if (typeof value !== def.type) {
|
||||||
|
throw new TypeError(`property type of ${name} mismatch: ${typeof value}`);
|
||||||
|
}
|
||||||
|
let newValue = value;
|
||||||
|
if (typeof value === 'string' && value === '') {
|
||||||
|
newValue = def.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = await this.get();
|
||||||
|
switch (name) {
|
||||||
|
case 'hintchars':
|
||||||
|
current.properties.hintchars = newValue as string;
|
||||||
|
break;
|
||||||
|
case 'smoothscroll':
|
||||||
|
current.properties.smoothscroll = newValue as boolean;
|
||||||
|
break;
|
||||||
|
case 'complete':
|
||||||
|
current.properties.complete = newValue as string;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return this.update(current);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
export default class VersionRepository {
|
|
||||||
async get() {
|
|
||||||
let { version } = await browser.storage.local.get('version');
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(version) {
|
|
||||||
return browser.storage.local.set({ version });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,10 +3,20 @@ import TabPresenter from '../presenters/TabPresenter';
|
||||||
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
||||||
|
|
||||||
export default class AddonEnabledUseCase {
|
export default class AddonEnabledUseCase {
|
||||||
|
private indicatorPresentor: IndicatorPresenter;
|
||||||
|
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private contentMessageClient: ContentMessageClient;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.indicatorPresentor = new IndicatorPresenter();
|
this.indicatorPresentor = new IndicatorPresenter();
|
||||||
|
|
||||||
this.indicatorPresentor.onClick(tab => this.onIndicatorClick(tab.id));
|
this.indicatorPresentor.onClick((tab) => {
|
||||||
|
if (tab.id) {
|
||||||
|
this.onIndicatorClick(tab.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId));
|
this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId));
|
||||||
|
@ -14,15 +24,15 @@ export default class AddonEnabledUseCase {
|
||||||
this.contentMessageClient = new ContentMessageClient();
|
this.contentMessageClient = new ContentMessageClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
indicate(enabled) {
|
indicate(enabled: boolean): Promise<void> {
|
||||||
return this.indicatorPresentor.indicate(enabled);
|
return this.indicatorPresentor.indicate(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
onIndicatorClick(tabId) {
|
onIndicatorClick(tabId: number): Promise<void> {
|
||||||
return this.contentMessageClient.toggleAddonEnabled(tabId);
|
return this.contentMessageClient.toggleAddonEnabled(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onTabSelected(tabId) {
|
async onTabSelected(tabId: number): Promise<void> {
|
||||||
let enabled = await this.contentMessageClient.getAddonEnabled(tabId);
|
let enabled = await this.contentMessageClient.getAddonEnabled(tabId);
|
||||||
return this.indicatorPresentor.indicate(enabled);
|
return this.indicatorPresentor.indicate(enabled);
|
||||||
}
|
}
|
|
@ -6,9 +6,20 @@ import SettingRepository from '../repositories/SettingRepository';
|
||||||
import BookmarkRepository from '../repositories/BookmarkRepository';
|
import BookmarkRepository from '../repositories/BookmarkRepository';
|
||||||
import ConsoleClient from '../infrastructures/ConsoleClient';
|
import ConsoleClient from '../infrastructures/ConsoleClient';
|
||||||
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
||||||
import * as properties from 'shared/settings/properties';
|
|
||||||
|
|
||||||
export default class CommandIndicator {
|
export default class CommandIndicator {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private windowPresenter: WindowPresenter;
|
||||||
|
|
||||||
|
private settingRepository: SettingRepository;
|
||||||
|
|
||||||
|
private bookmarkRepository: BookmarkRepository;
|
||||||
|
|
||||||
|
private consoleClient: ConsoleClient;
|
||||||
|
|
||||||
|
private contentMessageClient: ContentMessageClient;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.windowPresenter = new WindowPresenter();
|
this.windowPresenter = new WindowPresenter();
|
||||||
|
@ -19,34 +30,34 @@ export default class CommandIndicator {
|
||||||
this.contentMessageClient = new ContentMessageClient();
|
this.contentMessageClient = new ContentMessageClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
async open(keywords) {
|
async open(keywords: string): Promise<browser.tabs.Tab> {
|
||||||
let url = await this.urlOrSearch(keywords);
|
let url = await this.urlOrSearch(keywords);
|
||||||
return this.tabPresenter.open(url);
|
return this.tabPresenter.open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tabopen(keywords) {
|
async tabopen(keywords: string): Promise<browser.tabs.Tab> {
|
||||||
let url = await this.urlOrSearch(keywords);
|
let url = await this.urlOrSearch(keywords);
|
||||||
return this.tabPresenter.create(url);
|
return this.tabPresenter.create(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async winopen(keywords) {
|
async winopen(keywords: string): Promise<browser.windows.Window> {
|
||||||
let url = await this.urlOrSearch(keywords);
|
let url = await this.urlOrSearch(keywords);
|
||||||
return this.windowPresenter.create(url);
|
return this.windowPresenter.create(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
// eslint-disable-next-line max-statements
|
||||||
async buffer(keywords) {
|
async buffer(keywords: string): Promise<any> {
|
||||||
if (keywords.length === 0) {
|
if (keywords.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNaN(keywords)) {
|
if (!isNaN(Number(keywords))) {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
let index = parseInt(keywords, 10) - 1;
|
let index = parseInt(keywords, 10) - 1;
|
||||||
if (index < 0 || tabs.length <= index) {
|
if (index < 0 || tabs.length <= index) {
|
||||||
throw new RangeError(`tab ${index + 1} does not exist`);
|
throw new RangeError(`tab ${index + 1} does not exist`);
|
||||||
}
|
}
|
||||||
return this.tabPresenter.select(tabs[index].id);
|
return this.tabPresenter.select(tabs[index].id as number);
|
||||||
} else if (keywords.trim() === '%') {
|
} else if (keywords.trim() === '%') {
|
||||||
// Select current window
|
// Select current window
|
||||||
return;
|
return;
|
||||||
|
@ -66,13 +77,13 @@ export default class CommandIndicator {
|
||||||
}
|
}
|
||||||
for (let tab of tabs) {
|
for (let tab of tabs) {
|
||||||
if (tab.index > current.index) {
|
if (tab.index > current.index) {
|
||||||
return this.tabPresenter.select(tab.id);
|
return this.tabPresenter.select(tab.id as number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.tabPresenter.select(tabs[0].id);
|
return this.tabPresenter.select(tabs[0].id as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async bdelete(force, keywords) {
|
async bdelete(force: boolean, keywords: string): Promise<any> {
|
||||||
let excludePinned = !force;
|
let excludePinned = !force;
|
||||||
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
|
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
|
||||||
if (tabs.length === 0) {
|
if (tabs.length === 0) {
|
||||||
|
@ -80,45 +91,45 @@ export default class CommandIndicator {
|
||||||
} else if (tabs.length > 1) {
|
} else if (tabs.length > 1) {
|
||||||
throw new Error('More than one match for ' + keywords);
|
throw new Error('More than one match for ' + keywords);
|
||||||
}
|
}
|
||||||
return this.tabPresenter.remove([tabs[0].id]);
|
return this.tabPresenter.remove([tabs[0].id as number]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async bdeletes(force, keywords) {
|
async bdeletes(force: boolean, keywords: string): Promise<any> {
|
||||||
let excludePinned = !force;
|
let excludePinned = !force;
|
||||||
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
|
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
|
||||||
let ids = tabs.map(tab => tab.id);
|
let ids = tabs.map(tab => tab.id as number);
|
||||||
return this.tabPresenter.remove(ids);
|
return this.tabPresenter.remove(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
async quit() {
|
async quit(): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
return this.tabPresenter.remove([tab.id]);
|
return this.tabPresenter.remove([tab.id as number]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async quitAll() {
|
async quitAll(): Promise<any> {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
let ids = tabs.map(tab => tab.id);
|
let ids = tabs.map(tab => tab.id as number);
|
||||||
this.tabPresenter.remove(ids);
|
this.tabPresenter.remove(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addbookmark(title) {
|
async addbookmark(title: string): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
let item = await this.bookmarkRepository.create(title, tab.url);
|
let item = await this.bookmarkRepository.create(title, tab.url as string);
|
||||||
let message = 'Saved current page: ' + item.url;
|
let message = 'Saved current page: ' + item.url;
|
||||||
return this.consoleClient.showInfo(tab.id, message);
|
return this.consoleClient.showInfo(tab.id as number, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(keywords) {
|
async set(keywords: string): Promise<any> {
|
||||||
if (keywords.length === 0) {
|
if (keywords.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let [name, value] = parsers.parseSetOption(keywords, properties.types);
|
let [name, value] = parsers.parseSetOption(keywords);
|
||||||
await this.settingRepository.setProperty(name, value);
|
await this.settingRepository.setProperty(name, value);
|
||||||
|
|
||||||
return this.contentMessageClient.broadcastSettingsChanged();
|
return this.contentMessageClient.broadcastSettingsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
async urlOrSearch(keywords) {
|
async urlOrSearch(keywords: string): Promise<any> {
|
||||||
let settings = await this.settingRepository.get();
|
let settings = await this.settingRepository.get();
|
||||||
return urls.searchUrl(keywords, settings.search);
|
return urls.searchUrl(keywords, settings.search);
|
||||||
}
|
}
|
|
@ -1,23 +1,30 @@
|
||||||
import CompletionItem from '../domains/CompletionItem';
|
|
||||||
import CompletionGroup from '../domains/CompletionGroup';
|
import CompletionGroup from '../domains/CompletionGroup';
|
||||||
import Completions from '../domains/Completions';
|
|
||||||
import CommandDocs from '../domains/CommandDocs';
|
import CommandDocs from '../domains/CommandDocs';
|
||||||
import CompletionsRepository from '../repositories/CompletionsRepository';
|
import CompletionsRepository from '../repositories/CompletionsRepository';
|
||||||
import * as filters from './filters';
|
import * as filters from './filters';
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
import SettingRepository from '../repositories/SettingRepository';
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
import * as properties from '../../shared/settings/properties';
|
import * as PropertyDefs from '../../shared/property-defs';
|
||||||
|
|
||||||
const COMPLETION_ITEM_LIMIT = 10;
|
const COMPLETION_ITEM_LIMIT = 10;
|
||||||
|
|
||||||
|
type Tab = browser.tabs.Tab;
|
||||||
|
type HistoryItem = browser.history.HistoryItem;
|
||||||
|
|
||||||
export default class CompletionsUseCase {
|
export default class CompletionsUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private completionsRepository: CompletionsRepository;
|
||||||
|
|
||||||
|
private settingRepository: SettingRepository;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.completionsRepository = new CompletionsRepository();
|
this.completionsRepository = new CompletionsRepository();
|
||||||
this.settingRepository = new SettingRepository();
|
this.settingRepository = new SettingRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
queryConsoleCommand(prefix) {
|
queryConsoleCommand(prefix: string): Promise<CompletionGroup[]> {
|
||||||
let keys = Object.keys(CommandDocs);
|
let keys = Object.keys(CommandDocs);
|
||||||
let items = keys
|
let items = keys
|
||||||
.filter(name => name.startsWith(prefix))
|
.filter(name => name.startsWith(prefix))
|
||||||
|
@ -28,48 +35,49 @@ export default class CompletionsUseCase {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return Promise.resolve(Completions.empty());
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
return Promise.resolve(
|
return Promise.resolve([{ name: 'Console Command', items }]);
|
||||||
new Completions([new CompletionGroup('Console Command', items)])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryOpen(name, keywords) {
|
async queryOpen(name: string, keywords: string): Promise<CompletionGroup[]> {
|
||||||
let settings = await this.settingRepository.get();
|
let settings = await this.settingRepository.get();
|
||||||
let groups = [];
|
let groups: CompletionGroup[] = [];
|
||||||
|
|
||||||
let complete = settings.properties.complete || properties.defaults.complete;
|
let complete = settings.properties.complete;
|
||||||
for (let c of complete) {
|
for (let c of complete) {
|
||||||
if (c === 's') {
|
if (c === 's') {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
let engines = await this.querySearchEngineItems(name, keywords);
|
let engines = await this.querySearchEngineItems(name, keywords);
|
||||||
if (engines.length > 0) {
|
if (engines.length > 0) {
|
||||||
groups.push(new CompletionGroup('Search Engines', engines));
|
groups.push({ name: 'Search Engines', items: engines });
|
||||||
}
|
}
|
||||||
} else if (c === 'h') {
|
} else if (c === 'h') {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
let histories = await this.queryHistoryItems(name, keywords);
|
let histories = await this.queryHistoryItems(name, keywords);
|
||||||
if (histories.length > 0) {
|
if (histories.length > 0) {
|
||||||
groups.push(new CompletionGroup('History', histories));
|
groups.push({ name: 'History', items: histories });
|
||||||
}
|
}
|
||||||
} else if (c === 'b') {
|
} else if (c === 'b') {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
let bookmarks = await this.queryBookmarkItems(name, keywords);
|
let bookmarks = await this.queryBookmarkItems(name, keywords);
|
||||||
if (bookmarks.length > 0) {
|
if (bookmarks.length > 0) {
|
||||||
groups.push(new CompletionGroup('Bookmarks', bookmarks));
|
groups.push({ name: 'Bookmarks', items: bookmarks });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Completions(groups);
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
// eslint-disable-next-line max-statements
|
||||||
async queryBuffer(name, keywords) {
|
async queryBuffer(
|
||||||
|
name: string,
|
||||||
|
keywords: string,
|
||||||
|
): Promise<CompletionGroup[]> {
|
||||||
let lastId = await this.tabPresenter.getLastSelectedId();
|
let lastId = await this.tabPresenter.getLastSelectedId();
|
||||||
let trimmed = keywords.trim();
|
let trimmed = keywords.trim();
|
||||||
let tabs = [];
|
let tabs: Tab[] = [];
|
||||||
if (trimmed.length > 0 && !isNaN(trimmed)) {
|
if (trimmed.length > 0 && !isNaN(Number(trimmed))) {
|
||||||
let all = await this.tabPresenter.getAll();
|
let all = await this.tabPresenter.getAll();
|
||||||
let index = parseInt(trimmed, 10) - 1;
|
let index = parseInt(trimmed, 10) - 1;
|
||||||
if (index >= 0 && index < all.length) {
|
if (index >= 0 && index < all.length) {
|
||||||
|
@ -77,18 +85,18 @@ export default class CompletionsUseCase {
|
||||||
}
|
}
|
||||||
} else if (trimmed === '%') {
|
} else if (trimmed === '%') {
|
||||||
let all = await this.tabPresenter.getAll();
|
let all = await this.tabPresenter.getAll();
|
||||||
let tab = all.find(t => t.active);
|
let tab = all.find(t => t.active) as Tab;
|
||||||
tabs = [tab];
|
tabs = [tab];
|
||||||
} else if (trimmed === '#') {
|
} else if (trimmed === '#') {
|
||||||
if (typeof lastId !== 'undefined' && lastId !== null) {
|
if (typeof lastId !== 'undefined' && lastId !== null) {
|
||||||
let all = await this.tabPresenter.getAll();
|
let all = await this.tabPresenter.getAll();
|
||||||
let tab = all.find(t => t.id === lastId);
|
let tab = all.find(t => t.id === lastId) as Tab;
|
||||||
tabs = [tab];
|
tabs = [tab];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tabs = await this.completionsRepository.queryTabs(keywords, false);
|
tabs = await this.completionsRepository.queryTabs(keywords, false);
|
||||||
}
|
}
|
||||||
const flag = (tab) => {
|
const flag = (tab: Tab) => {
|
||||||
if (tab.active) {
|
if (tab.active) {
|
||||||
return '%';
|
return '%';
|
||||||
} else if (tab.id === lastId) {
|
} else if (tab.id === lastId) {
|
||||||
|
@ -96,87 +104,90 @@ export default class CompletionsUseCase {
|
||||||
}
|
}
|
||||||
return ' ';
|
return ' ';
|
||||||
};
|
};
|
||||||
let items = tabs.map(tab => new CompletionItem({
|
let items = tabs.map(tab => ({
|
||||||
caption: tab.index + 1 + ': ' + flag(tab) + ' ' + tab.title,
|
caption: tab.index + 1 + ': ' + flag(tab) + ' ' + tab.title,
|
||||||
content: name + ' ' + tab.title,
|
content: name + ' ' + tab.title,
|
||||||
url: tab.url,
|
url: tab.url,
|
||||||
icon: tab.favIconUrl
|
icon: tab.favIconUrl,
|
||||||
}));
|
}));
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return Promise.resolve(Completions.empty());
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
return new Completions([new CompletionGroup('Buffers', items)]);
|
return [{ name: 'Buffers', items }];
|
||||||
}
|
}
|
||||||
|
|
||||||
queryBdelete(name, keywords) {
|
queryBdelete(name: string, keywords: string): Promise<CompletionGroup[]> {
|
||||||
return this.queryTabs(name, true, keywords);
|
return this.queryTabs(name, true, keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
queryBdeleteForce(name, keywords) {
|
queryBdeleteForce(
|
||||||
|
name: string, keywords: string,
|
||||||
|
): Promise<CompletionGroup[]> {
|
||||||
return this.queryTabs(name, false, keywords);
|
return this.queryTabs(name, false, keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
querySet(name, keywords) {
|
querySet(name: string, keywords: string): Promise<CompletionGroup[]> {
|
||||||
let items = Object.keys(properties.docs).map((key) => {
|
let items = PropertyDefs.defs.map((def) => {
|
||||||
if (properties.types[key] === 'boolean') {
|
if (def.type === 'boolean') {
|
||||||
return [
|
return [
|
||||||
new CompletionItem({
|
{
|
||||||
caption: key,
|
caption: def.name,
|
||||||
content: name + ' ' + key,
|
content: name + ' ' + def.name,
|
||||||
url: 'Enable ' + properties.docs[key],
|
url: 'Enable ' + def.description,
|
||||||
}),
|
}, {
|
||||||
new CompletionItem({
|
caption: 'no' + def.name,
|
||||||
caption: 'no' + key,
|
content: name + ' no' + def.name,
|
||||||
content: name + ' no' + key,
|
url: 'Disable ' + def.description
|
||||||
url: 'Disable ' + properties.docs[key],
|
}
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
new CompletionItem({
|
{
|
||||||
caption: key,
|
caption: def.name,
|
||||||
content: name + ' ' + key,
|
content: name + ' ' + def.name,
|
||||||
url: 'Set ' + properties.docs[key],
|
url: 'Set ' + def.description,
|
||||||
})
|
}
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
items = items.reduce((acc, val) => acc.concat(val), []);
|
let flatten = items.reduce((acc, val) => acc.concat(val), []);
|
||||||
items = items.filter((item) => {
|
flatten = flatten.filter((item) => {
|
||||||
return item.caption.startsWith(keywords);
|
return item.caption.startsWith(keywords);
|
||||||
});
|
});
|
||||||
if (items.length === 0) {
|
if (flatten.length === 0) {
|
||||||
return Promise.resolve(Completions.empty());
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
new Completions([new CompletionGroup('Properties', items)])
|
[{ name: 'Properties', items: flatten }],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryTabs(name, excludePinned, args) {
|
async queryTabs(
|
||||||
|
name: string, excludePinned: boolean, args: string,
|
||||||
|
): Promise<CompletionGroup[]> {
|
||||||
let tabs = await this.completionsRepository.queryTabs(args, excludePinned);
|
let tabs = await this.completionsRepository.queryTabs(args, excludePinned);
|
||||||
let items = tabs.map(tab => new CompletionItem({
|
let items = tabs.map(tab => ({
|
||||||
caption: tab.title,
|
caption: tab.title,
|
||||||
content: name + ' ' + tab.title,
|
content: name + ' ' + tab.title,
|
||||||
url: tab.url,
|
url: tab.url,
|
||||||
icon: tab.favIconUrl
|
icon: tab.favIconUrl
|
||||||
}));
|
}));
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return Promise.resolve(Completions.empty());
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
return new Completions([new CompletionGroup('Buffers', items)]);
|
return [{ name: 'Buffers', items }];
|
||||||
}
|
}
|
||||||
|
|
||||||
async querySearchEngineItems(name, keywords) {
|
async querySearchEngineItems(name: string, keywords: string) {
|
||||||
let settings = await this.settingRepository.get();
|
let settings = await this.settingRepository.get();
|
||||||
let engines = Object.keys(settings.search.engines)
|
let engines = Object.keys(settings.search.engines)
|
||||||
.filter(key => key.startsWith(keywords));
|
.filter(key => key.startsWith(keywords));
|
||||||
return engines.map(key => new CompletionItem({
|
return engines.map(key => ({
|
||||||
caption: key,
|
caption: key,
|
||||||
content: name + ' ' + key,
|
content: name + ' ' + key,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryHistoryItems(name, keywords) {
|
async queryHistoryItems(name: string, keywords: string) {
|
||||||
let histories = await this.completionsRepository.queryHistories(keywords);
|
let histories = await this.completionsRepository.queryHistories(keywords);
|
||||||
histories = [histories]
|
histories = [histories]
|
||||||
.map(filters.filterBlankTitle)
|
.map(filters.filterBlankTitle)
|
||||||
|
@ -184,19 +195,21 @@ export default class CompletionsUseCase {
|
||||||
.map(filters.filterByTailingSlash)
|
.map(filters.filterByTailingSlash)
|
||||||
.map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT))
|
.map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT))
|
||||||
.map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0]
|
.map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0]
|
||||||
.sort((x, y) => x.visitCount < y.visitCount)
|
.sort((x: HistoryItem, y: HistoryItem): number => {
|
||||||
|
return Number(x.visitCount) - Number(y.visitCount);
|
||||||
|
})
|
||||||
.slice(0, COMPLETION_ITEM_LIMIT);
|
.slice(0, COMPLETION_ITEM_LIMIT);
|
||||||
return histories.map(page => new CompletionItem({
|
return histories.map(page => ({
|
||||||
caption: page.title,
|
caption: page.title,
|
||||||
content: name + ' ' + page.url,
|
content: name + ' ' + page.url,
|
||||||
url: page.url
|
url: page.url
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryBookmarkItems(name, keywords) {
|
async queryBookmarkItems(name: string, keywords: string) {
|
||||||
let bookmarks = await this.completionsRepository.queryBookmarks(keywords);
|
let bookmarks = await this.completionsRepository.queryBookmarks(keywords);
|
||||||
return bookmarks.slice(0, COMPLETION_ITEM_LIMIT)
|
return bookmarks.slice(0, COMPLETION_ITEM_LIMIT)
|
||||||
.map(page => new CompletionItem({
|
.map(page => ({
|
||||||
caption: page.title,
|
caption: page.title,
|
||||||
content: name + ' ' + page.url,
|
content: name + ' ' + page.url,
|
||||||
url: page.url
|
url: page.url
|
|
@ -1,61 +0,0 @@
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
|
||||||
import ConsoleClient from '../infrastructures/ConsoleClient';
|
|
||||||
|
|
||||||
export default class ConsoleUseCase {
|
|
||||||
constructor() {
|
|
||||||
this.tabPresenter = new TabPresenter();
|
|
||||||
this.consoleClient = new ConsoleClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
async showCommand() {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
return this.consoleClient.showCommand(tab.id, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async showOpenCommand(alter) {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let command = 'open ';
|
|
||||||
if (alter) {
|
|
||||||
command += tab.url;
|
|
||||||
}
|
|
||||||
return this.consoleClient.showCommand(tab.id, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async showTabopenCommand(alter) {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let command = 'tabopen ';
|
|
||||||
if (alter) {
|
|
||||||
command += tab.url;
|
|
||||||
}
|
|
||||||
return this.consoleClient.showCommand(tab.id, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async showWinopenCommand(alter) {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let command = 'winopen ';
|
|
||||||
if (alter) {
|
|
||||||
command += tab.url;
|
|
||||||
}
|
|
||||||
return this.consoleClient.showCommand(tab.id, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async showBufferCommand() {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let command = 'buffer ';
|
|
||||||
return this.consoleClient.showCommand(tab.id, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async showAddbookmarkCommand(alter) {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let command = 'addbookmark ';
|
|
||||||
if (alter) {
|
|
||||||
command += tab.title;
|
|
||||||
}
|
|
||||||
return this.consoleClient.showCommand(tab.id, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async hideConsole() {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
return this.consoleClient.hide(tab.id);
|
|
||||||
}
|
|
||||||
}
|
|
65
src/background/usecases/ConsoleUseCase.ts
Normal file
65
src/background/usecases/ConsoleUseCase.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
|
import ConsoleClient from '../infrastructures/ConsoleClient';
|
||||||
|
|
||||||
|
export default class ConsoleUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private consoleClient: ConsoleClient;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.tabPresenter = new TabPresenter();
|
||||||
|
this.consoleClient = new ConsoleClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
async showCommand(): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
return this.consoleClient.showCommand(tab.id as number, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async showOpenCommand(alter: boolean): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let command = 'open ';
|
||||||
|
if (alter) {
|
||||||
|
command += tab.url || '';
|
||||||
|
}
|
||||||
|
return this.consoleClient.showCommand(tab.id as number, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async showTabopenCommand(alter: boolean): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let command = 'tabopen ';
|
||||||
|
if (alter) {
|
||||||
|
command += tab.url || '';
|
||||||
|
}
|
||||||
|
return this.consoleClient.showCommand(tab.id as number, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async showWinopenCommand(alter: boolean): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let command = 'winopen ';
|
||||||
|
if (alter) {
|
||||||
|
command += tab.url || '';
|
||||||
|
}
|
||||||
|
return this.consoleClient.showCommand(tab.id as number, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async showBufferCommand(): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let command = 'buffer ';
|
||||||
|
return this.consoleClient.showCommand(tab.id as number, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async showAddbookmarkCommand(alter: boolean): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let command = 'addbookmark ';
|
||||||
|
if (alter) {
|
||||||
|
command += tab.title || '';
|
||||||
|
}
|
||||||
|
return this.consoleClient.showCommand(tab.id as number, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async hideConsole(): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
return this.consoleClient.hide(tab.id as number);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,22 +3,28 @@ import TabPresenter from '../presenters/TabPresenter';
|
||||||
import ConsoleClient from '../infrastructures/ConsoleClient';
|
import ConsoleClient from '../infrastructures/ConsoleClient';
|
||||||
|
|
||||||
export default class FindUseCase {
|
export default class FindUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private findRepository: FindRepository;
|
||||||
|
|
||||||
|
private consoleClient: ConsoleClient;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.findRepository = new FindRepository();
|
this.findRepository = new FindRepository();
|
||||||
this.consoleClient = new ConsoleClient();
|
this.consoleClient = new ConsoleClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyword() {
|
getKeyword(): Promise<string> {
|
||||||
return this.findRepository.getKeyword();
|
return this.findRepository.getKeyword();
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeyword(keyword) {
|
setKeyword(keyword: string): Promise<any> {
|
||||||
return this.findRepository.setKeyword(keyword);
|
return this.findRepository.setKeyword(keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findStart() {
|
async findStart(): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
return this.consoleClient.showFind(tab.id);
|
return this.consoleClient.showFind(tab.id as number);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
|
|
||||||
export default class LinkUseCase {
|
export default class LinkUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.settingRepository = new SettingRepository();
|
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
openToTab(url, tabId) {
|
openToTab(url: string, tabId: number): Promise<any> {
|
||||||
return this.tabPresenter.open(url, tabId);
|
return this.tabPresenter.open(url, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
openNewTab(url, openerId, background) {
|
openNewTab(url: string, openerId: number, background: boolean): Promise<any> {
|
||||||
return this.tabPresenter.create(url, {
|
return this.tabPresenter.create(url, {
|
||||||
openerTabId: openerId, active: !background
|
openerTabId: openerId, active: !background
|
||||||
});
|
});
|
|
@ -1,10 +1,17 @@
|
||||||
import GlobalMark from '../domains/GlobalMark';
|
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
import MarkRepository from '../repositories/MarkRepository';
|
import MarkRepository from '../repositories/MarkRepository';
|
||||||
import ConsoleClient from '../infrastructures/ConsoleClient';
|
import ConsoleClient from '../infrastructures/ConsoleClient';
|
||||||
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
import ContentMessageClient from '../infrastructures/ContentMessageClient';
|
||||||
|
|
||||||
export default class MarkUseCase {
|
export default class MarkUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private markRepository: MarkRepository;
|
||||||
|
|
||||||
|
private consoleClient: ConsoleClient;
|
||||||
|
|
||||||
|
private contentMessageClient: ContentMessageClient;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.markRepository = new MarkRepository();
|
this.markRepository = new MarkRepository();
|
||||||
|
@ -12,28 +19,28 @@ export default class MarkUseCase {
|
||||||
this.contentMessageClient = new ContentMessageClient();
|
this.contentMessageClient = new ContentMessageClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setGlobal(key, x, y) {
|
async setGlobal(key: string, x: number, y: number): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
let mark = new GlobalMark(tab.id, tab.url, x, y);
|
let mark = { tabId: tab.id as number, url: tab.url as string, x, y };
|
||||||
return this.markRepository.setMark(key, mark);
|
return this.markRepository.setMark(key, mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
async jumpGlobal(key) {
|
async jumpGlobal(key: string): Promise<any> {
|
||||||
let current = await this.tabPresenter.getCurrent();
|
let current = await this.tabPresenter.getCurrent();
|
||||||
|
|
||||||
let mark = await this.markRepository.getMark(key);
|
let mark = await this.markRepository.getMark(key);
|
||||||
if (!mark) {
|
if (!mark) {
|
||||||
return this.consoleClient.showError(current.id, 'Mark is not set');
|
return this.consoleClient.showError(
|
||||||
|
current.id as number, 'Mark is not set');
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
return this.contentMessageClient.scrollTo(
|
await this.contentMessageClient.scrollTo(mark.tabId, mark.x, mark.y);
|
||||||
mark.tabId, mark.x, mark.y
|
|
||||||
).then(() => {
|
|
||||||
return this.tabPresenter.select(mark.tabId);
|
return this.tabPresenter.select(mark.tabId);
|
||||||
}).catch(async() => {
|
} catch (e) {
|
||||||
let tab = await this.tabPresenter.create(mark.url);
|
let tab = await this.tabPresenter.create(mark.url);
|
||||||
let mark2 = new GlobalMark(tab.id, mark.url, mark.x, mark.y);
|
return this.markRepository.setMark(key, {
|
||||||
return this.markRepository.setMark(key, mark2);
|
tabId: tab.id as number, url: mark.url, x: mark.x, y: mark.y,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,28 +1,31 @@
|
||||||
import Setting from '../domains/Setting';
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
import PersistentSettingRepository from '../repositories/PersistentSettingRepository';
|
import PersistentSettingRepository from '../repositories/PersistentSettingRepository';
|
||||||
import SettingRepository from '../repositories/SettingRepository';
|
import SettingRepository from '../repositories/SettingRepository';
|
||||||
|
import { DefaultSettingData } from '../../shared/SettingData';
|
||||||
|
import Settings from '../../shared/Settings';
|
||||||
|
|
||||||
export default class SettingUseCase {
|
export default class SettingUseCase {
|
||||||
|
private persistentSettingRepository: PersistentSettingRepository;
|
||||||
|
|
||||||
|
private settingRepository: SettingRepository;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.persistentSettingRepository = new PersistentSettingRepository();
|
this.persistentSettingRepository = new PersistentSettingRepository();
|
||||||
this.settingRepository = new SettingRepository();
|
this.settingRepository = new SettingRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
get(): Promise<Settings> {
|
||||||
return this.settingRepository.get();
|
return this.settingRepository.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload() {
|
async reload(): Promise<Settings> {
|
||||||
let settings = await this.persistentSettingRepository.load();
|
let data = await this.persistentSettingRepository.load();
|
||||||
if (!settings) {
|
if (!data) {
|
||||||
settings = Setting.defaultSettings();
|
data = DefaultSettingData;
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = settings.value();
|
let value = data.toSettings();
|
||||||
|
|
||||||
this.settingRepository.update(value);
|
this.settingRepository.update(value);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
|
|
||||||
export default class TabSelectUseCase {
|
export default class TabSelectUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectPrev(count) {
|
async selectPrev(count: number): Promise<any> {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
if (tabs.length < 2) {
|
if (tabs.length < 2) {
|
||||||
return;
|
return;
|
||||||
|
@ -15,10 +17,10 @@ export default class TabSelectUseCase {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let select = (tab.index - count + tabs.length) % tabs.length;
|
let select = (tab.index - count + tabs.length) % tabs.length;
|
||||||
return this.tabPresenter.select(tabs[select].id);
|
return this.tabPresenter.select(tabs[select].id as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectNext(count) {
|
async selectNext(count: number): Promise<any> {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
if (tabs.length < 2) {
|
if (tabs.length < 2) {
|
||||||
return;
|
return;
|
||||||
|
@ -28,24 +30,24 @@ export default class TabSelectUseCase {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let select = (tab.index + count) % tabs.length;
|
let select = (tab.index + count) % tabs.length;
|
||||||
return this.tabPresenter.select(tabs[select].id);
|
return this.tabPresenter.select(tabs[select].id as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectFirst() {
|
async selectFirst(): Promise<any> {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
return this.tabPresenter.select(tabs[0].id);
|
return this.tabPresenter.select(tabs[0].id as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectLast() {
|
async selectLast(): Promise<any> {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
return this.tabPresenter.select(tabs[tabs.length - 1].id);
|
return this.tabPresenter.select(tabs[tabs.length - 1].id as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectPrevSelected() {
|
async selectPrevSelected(): Promise<any> {
|
||||||
let tabId = await this.tabPresenter.getLastSelectedId();
|
let tabId = await this.tabPresenter.getLastSelectedId();
|
||||||
if (tabId === null || typeof tabId === 'undefined') {
|
if (tabId === null || typeof tabId === 'undefined') {
|
||||||
return;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
this.tabPresenter.select(tabId);
|
return this.tabPresenter.select(tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,20 +2,24 @@ import TabPresenter from '../presenters/TabPresenter';
|
||||||
import BrowserSettingRepository from '../repositories/BrowserSettingRepository';
|
import BrowserSettingRepository from '../repositories/BrowserSettingRepository';
|
||||||
|
|
||||||
export default class TabUseCase {
|
export default class TabUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private browserSettingRepository: BrowserSettingRepository;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.browserSettingRepository = new BrowserSettingRepository();
|
this.browserSettingRepository = new BrowserSettingRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(force) {
|
async close(force: boolean): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
if (!force && tab.pinned) {
|
if (!force && tab.pinned) {
|
||||||
return;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return this.tabPresenter.remove([tab.id]);
|
return this.tabPresenter.remove([tab.id as number]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeRight() {
|
async closeRight(): Promise<any> {
|
||||||
let tabs = await this.tabPresenter.getAll();
|
let tabs = await this.tabPresenter.getAll();
|
||||||
tabs.sort((t1, t2) => t1.index - t2.index);
|
tabs.sort((t1, t2) => t1.index - t2.index);
|
||||||
let index = tabs.findIndex(t => t.active);
|
let index = tabs.findIndex(t => t.active);
|
||||||
|
@ -25,42 +29,42 @@ export default class TabUseCase {
|
||||||
for (let i = index + 1; i < tabs.length; ++i) {
|
for (let i = index + 1; i < tabs.length; ++i) {
|
||||||
let tab = tabs[i];
|
let tab = tabs[i];
|
||||||
if (!tab.pinned) {
|
if (!tab.pinned) {
|
||||||
this.tabPresenter.remove(tab.id);
|
this.tabPresenter.remove([tab.id as number]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reopen() {
|
reopen(): Promise<any> {
|
||||||
return this.tabPresenter.reopen();
|
return this.tabPresenter.reopen();
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(cache) {
|
async reload(cache: boolean): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
return this.tabPresenter.reload(tab.id, cache);
|
return this.tabPresenter.reload(tab.id as number, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPinned(pinned) {
|
async setPinned(pinned: boolean): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
return this.tabPresenter.setPinned(tab.id, pinned);
|
return this.tabPresenter.setPinned(tab.id as number, pinned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async togglePinned() {
|
async togglePinned(): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
return this.tabPresenter.setPinned(tab.id, !tab.pinned);
|
return this.tabPresenter.setPinned(tab.id as number, !tab.pinned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async duplicate() {
|
async duplicate(): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
return this.tabPresenter.duplicate(tab.id);
|
return this.tabPresenter.duplicate(tab.id as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async openPageSource() {
|
async openPageSource(): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
let url = 'view-source:' + tab.url;
|
let url = 'view-source:' + tab.url;
|
||||||
return this.tabPresenter.create(url);
|
return this.tabPresenter.create(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async openHome(newTab) {
|
async openHome(newTab: boolean): Promise<any> {
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
let urls = await this.browserSettingRepository.getHomepageUrls();
|
let urls = await this.browserSettingRepository.getHomepageUrls();
|
||||||
if (urls.length === 1 && urls[0] === 'about:home') {
|
if (urls.length === 1 && urls[0] === 'about:home') {
|
|
@ -1,23 +1,27 @@
|
||||||
import manifest from '../../../manifest.json';
|
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
import NotifyPresenter from '../presenters/NotifyPresenter';
|
import NotifyPresenter from '../presenters/NotifyPresenter';
|
||||||
|
|
||||||
export default class VersionUseCase {
|
export default class VersionUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
private notifyPresenter: NotifyPresenter;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabPresenter = new TabPresenter();
|
this.tabPresenter = new TabPresenter();
|
||||||
this.notifyPresenter = new NotifyPresenter();
|
this.notifyPresenter = new NotifyPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
notify() {
|
notify(): Promise<void> {
|
||||||
|
let manifest = browser.runtime.getManifest();
|
||||||
let title = `Vim Vixen ${manifest.version} has been installed`;
|
let title = `Vim Vixen ${manifest.version} has been installed`;
|
||||||
let message = 'Click here to see release notes';
|
let message = 'Click here to see release notes';
|
||||||
let url = this.releaseNoteUrl(manifest.version);
|
let url = this.releaseNoteUrl(manifest.version);
|
||||||
this.notifyPresenter.notify(title, message, () => {
|
return this.notifyPresenter.notify(title, message, () => {
|
||||||
this.tabPresenter.create(url);
|
this.tabPresenter.create(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseNoteUrl(version) {
|
releaseNoteUrl(version?: string): string {
|
||||||
if (version) {
|
if (version) {
|
||||||
return `https://github.com/ueokande/vim-vixen/releases/tag/${version}`;
|
return `https://github.com/ueokande/vim-vixen/releases/tag/${version}`;
|
||||||
}
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
import TabPresenter from '../presenters/TabPresenter';
|
|
||||||
|
|
||||||
const ZOOM_SETTINGS = [
|
|
||||||
0.33, 0.50, 0.66, 0.75, 0.80, 0.90, 1.00,
|
|
||||||
1.10, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class ZoomUseCase {
|
|
||||||
constructor() {
|
|
||||||
this.tabPresenter = new TabPresenter();
|
|
||||||
}
|
|
||||||
|
|
||||||
async zoomIn(tabId) {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let current = await this.tabPresenter.getZoom(tab.id);
|
|
||||||
let factor = ZOOM_SETTINGS.find(f => f > current);
|
|
||||||
if (factor) {
|
|
||||||
return this.tabPresenter.setZoom(tabId, factor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async zoomOut(tabId) {
|
|
||||||
let tab = await this.tabPresenter.getCurrent();
|
|
||||||
let current = await this.tabPresenter.getZoom(tab.id);
|
|
||||||
let factor = [].concat(ZOOM_SETTINGS).reverse().find(f => f < current);
|
|
||||||
if (factor) {
|
|
||||||
return this.tabPresenter.setZoom(tabId, factor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomNutoral(tabId) {
|
|
||||||
return this.tabPresenter.setZoom(tabId, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
39
src/background/usecases/ZoomUseCase.ts
Normal file
39
src/background/usecases/ZoomUseCase.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import TabPresenter from '../presenters/TabPresenter';
|
||||||
|
|
||||||
|
const ZOOM_SETTINGS: number[] = [
|
||||||
|
0.33, 0.50, 0.66, 0.75, 0.80, 0.90, 1.00,
|
||||||
|
1.10, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class ZoomUseCase {
|
||||||
|
private tabPresenter: TabPresenter;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.tabPresenter = new TabPresenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
async zoomIn(): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let tabId = tab.id as number;
|
||||||
|
let current = await this.tabPresenter.getZoom(tabId);
|
||||||
|
let factor = ZOOM_SETTINGS.find(f => f > current);
|
||||||
|
if (factor) {
|
||||||
|
return this.tabPresenter.setZoom(tabId as number, factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async zoomOut(): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
let tabId = tab.id as number;
|
||||||
|
let current = await this.tabPresenter.getZoom(tabId);
|
||||||
|
let factor = ZOOM_SETTINGS.slice(0).reverse().find(f => f < current);
|
||||||
|
if (factor) {
|
||||||
|
return this.tabPresenter.setZoom(tabId as number, factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async zoomNutoral(): Promise<any> {
|
||||||
|
let tab = await this.tabPresenter.getCurrent();
|
||||||
|
return this.tabPresenter.setZoom(tab.id as number, 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
const filterHttp = (items) => {
|
|
||||||
let httpsHosts = items.map(x => new URL(x.url))
|
|
||||||
.filter(x => x.protocol === 'https:')
|
|
||||||
.map(x => x.host);
|
|
||||||
httpsHosts = new Set(httpsHosts);
|
|
||||||
|
|
||||||
return items.filter((item) => {
|
|
||||||
let url = new URL(item.url);
|
|
||||||
return url.protocol === 'https:' || !httpsHosts.has(url.host);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterBlankTitle = (items) => {
|
|
||||||
return items.filter(item => item.title && item.title !== '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterByTailingSlash = (items) => {
|
|
||||||
let urls = items.map(item => new URL(item.url));
|
|
||||||
let simplePaths = urls
|
|
||||||
.filter(url => url.hash === '' && url.search === '')
|
|
||||||
.map(url => url.origin + url.pathname);
|
|
||||||
simplePaths = new Set(simplePaths);
|
|
||||||
|
|
||||||
return items.filter((item) => {
|
|
||||||
let url = new URL(item.url);
|
|
||||||
if (url.hash !== '' || url.search !== '' ||
|
|
||||||
url.pathname.slice(-1) !== '/') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !simplePaths.has(url.origin + url.pathname.slice(0, -1));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterByPathname = (items, min) => {
|
|
||||||
let hash = {};
|
|
||||||
for (let item of items) {
|
|
||||||
let url = new URL(item.url);
|
|
||||||
let pathname = url.origin + url.pathname;
|
|
||||||
if (!hash[pathname]) {
|
|
||||||
hash[pathname] = item;
|
|
||||||
} else if (hash[pathname].url.length > item.url.length) {
|
|
||||||
hash[pathname] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let filtered = Object.values(hash);
|
|
||||||
if (filtered.length < min) {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
return filtered;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterByOrigin = (items, min) => {
|
|
||||||
let hash = {};
|
|
||||||
for (let item of items) {
|
|
||||||
let origin = new URL(item.url).origin;
|
|
||||||
if (!hash[origin]) {
|
|
||||||
hash[origin] = item;
|
|
||||||
} else if (hash[origin].url.length > item.url.length) {
|
|
||||||
hash[origin] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let filtered = Object.values(hash);
|
|
||||||
if (filtered.length < min) {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
return filtered;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
filterHttp, filterBlankTitle, filterByTailingSlash,
|
|
||||||
filterByPathname, filterByOrigin
|
|
||||||
};
|
|
76
src/background/usecases/filters.ts
Normal file
76
src/background/usecases/filters.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
type Item = browser.history.HistoryItem;
|
||||||
|
|
||||||
|
const filterHttp = (items: Item[]): Item[] => {
|
||||||
|
let httpsHosts = items.map(x => new URL(x.url as string))
|
||||||
|
.filter(x => x.protocol === 'https:')
|
||||||
|
.map(x => x.host);
|
||||||
|
let hostsSet = new Set(httpsHosts);
|
||||||
|
|
||||||
|
return items.filter((item: Item) => {
|
||||||
|
let url = new URL(item.url as string);
|
||||||
|
return url.protocol === 'https:' || !hostsSet.has(url.host);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterBlankTitle = (items: Item[]): Item[] => {
|
||||||
|
return items.filter(item => item.title && item.title !== '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByTailingSlash = (items: Item[]): Item[] => {
|
||||||
|
let urls = items.map(item => new URL(item.url as string));
|
||||||
|
let simplePaths = urls
|
||||||
|
.filter(url => url.hash === '' && url.search === '')
|
||||||
|
.map(url => url.origin + url.pathname);
|
||||||
|
let pathsSet = new Set(simplePaths);
|
||||||
|
|
||||||
|
return items.filter((item) => {
|
||||||
|
let url = new URL(item.url as string);
|
||||||
|
if (url.hash !== '' || url.search !== '' ||
|
||||||
|
url.pathname.slice(-1) !== '/') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !pathsSet.has(url.origin + url.pathname.slice(0, -1));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByPathname = (items: Item[], min: number): Item[] => {
|
||||||
|
let hash: {[key: string]: Item} = {};
|
||||||
|
for (let item of items) {
|
||||||
|
let url = new URL(item.url as string);
|
||||||
|
let pathname = url.origin + url.pathname;
|
||||||
|
if (!hash[pathname]) {
|
||||||
|
hash[pathname] = item;
|
||||||
|
} else if ((hash[pathname].url as string).length >
|
||||||
|
(item.url as string).length) {
|
||||||
|
hash[pathname] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let filtered = Object.values(hash);
|
||||||
|
if (filtered.length < min) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByOrigin = (items: Item[], min: number): Item[] => {
|
||||||
|
let hash: {[key: string]: Item} = {};
|
||||||
|
for (let item of items) {
|
||||||
|
let origin = new URL(item.url as string).origin;
|
||||||
|
if (!hash[origin]) {
|
||||||
|
hash[origin] = item;
|
||||||
|
} else if ((hash[origin].url as string).length >
|
||||||
|
(item.url as string).length) {
|
||||||
|
hash[origin] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let filtered = Object.values(hash);
|
||||||
|
if (filtered.length < min) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
filterHttp, filterBlankTitle, filterByTailingSlash,
|
||||||
|
filterByPathname, filterByOrigin
|
||||||
|
};
|
|
@ -1,31 +0,0 @@
|
||||||
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];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { parseSetOption };
|
|
36
src/background/usecases/parsers.ts
Normal file
36
src/background/usecases/parsers.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as PropertyDefs from '../../shared//property-defs';
|
||||||
|
|
||||||
|
const mustNumber = (v: any): number => {
|
||||||
|
let num = Number(v);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
throw new Error('Not number: ' + v);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseSetOption = (
|
||||||
|
args: string,
|
||||||
|
): any[] => {
|
||||||
|
let [key, value]: any[] = args.split('=');
|
||||||
|
if (value === undefined) {
|
||||||
|
value = !key.startsWith('no');
|
||||||
|
key = value ? key : key.slice(2);
|
||||||
|
}
|
||||||
|
let def = PropertyDefs.defs.find(d => d.name === key);
|
||||||
|
if (!def) {
|
||||||
|
throw new Error('Unknown property: ' + key);
|
||||||
|
}
|
||||||
|
if (def.type === 'boolean' && typeof value !== 'boolean' ||
|
||||||
|
def.type !== 'boolean' && typeof value === 'boolean') {
|
||||||
|
throw new Error('Invalid argument: ' + args);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (def.type) {
|
||||||
|
case 'string': return [key, value];
|
||||||
|
case 'number': return [key, mustNumber(value)];
|
||||||
|
case 'boolean': return [key, value];
|
||||||
|
}
|
||||||
|
throw new Error('Unknown property type: ' + def.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { parseSetOption };
|
|
@ -1,40 +1,40 @@
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import actions from 'console/actions';
|
import * as actions from './index';
|
||||||
|
|
||||||
const hide = () => {
|
const hide = (): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_HIDE,
|
type: actions.CONSOLE_HIDE,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const showCommand = (text) => {
|
const showCommand = (text: string): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_SHOW_COMMAND,
|
type: actions.CONSOLE_SHOW_COMMAND,
|
||||||
text: text
|
text: text
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const showFind = () => {
|
const showFind = (): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_SHOW_FIND,
|
type: actions.CONSOLE_SHOW_FIND,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const showError = (text) => {
|
const showError = (text: string): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_SHOW_ERROR,
|
type: actions.CONSOLE_SHOW_ERROR,
|
||||||
text: text
|
text: text
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const showInfo = (text) => {
|
const showInfo = (text: string): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_SHOW_INFO,
|
type: actions.CONSOLE_SHOW_INFO,
|
||||||
text: text
|
text: text
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideCommand = () => {
|
const hideCommand = (): actions.ConsoleAction => {
|
||||||
window.top.postMessage(JSON.stringify({
|
window.top.postMessage(JSON.stringify({
|
||||||
type: messages.CONSOLE_UNFOCUS,
|
type: messages.CONSOLE_UNFOCUS,
|
||||||
}), '*');
|
}), '*');
|
||||||
|
@ -43,15 +43,17 @@ const hideCommand = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const enterCommand = async(text) => {
|
const enterCommand = async(
|
||||||
|
text: string,
|
||||||
|
): Promise<actions.ConsoleAction> => {
|
||||||
await browser.runtime.sendMessage({
|
await browser.runtime.sendMessage({
|
||||||
type: messages.CONSOLE_ENTER_COMMAND,
|
type: messages.CONSOLE_ENTER_COMMAND,
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
return hideCommand(text);
|
return hideCommand();
|
||||||
};
|
};
|
||||||
|
|
||||||
const enterFind = (text) => {
|
const enterFind = (text: string): actions.ConsoleAction => {
|
||||||
window.top.postMessage(JSON.stringify({
|
window.top.postMessage(JSON.stringify({
|
||||||
type: messages.CONSOLE_ENTER_FIND,
|
type: messages.CONSOLE_ENTER_FIND,
|
||||||
text,
|
text,
|
||||||
|
@ -59,14 +61,14 @@ const enterFind = (text) => {
|
||||||
return hideCommand();
|
return hideCommand();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setConsoleText = (consoleText) => {
|
const setConsoleText = (consoleText: string): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_SET_CONSOLE_TEXT,
|
type: actions.CONSOLE_SET_CONSOLE_TEXT,
|
||||||
consoleText,
|
consoleText,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCompletions = async(text) => {
|
const getCompletions = async(text: string): Promise<actions.ConsoleAction> => {
|
||||||
let completions = await browser.runtime.sendMessage({
|
let completions = await browser.runtime.sendMessage({
|
||||||
type: messages.CONSOLE_QUERY_COMPLETIONS,
|
type: messages.CONSOLE_QUERY_COMPLETIONS,
|
||||||
text,
|
text,
|
||||||
|
@ -78,13 +80,13 @@ const getCompletions = async(text) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const completionNext = () => {
|
const completionNext = (): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_COMPLETION_NEXT,
|
type: actions.CONSOLE_COMPLETION_NEXT,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const completionPrev = () => {
|
const completionPrev = (): actions.ConsoleAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.CONSOLE_COMPLETION_PREV,
|
type: actions.CONSOLE_COMPLETION_PREV,
|
||||||
};
|
};
|
||||||
|
@ -92,5 +94,5 @@ const completionPrev = () => {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText,
|
hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText,
|
||||||
enterCommand, enterFind, getCompletions, completionNext, completionPrev
|
enterCommand, enterFind, getCompletions, completionNext, completionPrev,
|
||||||
};
|
};
|
|
@ -1,13 +0,0 @@
|
||||||
export default {
|
|
||||||
// console commands
|
|
||||||
CONSOLE_HIDE: 'console.hide',
|
|
||||||
CONSOLE_SHOW_COMMAND: 'console.show.command',
|
|
||||||
CONSOLE_SHOW_ERROR: 'console.show.error',
|
|
||||||
CONSOLE_SHOW_INFO: 'console.show.info',
|
|
||||||
CONSOLE_HIDE_COMMAND: 'console.hide.command',
|
|
||||||
CONSOLE_SET_CONSOLE_TEXT: 'console.set.command',
|
|
||||||
CONSOLE_SET_COMPLETIONS: 'console.set.completions',
|
|
||||||
CONSOLE_COMPLETION_NEXT: 'console.completion.next',
|
|
||||||
CONSOLE_COMPLETION_PREV: 'console.completion.prev',
|
|
||||||
CONSOLE_SHOW_FIND: 'console.show.find',
|
|
||||||
};
|
|
63
src/console/actions/index.ts
Normal file
63
src/console/actions/index.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// console commands
|
||||||
|
export const CONSOLE_HIDE = 'console.hide';
|
||||||
|
export const CONSOLE_SHOW_COMMAND = 'console.show.command';
|
||||||
|
export const CONSOLE_SHOW_ERROR = 'console.show.error';
|
||||||
|
export const CONSOLE_SHOW_INFO = 'console.show.info';
|
||||||
|
export const CONSOLE_HIDE_COMMAND = 'console.hide.command';
|
||||||
|
export const CONSOLE_SET_CONSOLE_TEXT = 'console.set.command';
|
||||||
|
export const CONSOLE_SET_COMPLETIONS = 'console.set.completions';
|
||||||
|
export const CONSOLE_COMPLETION_NEXT = 'console.completion.next';
|
||||||
|
export const CONSOLE_COMPLETION_PREV = 'console.completion.prev';
|
||||||
|
export const CONSOLE_SHOW_FIND = 'console.show.find';
|
||||||
|
|
||||||
|
interface HideAction {
|
||||||
|
type: typeof CONSOLE_HIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShowCommand {
|
||||||
|
type: typeof CONSOLE_SHOW_COMMAND;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShowFindAction {
|
||||||
|
type: typeof CONSOLE_SHOW_FIND;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShowErrorAction {
|
||||||
|
type: typeof CONSOLE_SHOW_ERROR;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShowInfoAction {
|
||||||
|
type: typeof CONSOLE_SHOW_INFO;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HideCommandAction {
|
||||||
|
type: typeof CONSOLE_HIDE_COMMAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetConsoleTextAction {
|
||||||
|
type: typeof CONSOLE_SET_CONSOLE_TEXT;
|
||||||
|
consoleText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetCompletionsAction {
|
||||||
|
type: typeof CONSOLE_SET_COMPLETIONS;
|
||||||
|
completions: any[];
|
||||||
|
completionSource: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompletionNextAction {
|
||||||
|
type: typeof CONSOLE_COMPLETION_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompletionPrevAction {
|
||||||
|
type: typeof CONSOLE_COMPLETION_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConsoleAction =
|
||||||
|
HideAction | ShowCommand | ShowFindAction | ShowErrorAction |
|
||||||
|
ShowInfoAction | HideCommandAction | SetConsoleTextAction |
|
||||||
|
SetCompletionsAction | CompletionNextAction | CompletionPrevAction;
|
||||||
|
|
|
@ -1,26 +1,40 @@
|
||||||
import './console.scss';
|
import './console.scss';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Input from './console/Input';
|
import Input from './console/Input';
|
||||||
import Completion from './console/Completion';
|
import Completion from './console/Completion';
|
||||||
import Message from './console/Message';
|
import Message from './console/Message';
|
||||||
import * as consoleActions from '../../console/actions/console';
|
import * as consoleActions from '../../console/actions/console';
|
||||||
|
import { State as AppState } from '../reducers';
|
||||||
|
|
||||||
const COMPLETION_MAX_ITEMS = 33;
|
const COMPLETION_MAX_ITEMS = 33;
|
||||||
|
|
||||||
class Console extends React.Component {
|
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||||
|
interface DispatchProps {
|
||||||
|
dispatch: (action: any) => void,
|
||||||
|
}
|
||||||
|
type Props = StateProps & DispatchProps
|
||||||
|
|
||||||
|
class Console extends React.Component<Props> {
|
||||||
|
private input: React.RefObject<Input>;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.input = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
onBlur() {
|
onBlur() {
|
||||||
if (this.props.mode === 'command' || this.props.mode === 'find') {
|
if (this.props.mode === 'command' || this.props.mode === 'find') {
|
||||||
return this.props.dispatch(consoleActions.hideCommand());
|
return this.props.dispatch(consoleActions.hideCommand());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doEnter(e) {
|
doEnter(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let value = e.target.value;
|
let value = (e.target as HTMLInputElement).value;
|
||||||
if (this.props.mode === 'command') {
|
if (this.props.mode === 'command') {
|
||||||
return this.props.dispatch(consoleActions.enterCommand(value));
|
return this.props.dispatch(consoleActions.enterCommand(value));
|
||||||
} else if (this.props.mode === 'find') {
|
} else if (this.props.mode === 'find') {
|
||||||
|
@ -28,28 +42,25 @@ class Console extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNext(e) {
|
selectNext(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
this.props.dispatch(consoleActions.completionNext());
|
this.props.dispatch(consoleActions.completionNext());
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPrev(e) {
|
selectPrev(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
this.props.dispatch(consoleActions.completionPrev());
|
this.props.dispatch(consoleActions.completionPrev());
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(e) {
|
onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) {
|
switch (e.key) {
|
||||||
this.props.dispatch(consoleActions.hideCommand());
|
case 'Escape':
|
||||||
}
|
|
||||||
switch (e.keyCode) {
|
|
||||||
case KeyboardEvent.DOM_VK_ESCAPE:
|
|
||||||
return this.props.dispatch(consoleActions.hideCommand());
|
return this.props.dispatch(consoleActions.hideCommand());
|
||||||
case KeyboardEvent.DOM_VK_RETURN:
|
case 'Enter':
|
||||||
return this.doEnter(e);
|
return this.doEnter(e);
|
||||||
case KeyboardEvent.DOM_VK_TAB:
|
case 'Tab':
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
this.props.dispatch(consoleActions.completionPrev());
|
this.props.dispatch(consoleActions.completionPrev());
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,22 +69,22 @@ class Console extends React.Component {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
break;
|
break;
|
||||||
case KeyboardEvent.DOM_VK_OPEN_BRACKET:
|
case '[':
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
return this.props.dispatch(consoleActions.hideCommand());
|
return this.props.dispatch(consoleActions.hideCommand());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KeyboardEvent.DOM_VK_M:
|
case 'm':
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
return this.doEnter(e);
|
return this.doEnter(e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KeyboardEvent.DOM_VK_N:
|
case 'n':
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
this.selectNext(e);
|
this.selectNext(e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KeyboardEvent.DOM_VK_P:
|
case 'p':
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
this.selectPrev(e);
|
this.selectPrev(e);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +92,7 @@ class Console extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(e) {
|
onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
let text = e.target.value;
|
let text = e.target.value;
|
||||||
this.props.dispatch(consoleActions.setConsoleText(text));
|
this.props.dispatch(consoleActions.setConsoleText(text));
|
||||||
if (this.props.mode === 'command') {
|
if (this.props.mode === 'command') {
|
||||||
|
@ -90,10 +101,7 @@ class Console extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
if (!this.input) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (prevProps.mode !== 'command' && this.props.mode === 'command') {
|
if (prevProps.mode !== 'command' && this.props.mode === 'command') {
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
consoleActions.getCompletions(this.props.consoleText));
|
consoleActions.getCompletions(this.props.consoleText));
|
||||||
|
@ -114,7 +122,7 @@ class Console extends React.Component {
|
||||||
select={this.props.select}
|
select={this.props.select}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
ref={(c) => { this.input = c; }}
|
ref={this.input}
|
||||||
mode={this.props.mode}
|
mode={this.props.mode}
|
||||||
onBlur={this.onBlur.bind(this)}
|
onBlur={this.onBlur.bind(this)}
|
||||||
onKeyDown={this.onKeyDown.bind(this)}
|
onKeyDown={this.onKeyDown.bind(this)}
|
||||||
|
@ -134,16 +142,14 @@ class Console extends React.Component {
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
window.focus();
|
window.focus();
|
||||||
this.input.focus();
|
if (this.input.current) {
|
||||||
|
this.input.current.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.propTypes = {
|
const mapStateToProps = (state: AppState) => ({ ...state });
|
||||||
mode: PropTypes.string,
|
|
||||||
consoleText: PropTypes.string,
|
|
||||||
messageText: PropTypes.string,
|
|
||||||
children: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => state;
|
export default connect(
|
||||||
export default connect(mapStateToProps)(Console);
|
mapStateToProps,
|
||||||
|
)(Console);
|
|
@ -1,15 +1,36 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import CompletionItem from './CompletionItem';
|
import CompletionItem from './CompletionItem';
|
||||||
import CompletionTitle from './CompletionTitle';
|
import CompletionTitle from './CompletionTitle';
|
||||||
|
|
||||||
class Completion extends React.Component {
|
interface Item {
|
||||||
constructor() {
|
icon?: string;
|
||||||
super();
|
caption?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Group {
|
||||||
|
name: string;
|
||||||
|
items: Item[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
select: number;
|
||||||
|
size: number;
|
||||||
|
completions: Group[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
viewOffset: number;
|
||||||
|
select: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Completion extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
this.state = { viewOffset: 0, select: -1 };
|
this.state = { viewOffset: 0, select: -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||||
if (prevState.select === nextProps.select) {
|
if (prevState.select === nextProps.select) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +45,7 @@ class Completion extends React.Component {
|
||||||
}
|
}
|
||||||
index += g.items.length;
|
index += g.items.length;
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let viewOffset = 0;
|
let viewOffset = 0;
|
||||||
|
@ -70,17 +92,4 @@ class Completion extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Completion.propTypes = {
|
|
||||||
select: PropTypes.number,
|
|
||||||
size: PropTypes.number,
|
|
||||||
completions: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
name: PropTypes.string,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
icon: PropTypes.string,
|
|
||||||
caption: PropTypes.string,
|
|
||||||
url: PropTypes.string,
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Completion;
|
export default Completion;
|
|
@ -1,7 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const CompletionItem = (props) => {
|
interface Props {
|
||||||
|
highlight: boolean;
|
||||||
|
caption?: string;
|
||||||
|
url?: string;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompletionItem = (props: Props) => {
|
||||||
let className = 'vimvixen-console-completion-item';
|
let className = 'vimvixen-console-completion-item';
|
||||||
if (props.highlight) {
|
if (props.highlight) {
|
||||||
className += ' vimvixen-completion-selected';
|
className += ' vimvixen-completion-selected';
|
|
@ -1,14 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const CompletionTitle = (props) => {
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompletionTitle = (props: Props) => {
|
||||||
return <li className='vimvixen-console-completion-title'>
|
return <li className='vimvixen-console-completion-title'>
|
||||||
{props.title}
|
{props.title}
|
||||||
</li>;
|
</li>;
|
||||||
};
|
};
|
||||||
|
|
||||||
CompletionTitle.propTypes = {
|
|
||||||
title: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CompletionTitle;
|
export default CompletionTitle;
|
|
@ -1,9 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
class Input extends React.Component {
|
interface Props {
|
||||||
|
mode: string;
|
||||||
|
value: string;
|
||||||
|
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||||
|
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Input extends React.Component<Props> {
|
||||||
|
private input: React.RefObject<HTMLInputElement>;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.input = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.input.focus();
|
if (this.input.current) {
|
||||||
|
this.input.current.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -21,7 +38,7 @@ class Input extends React.Component {
|
||||||
</i>
|
</i>
|
||||||
<input
|
<input
|
||||||
className='vimvixen-console-command-input'
|
className='vimvixen-console-command-input'
|
||||||
ref={(c) => { this.input = c; }}
|
ref={this.input}
|
||||||
onBlur={this.props.onBlur}
|
onBlur={this.props.onBlur}
|
||||||
onKeyDown={this.props.onKeyDown}
|
onKeyDown={this.props.onKeyDown}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
|
@ -32,12 +49,4 @@ class Input extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Input.propTypes = {
|
|
||||||
mode: PropTypes.string,
|
|
||||||
value: PropTypes.string,
|
|
||||||
onBlur: PropTypes.func,
|
|
||||||
onKeyDown: PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Input;
|
export default Input;
|
|
@ -1,7 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const Message = (props) => {
|
interface Props {
|
||||||
|
mode: string;
|
||||||
|
children: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Message = (props: Props) => {
|
||||||
switch (props.mode) {
|
switch (props.mode) {
|
||||||
case 'error':
|
case 'error':
|
||||||
return (
|
return (
|
||||||
|
@ -16,10 +20,7 @@ const Message = (props) => {
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
return null;
|
||||||
|
|
||||||
Message.propTypes = {
|
|
||||||
children: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Message;
|
export default Message;
|
|
@ -1,8 +1,8 @@
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../shared/messages';
|
||||||
import reducers from 'console/reducers';
|
import reducers from './reducers';
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
import promise from 'redux-promise';
|
import promise from 'redux-promise';
|
||||||
import * as consoleActions from 'console/actions/console';
|
import * as consoleActions from './actions/console';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import Console from './components/Console';
|
import Console from './components/Console';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -22,21 +22,22 @@ window.addEventListener('load', () => {
|
||||||
wrapper);
|
wrapper);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onMessage = (message) => {
|
const onMessage = (message: any): any => {
|
||||||
switch (message.type) {
|
let msg = messages.valueOf(message);
|
||||||
|
switch (msg.type) {
|
||||||
case messages.CONSOLE_SHOW_COMMAND:
|
case messages.CONSOLE_SHOW_COMMAND:
|
||||||
return store.dispatch(consoleActions.showCommand(message.command));
|
return store.dispatch(consoleActions.showCommand(msg.command));
|
||||||
case messages.CONSOLE_SHOW_FIND:
|
case messages.CONSOLE_SHOW_FIND:
|
||||||
return store.dispatch(consoleActions.showFind());
|
return store.dispatch(consoleActions.showFind());
|
||||||
case messages.CONSOLE_SHOW_ERROR:
|
case messages.CONSOLE_SHOW_ERROR:
|
||||||
return store.dispatch(consoleActions.showError(message.text));
|
return store.dispatch(consoleActions.showError(msg.text));
|
||||||
case messages.CONSOLE_SHOW_INFO:
|
case messages.CONSOLE_SHOW_INFO:
|
||||||
return store.dispatch(consoleActions.showInfo(message.text));
|
return store.dispatch(consoleActions.showInfo(msg.text));
|
||||||
case messages.CONSOLE_HIDE:
|
case messages.CONSOLE_HIDE:
|
||||||
return store.dispatch(consoleActions.hide());
|
return store.dispatch(consoleActions.hide());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener(onMessage);
|
browser.runtime.onMessage.addListener(onMessage);
|
||||||
let port = browser.runtime.connect({ name: 'vimvixen-console' });
|
let port = browser.runtime.connect(undefined, { name: 'vimvixen-console' });
|
||||||
port.onMessage.addListener(onMessage);
|
port.onMessage.addListener(onMessage);
|
|
@ -1,4 +1,14 @@
|
||||||
import actions from 'console/actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
mode: string;
|
||||||
|
messageText: string;
|
||||||
|
consoleText: string;
|
||||||
|
completionSource: string;
|
||||||
|
completions: any[],
|
||||||
|
select: number;
|
||||||
|
viewIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
mode: '',
|
mode: '',
|
||||||
|
@ -10,7 +20,7 @@ const defaultState = {
|
||||||
viewIndex: 0,
|
viewIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextSelection = (state) => {
|
const nextSelection = (state: State): number => {
|
||||||
if (state.completions.length === 0) {
|
if (state.completions.length === 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +37,7 @@ const nextSelection = (state) => {
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const prevSelection = (state) => {
|
const prevSelection = (state: State): number => {
|
||||||
let length = state.completions
|
let length = state.completions
|
||||||
.map(g => g.items.length)
|
.map(g => g.items.length)
|
||||||
.reduce((x, y) => x + y);
|
.reduce((x, y) => x + y);
|
||||||
|
@ -37,7 +47,7 @@ const prevSelection = (state) => {
|
||||||
return state.select - 1;
|
return state.select - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextConsoleText = (completions, select, defaults) => {
|
const nextConsoleText = (completions: any[], select: number, defaults: any) => {
|
||||||
if (select < 0) {
|
if (select < 0) {
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +56,10 @@ const nextConsoleText = (completions, select, defaults) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line max-lines-per-function
|
// eslint-disable-next-line max-lines-per-function
|
||||||
export default function reducer(state = defaultState, action = {}) {
|
export default function reducer(
|
||||||
|
state: State = defaultState,
|
||||||
|
action: actions.ConsoleAction,
|
||||||
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.CONSOLE_HIDE:
|
case actions.CONSOLE_HIDE:
|
||||||
return { ...state,
|
return { ...state,
|
6
src/content/Mark.ts
Normal file
6
src/content/Mark.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export default interface Mark {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
32
src/content/MessageListener.ts
Normal file
32
src/content/MessageListener.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Message, valueOf } from '../shared/messages';
|
||||||
|
|
||||||
|
export type WebMessageSender = Window | MessagePort | ServiceWorker | null;
|
||||||
|
export type WebExtMessageSender = browser.runtime.MessageSender;
|
||||||
|
|
||||||
|
export default class MessageListener {
|
||||||
|
onWebMessage(
|
||||||
|
listener: (msg: Message, sender: WebMessageSender) => void,
|
||||||
|
) {
|
||||||
|
window.addEventListener('message', (event: MessageEvent) => {
|
||||||
|
let sender = event.source;
|
||||||
|
let message = null;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(event.data);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore unexpected message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listener(message, sender);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackgroundMessage(
|
||||||
|
listener: (msg: Message, sender: WebExtMessageSender) => any,
|
||||||
|
) {
|
||||||
|
browser.runtime.onMessage.addListener(
|
||||||
|
(msg: any, sender: WebExtMessageSender) => {
|
||||||
|
listener(valueOf(msg), sender);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
import messages from 'shared/messages';
|
|
||||||
import actions from 'content/actions';
|
|
||||||
|
|
||||||
const enable = () => setEnabled(true);
|
|
||||||
|
|
||||||
const disable = () => setEnabled(false);
|
|
||||||
|
|
||||||
const setEnabled = async(enabled) => {
|
|
||||||
await browser.runtime.sendMessage({
|
|
||||||
type: messages.ADDON_ENABLED_RESPONSE,
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
type: actions.ADDON_SET_ENABLED,
|
|
||||||
enabled,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export { enable, disable, setEnabled };
|
|
19
src/content/actions/addon.ts
Normal file
19
src/content/actions/addon.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
import * as actions from './index';
|
||||||
|
|
||||||
|
const enable = (): Promise<actions.AddonAction> => setEnabled(true);
|
||||||
|
|
||||||
|
const disable = (): Promise<actions.AddonAction> => setEnabled(false);
|
||||||
|
|
||||||
|
const setEnabled = async(enabled: boolean): Promise<actions.AddonAction> => {
|
||||||
|
await browser.runtime.sendMessage({
|
||||||
|
type: messages.ADDON_ENABLED_RESPONSE,
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
type: actions.ADDON_SET_ENABLED,
|
||||||
|
enabled,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { enable, disable, setEnabled };
|
|
@ -1,68 +0,0 @@
|
||||||
//
|
|
||||||
// window.find(aString, aCaseSensitive, aBackwards, aWrapAround,
|
|
||||||
// aWholeWord, aSearchInFrames);
|
|
||||||
//
|
|
||||||
// NOTE: window.find is not standard API
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/find
|
|
||||||
|
|
||||||
import messages from 'shared/messages';
|
|
||||||
import actions from 'content/actions';
|
|
||||||
import * as consoleFrames from '../console-frames';
|
|
||||||
|
|
||||||
const find = (string, backwards) => {
|
|
||||||
let caseSensitive = false;
|
|
||||||
let wrapScan = true;
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
|
|
||||||
// because of same origin policy
|
|
||||||
let found = window.find(string, caseSensitive, backwards, wrapScan);
|
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
window.getSelection().removeAllRanges();
|
|
||||||
return window.find(string, caseSensitive, backwards, wrapScan);
|
|
||||||
};
|
|
||||||
|
|
||||||
const findNext = async(currentKeyword, reset, backwards) => {
|
|
||||||
if (reset) {
|
|
||||||
window.getSelection().removeAllRanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
let keyword = currentKeyword;
|
|
||||||
if (currentKeyword) {
|
|
||||||
browser.runtime.sendMessage({
|
|
||||||
type: messages.FIND_SET_KEYWORD,
|
|
||||||
keyword: currentKeyword,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
keyword = await browser.runtime.sendMessage({
|
|
||||||
type: messages.FIND_GET_KEYWORD,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!keyword) {
|
|
||||||
return consoleFrames.postError('No previous search keywords');
|
|
||||||
}
|
|
||||||
let found = find(keyword, backwards);
|
|
||||||
if (found) {
|
|
||||||
consoleFrames.postInfo('Pattern found: ' + keyword);
|
|
||||||
} else {
|
|
||||||
consoleFrames.postError('Pattern not found: ' + keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: actions.FIND_SET_KEYWORD,
|
|
||||||
keyword,
|
|
||||||
found,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const next = (currentKeyword, reset) => {
|
|
||||||
return findNext(currentKeyword, reset, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const prev = (currentKeyword, reset) => {
|
|
||||||
return findNext(currentKeyword, reset, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { next, prev };
|
|
100
src/content/actions/find.ts
Normal file
100
src/content/actions/find.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
//
|
||||||
|
// window.find(aString, aCaseSensitive, aBackwards, aWrapAround,
|
||||||
|
// aWholeWord, aSearchInFrames);
|
||||||
|
//
|
||||||
|
// NOTE: window.find is not standard API
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/find
|
||||||
|
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
import * as actions from './index';
|
||||||
|
import * as consoleFrames from '../console-frames';
|
||||||
|
|
||||||
|
interface MyWindow extends Window {
|
||||||
|
find(
|
||||||
|
aString: string,
|
||||||
|
aCaseSensitive?: boolean,
|
||||||
|
aBackwards?: boolean,
|
||||||
|
aWrapAround?: boolean,
|
||||||
|
aWholeWord?: boolean,
|
||||||
|
aSearchInFrames?: boolean,
|
||||||
|
aShowDialog?: boolean): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-var, vars-on-top, init-declarations
|
||||||
|
declare var window: MyWindow;
|
||||||
|
|
||||||
|
const find = (str: string, backwards: boolean): boolean => {
|
||||||
|
let caseSensitive = false;
|
||||||
|
let wrapScan = true;
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
|
||||||
|
// because of same origin policy
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-extra-parens
|
||||||
|
let found = window.find(str, caseSensitive, backwards, wrapScan);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
let sel = window.getSelection();
|
||||||
|
if (sel) {
|
||||||
|
sel.removeAllRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-extra-parens
|
||||||
|
return window.find(str, caseSensitive, backwards, wrapScan);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-statements
|
||||||
|
const findNext = async(
|
||||||
|
currentKeyword: string, reset: boolean, backwards: boolean,
|
||||||
|
): Promise<actions.FindAction> => {
|
||||||
|
if (reset) {
|
||||||
|
let sel = window.getSelection();
|
||||||
|
if (sel) {
|
||||||
|
sel.removeAllRanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyword = currentKeyword;
|
||||||
|
if (currentKeyword) {
|
||||||
|
browser.runtime.sendMessage({
|
||||||
|
type: messages.FIND_SET_KEYWORD,
|
||||||
|
keyword: currentKeyword,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
keyword = await browser.runtime.sendMessage({
|
||||||
|
type: messages.FIND_GET_KEYWORD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!keyword) {
|
||||||
|
await consoleFrames.postError('No previous search keywords');
|
||||||
|
return { type: actions.NOOP };
|
||||||
|
}
|
||||||
|
let found = find(keyword, backwards);
|
||||||
|
if (found) {
|
||||||
|
consoleFrames.postInfo('Pattern found: ' + keyword);
|
||||||
|
} else {
|
||||||
|
consoleFrames.postError('Pattern not found: ' + keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: actions.FIND_SET_KEYWORD,
|
||||||
|
keyword,
|
||||||
|
found,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const next = (
|
||||||
|
currentKeyword: string, reset: boolean,
|
||||||
|
): Promise<actions.FindAction> => {
|
||||||
|
return findNext(currentKeyword, reset, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prev = (
|
||||||
|
currentKeyword: string, reset: boolean,
|
||||||
|
): Promise<actions.FindAction> => {
|
||||||
|
return findNext(currentKeyword, reset, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { next, prev };
|
|
@ -1,6 +1,8 @@
|
||||||
import actions from 'content/actions';
|
import * as actions from './index';
|
||||||
|
|
||||||
const enable = (newTab, background) => {
|
const enable = (
|
||||||
|
newTab: boolean, background: boolean,
|
||||||
|
): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_ENABLE,
|
type: actions.FOLLOW_CONTROLLER_ENABLE,
|
||||||
newTab,
|
newTab,
|
||||||
|
@ -8,20 +10,20 @@ const enable = (newTab, background) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const disable = () => {
|
const disable = (): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_DISABLE,
|
type: actions.FOLLOW_CONTROLLER_DISABLE,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyPress = (key) => {
|
const keyPress = (key: string): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_KEY_PRESS,
|
type: actions.FOLLOW_CONTROLLER_KEY_PRESS,
|
||||||
key: key
|
key: key
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const backspace = () => {
|
const backspace = (): actions.FollowAction => {
|
||||||
return {
|
return {
|
||||||
type: actions.FOLLOW_CONTROLLER_BACKSPACE,
|
type: actions.FOLLOW_CONTROLLER_BACKSPACE,
|
||||||
};
|
};
|
|
@ -1,31 +0,0 @@
|
||||||
export default {
|
|
||||||
// Enable/disable
|
|
||||||
ADDON_SET_ENABLED: 'addon.set.enabled',
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
SETTING_SET: 'setting.set',
|
|
||||||
|
|
||||||
// User input
|
|
||||||
INPUT_KEY_PRESS: 'input.key.press',
|
|
||||||
INPUT_CLEAR_KEYS: 'input.clear.keys',
|
|
||||||
|
|
||||||
// Completion
|
|
||||||
COMPLETION_SET_ITEMS: 'completion.set.items',
|
|
||||||
COMPLETION_SELECT_NEXT: 'completions.select.next',
|
|
||||||
COMPLETION_SELECT_PREV: 'completions.select.prev',
|
|
||||||
|
|
||||||
// Follow
|
|
||||||
FOLLOW_CONTROLLER_ENABLE: 'follow.controller.enable',
|
|
||||||
FOLLOW_CONTROLLER_DISABLE: 'follow.controller.disable',
|
|
||||||
FOLLOW_CONTROLLER_KEY_PRESS: 'follow.controller.key.press',
|
|
||||||
FOLLOW_CONTROLLER_BACKSPACE: 'follow.controller.backspace',
|
|
||||||
|
|
||||||
// Find
|
|
||||||
FIND_SET_KEYWORD: 'find.set.keyword',
|
|
||||||
|
|
||||||
// Mark
|
|
||||||
MARK_START_SET: 'mark.start.set',
|
|
||||||
MARK_START_JUMP: 'mark.start.jump',
|
|
||||||
MARK_CANCEL: 'mark.cancel',
|
|
||||||
MARK_SET_LOCAL: 'mark.set.local',
|
|
||||||
};
|
|
122
src/content/actions/index.ts
Normal file
122
src/content/actions/index.ts
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import Redux from 'redux';
|
||||||
|
import Settings from '../../shared/Settings';
|
||||||
|
import * as keyUtils from '../../shared/utils/keys';
|
||||||
|
|
||||||
|
// Enable/disable
|
||||||
|
export const ADDON_SET_ENABLED = 'addon.set.enabled';
|
||||||
|
|
||||||
|
// Find
|
||||||
|
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
export const SETTING_SET = 'setting.set';
|
||||||
|
|
||||||
|
// User input
|
||||||
|
export const INPUT_KEY_PRESS = 'input.key.press';
|
||||||
|
export const INPUT_CLEAR_KEYS = 'input.clear.keys';
|
||||||
|
|
||||||
|
// Completion
|
||||||
|
export const COMPLETION_SET_ITEMS = 'completion.set.items';
|
||||||
|
export const COMPLETION_SELECT_NEXT = 'completions.select.next';
|
||||||
|
export const COMPLETION_SELECT_PREV = 'completions.select.prev';
|
||||||
|
|
||||||
|
// Follow
|
||||||
|
export const FOLLOW_CONTROLLER_ENABLE = 'follow.controller.enable';
|
||||||
|
export const FOLLOW_CONTROLLER_DISABLE = 'follow.controller.disable';
|
||||||
|
export const FOLLOW_CONTROLLER_KEY_PRESS = 'follow.controller.key.press';
|
||||||
|
export const FOLLOW_CONTROLLER_BACKSPACE = 'follow.controller.backspace';
|
||||||
|
|
||||||
|
// Mark
|
||||||
|
export const MARK_START_SET = 'mark.start.set';
|
||||||
|
export const MARK_START_JUMP = 'mark.start.jump';
|
||||||
|
export const MARK_CANCEL = 'mark.cancel';
|
||||||
|
export const MARK_SET_LOCAL = 'mark.set.local';
|
||||||
|
|
||||||
|
export const NOOP = 'noop';
|
||||||
|
|
||||||
|
export interface AddonSetEnabledAction extends Redux.Action {
|
||||||
|
type: typeof ADDON_SET_ENABLED;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindSetKeywordAction extends Redux.Action {
|
||||||
|
type: typeof FIND_SET_KEYWORD;
|
||||||
|
keyword: string;
|
||||||
|
found: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingSetAction extends Redux.Action {
|
||||||
|
type: typeof SETTING_SET;
|
||||||
|
settings: Settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputKeyPressAction extends Redux.Action {
|
||||||
|
type: typeof INPUT_KEY_PRESS;
|
||||||
|
key: keyUtils.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputClearKeysAction extends Redux.Action {
|
||||||
|
type: typeof INPUT_CLEAR_KEYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerEnableAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_ENABLE;
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerDisableAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_DISABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerKeyPressAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_KEY_PRESS;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowControllerBackspaceAction extends Redux.Action {
|
||||||
|
type: typeof FOLLOW_CONTROLLER_BACKSPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkStartSetAction extends Redux.Action {
|
||||||
|
type: typeof MARK_START_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkStartJumpAction extends Redux.Action {
|
||||||
|
type: typeof MARK_START_JUMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkCancelAction extends Redux.Action {
|
||||||
|
type: typeof MARK_CANCEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkSetLocalAction extends Redux.Action {
|
||||||
|
type: typeof MARK_SET_LOCAL;
|
||||||
|
key: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoopAction extends Redux.Action {
|
||||||
|
type: typeof NOOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddonAction = AddonSetEnabledAction;
|
||||||
|
export type FindAction = FindSetKeywordAction | NoopAction;
|
||||||
|
export type SettingAction = SettingSetAction;
|
||||||
|
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
||||||
|
export type FollowAction =
|
||||||
|
FollowControllerEnableAction | FollowControllerDisableAction |
|
||||||
|
FollowControllerKeyPressAction | FollowControllerBackspaceAction;
|
||||||
|
export type MarkAction =
|
||||||
|
MarkStartSetAction | MarkStartJumpAction |
|
||||||
|
MarkCancelAction | MarkSetLocalAction | NoopAction;
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
AddonAction |
|
||||||
|
FindAction |
|
||||||
|
SettingAction |
|
||||||
|
InputAction |
|
||||||
|
FollowAction |
|
||||||
|
MarkAction |
|
||||||
|
NoopAction;
|
|
@ -1,16 +0,0 @@
|
||||||
import actions from 'content/actions';
|
|
||||||
|
|
||||||
const keyPress = (key) => {
|
|
||||||
return {
|
|
||||||
type: actions.INPUT_KEY_PRESS,
|
|
||||||
key,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearKeys = () => {
|
|
||||||
return {
|
|
||||||
type: actions.INPUT_CLEAR_KEYS
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export { keyPress, clearKeys };
|
|
17
src/content/actions/input.ts
Normal file
17
src/content/actions/input.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import * as actions from './index';
|
||||||
|
import * as keyUtils from '../../shared/utils/keys';
|
||||||
|
|
||||||
|
const keyPress = (key: keyUtils.Key): actions.InputAction => {
|
||||||
|
return {
|
||||||
|
type: actions.INPUT_KEY_PRESS,
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearKeys = (): actions.InputAction => {
|
||||||
|
return {
|
||||||
|
type: actions.INPUT_CLEAR_KEYS
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { keyPress, clearKeys };
|
|
@ -1,46 +0,0 @@
|
||||||
import actions from 'content/actions';
|
|
||||||
import messages from 'shared/messages';
|
|
||||||
|
|
||||||
const startSet = () => {
|
|
||||||
return { type: actions.MARK_START_SET };
|
|
||||||
};
|
|
||||||
|
|
||||||
const startJump = () => {
|
|
||||||
return { type: actions.MARK_START_JUMP };
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancel = () => {
|
|
||||||
return { type: actions.MARK_CANCEL };
|
|
||||||
};
|
|
||||||
|
|
||||||
const setLocal = (key, x, y) => {
|
|
||||||
return {
|
|
||||||
type: actions.MARK_SET_LOCAL,
|
|
||||||
key,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const setGlobal = (key, x, y) => {
|
|
||||||
browser.runtime.sendMessage({
|
|
||||||
type: messages.MARK_SET_GLOBAL,
|
|
||||||
key,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
});
|
|
||||||
return { type: '' };
|
|
||||||
};
|
|
||||||
|
|
||||||
const jumpGlobal = (key) => {
|
|
||||||
browser.runtime.sendMessage({
|
|
||||||
type: messages.MARK_JUMP_GLOBAL,
|
|
||||||
key,
|
|
||||||
});
|
|
||||||
return { type: '' };
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
startSet, startJump, cancel, setLocal,
|
|
||||||
setGlobal, jumpGlobal,
|
|
||||||
};
|
|
46
src/content/actions/mark.ts
Normal file
46
src/content/actions/mark.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import * as actions from './index';
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
|
const startSet = (): actions.MarkAction => {
|
||||||
|
return { type: actions.MARK_START_SET };
|
||||||
|
};
|
||||||
|
|
||||||
|
const startJump = (): actions.MarkAction => {
|
||||||
|
return { type: actions.MARK_START_JUMP };
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = (): actions.MarkAction => {
|
||||||
|
return { type: actions.MARK_CANCEL };
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLocal = (key: string, x: number, y: number): actions.MarkAction => {
|
||||||
|
return {
|
||||||
|
type: actions.MARK_SET_LOCAL,
|
||||||
|
key,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const setGlobal = (key: string, x: number, y: number): actions.MarkAction => {
|
||||||
|
browser.runtime.sendMessage({
|
||||||
|
type: messages.MARK_SET_GLOBAL,
|
||||||
|
key,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
});
|
||||||
|
return { type: actions.NOOP };
|
||||||
|
};
|
||||||
|
|
||||||
|
const jumpGlobal = (key: string): actions.MarkAction => {
|
||||||
|
browser.runtime.sendMessage({
|
||||||
|
type: messages.MARK_JUMP_GLOBAL,
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
return { type: actions.NOOP };
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
startSet, startJump, cancel, setLocal,
|
||||||
|
setGlobal, jumpGlobal,
|
||||||
|
};
|
|
@ -1,18 +1,21 @@
|
||||||
import operations from 'shared/operations';
|
import * as operations from '../../shared/operations';
|
||||||
import messages from 'shared/messages';
|
import * as actions from './index';
|
||||||
import * as scrolls from 'content/scrolls';
|
import * as messages from '../../shared/messages';
|
||||||
import * as navigates from 'content/navigates';
|
import * as scrolls from '../scrolls';
|
||||||
import * as focuses from 'content/focuses';
|
import * as navigates from '../navigates';
|
||||||
import * as urls from 'content/urls';
|
import * as focuses from '../focuses';
|
||||||
import * as consoleFrames from 'content/console-frames';
|
import * as urls from '../urls';
|
||||||
|
import * as consoleFrames from '../console-frames';
|
||||||
import * as addonActions from './addon';
|
import * as addonActions from './addon';
|
||||||
import * as markActions from './mark';
|
import * as markActions from './mark';
|
||||||
import * as properties from 'shared/settings/properties';
|
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
const exec = (operation, settings, addonEnabled) => {
|
const exec = (
|
||||||
let smoothscroll = settings.properties.smoothscroll ||
|
operation: operations.Operation,
|
||||||
properties.defaults.smoothscroll;
|
settings: any,
|
||||||
|
addonEnabled: boolean,
|
||||||
|
): Promise<actions.Action> | actions.Action => {
|
||||||
|
let smoothscroll = settings.properties.smoothscroll;
|
||||||
switch (operation.type) {
|
switch (operation.type) {
|
||||||
case operations.ADDON_ENABLE:
|
case operations.ADDON_ENABLE:
|
||||||
return addonActions.enable();
|
return addonActions.enable();
|
||||||
|
@ -98,7 +101,7 @@ const exec = (operation, settings, addonEnabled) => {
|
||||||
operation,
|
operation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { type: '' };
|
return { type: actions.NOOP };
|
||||||
};
|
};
|
||||||
|
|
||||||
export { exec };
|
export { exec };
|
|
@ -1,37 +0,0 @@
|
||||||
import actions from 'content/actions';
|
|
||||||
import * as keyUtils from 'shared/utils/keys';
|
|
||||||
import operations from 'shared/operations';
|
|
||||||
import messages from 'shared/messages';
|
|
||||||
|
|
||||||
const reservedKeymaps = {
|
|
||||||
'<Esc>': { type: operations.CANCEL },
|
|
||||||
'<C-[>': { type: operations.CANCEL },
|
|
||||||
};
|
|
||||||
|
|
||||||
const set = (value) => {
|
|
||||||
let entries = [];
|
|
||||||
if (value.keymaps) {
|
|
||||||
let keymaps = { ...value.keymaps, ...reservedKeymaps };
|
|
||||||
entries = Object.entries(keymaps).map((entry) => {
|
|
||||||
return [
|
|
||||||
keyUtils.fromMapKeys(entry[0]),
|
|
||||||
entry[1],
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: actions.SETTING_SET,
|
|
||||||
value: { ...value,
|
|
||||||
keymaps: entries, }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const load = async() => {
|
|
||||||
let settings = await browser.runtime.sendMessage({
|
|
||||||
type: messages.SETTINGS_QUERY,
|
|
||||||
});
|
|
||||||
return set(settings);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { set, load };
|
|
28
src/content/actions/setting.ts
Normal file
28
src/content/actions/setting.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import * as actions from './index';
|
||||||
|
import * as operations from '../../shared/operations';
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
import Settings, { Keymaps } from '../../shared/Settings';
|
||||||
|
|
||||||
|
const reservedKeymaps: Keymaps = {
|
||||||
|
'<Esc>': { type: operations.CANCEL },
|
||||||
|
'<C-[>': { type: operations.CANCEL },
|
||||||
|
};
|
||||||
|
|
||||||
|
const set = (settings: Settings): actions.SettingAction => {
|
||||||
|
return {
|
||||||
|
type: actions.SETTING_SET,
|
||||||
|
settings: {
|
||||||
|
...settings,
|
||||||
|
keymaps: { ...settings.keymaps, ...reservedKeymaps },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = async(): Promise<actions.SettingAction> => {
|
||||||
|
let settings = await browser.runtime.sendMessage({
|
||||||
|
type: messages.SETTINGS_QUERY,
|
||||||
|
});
|
||||||
|
return set(settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { set, load };
|
|
@ -1,6 +1,8 @@
|
||||||
import messages from 'shared/messages';
|
import MessageListener from '../../MessageListener';
|
||||||
import Hint from './hint';
|
import Hint from './hint';
|
||||||
import * as dom from 'shared/utils/dom';
|
import * as dom from '../../../shared/utils/dom';
|
||||||
|
import * as messages from '../../../shared/messages';
|
||||||
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
|
|
||||||
const TARGET_SELECTOR = [
|
const TARGET_SELECTOR = [
|
||||||
'a', 'button', 'input', 'textarea', 'area',
|
'a', 'button', 'input', 'textarea', 'area',
|
||||||
|
@ -8,8 +10,22 @@ const TARGET_SELECTOR = [
|
||||||
'[role="button"]', 'summary'
|
'[role="button"]', 'summary'
|
||||||
].join(',');
|
].join(',');
|
||||||
|
|
||||||
|
interface Size {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
const inViewport = (win, element, viewSize, framePosition) => {
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inViewport = (
|
||||||
|
win: Window,
|
||||||
|
element: Element,
|
||||||
|
viewSize: Size,
|
||||||
|
framePosition: Point,
|
||||||
|
): boolean => {
|
||||||
let {
|
let {
|
||||||
top, left, bottom, right
|
top, left, bottom, right
|
||||||
} = dom.viewportRect(element);
|
} = dom.viewportRect(element);
|
||||||
|
@ -30,34 +46,44 @@ const inViewport = (win, element, viewSize, framePosition) => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAriaHiddenOrAriaDisabled = (win, element) => {
|
const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => {
|
||||||
if (!element || win.document.documentElement === element) {
|
if (!element || win.document.documentElement === element) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (let attr of ['aria-hidden', 'aria-disabled']) {
|
for (let attr of ['aria-hidden', 'aria-disabled']) {
|
||||||
if (element.hasAttribute(attr)) {
|
let value = element.getAttribute(attr);
|
||||||
let hidden = element.getAttribute(attr).toLowerCase();
|
if (value !== null) {
|
||||||
|
let hidden = value.toLowerCase();
|
||||||
if (hidden === '' || hidden === 'true') {
|
if (hidden === '' || hidden === 'true') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return isAriaHiddenOrAriaDisabled(win, element.parentNode);
|
return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Follow {
|
export default class Follow {
|
||||||
constructor(win, store) {
|
private win: Window;
|
||||||
|
|
||||||
|
private newTab: boolean;
|
||||||
|
|
||||||
|
private background: boolean;
|
||||||
|
|
||||||
|
private hints: {[key: string]: Hint };
|
||||||
|
|
||||||
|
private targets: HTMLElement[] = [];
|
||||||
|
|
||||||
|
constructor(win: Window) {
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.store = store;
|
|
||||||
this.newTab = false;
|
this.newTab = false;
|
||||||
this.background = false;
|
this.background = false;
|
||||||
this.hints = {};
|
this.hints = {};
|
||||||
this.targets = [];
|
this.targets = [];
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
key(key) {
|
key(key: keyUtils.Key): boolean {
|
||||||
if (Object.keys(this.hints).length === 0) {
|
if (Object.keys(this.hints).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +95,7 @@ export default class Follow {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
openLink(element) {
|
openLink(element: HTMLAreaElement|HTMLAnchorElement) {
|
||||||
// Browser prevent new tab by link with target='_blank'
|
// Browser prevent new tab by link with target='_blank'
|
||||||
if (!this.newTab && element.getAttribute('target') !== '_blank') {
|
if (!this.newTab && element.getAttribute('target') !== '_blank') {
|
||||||
element.click();
|
element.click();
|
||||||
|
@ -90,7 +116,7 @@ export default class Follow {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
countHints(sender, viewSize, framePosition) {
|
countHints(sender: any, viewSize: Size, framePosition: Point) {
|
||||||
this.targets = Follow.getTargetElements(this.win, viewSize, framePosition);
|
this.targets = Follow.getTargetElements(this.win, viewSize, framePosition);
|
||||||
sender.postMessage(JSON.stringify({
|
sender.postMessage(JSON.stringify({
|
||||||
type: messages.FOLLOW_RESPONSE_COUNT_TARGETS,
|
type: messages.FOLLOW_RESPONSE_COUNT_TARGETS,
|
||||||
|
@ -98,7 +124,7 @@ export default class Follow {
|
||||||
}), '*');
|
}), '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
createHints(keysArray, newTab, background) {
|
createHints(keysArray: string[], newTab: boolean, background: boolean) {
|
||||||
if (keysArray.length !== this.targets.length) {
|
if (keysArray.length !== this.targets.length) {
|
||||||
throw new Error('illegal hint count');
|
throw new Error('illegal hint count');
|
||||||
}
|
}
|
||||||
|
@ -113,7 +139,7 @@ export default class Follow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showHints(keys) {
|
showHints(keys: string) {
|
||||||
Object.keys(this.hints).filter(key => key.startsWith(keys))
|
Object.keys(this.hints).filter(key => key.startsWith(keys))
|
||||||
.forEach(key => this.hints[key].show());
|
.forEach(key => this.hints[key].show());
|
||||||
Object.keys(this.hints).filter(key => !key.startsWith(keys))
|
Object.keys(this.hints).filter(key => !key.startsWith(keys))
|
||||||
|
@ -128,18 +154,19 @@ export default class Follow {
|
||||||
this.targets = [];
|
this.targets = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
activateHints(keys) {
|
activateHints(keys: string) {
|
||||||
let hint = this.hints[keys];
|
let hint = this.hints[keys];
|
||||||
if (!hint) {
|
if (!hint) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let element = hint.target;
|
let element = hint.getTarget();
|
||||||
switch (element.tagName.toLowerCase()) {
|
switch (element.tagName.toLowerCase()) {
|
||||||
case 'a':
|
case 'a':
|
||||||
|
return this.openLink(element as HTMLAnchorElement);
|
||||||
case 'area':
|
case 'area':
|
||||||
return this.openLink(element);
|
return this.openLink(element as HTMLAreaElement);
|
||||||
case 'input':
|
case 'input':
|
||||||
switch (element.type) {
|
switch ((element as HTMLInputElement).type) {
|
||||||
case 'file':
|
case 'file':
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
case 'radio':
|
case 'radio':
|
||||||
|
@ -166,7 +193,7 @@ export default class Follow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message, sender) {
|
onMessage(message: messages.Message, sender: any) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
|
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
|
||||||
return this.countHints(sender, message.viewSize, message.framePosition);
|
return this.countHints(sender, message.viewSize, message.framePosition);
|
||||||
|
@ -178,19 +205,23 @@ export default class Follow {
|
||||||
case messages.FOLLOW_ACTIVATE:
|
case messages.FOLLOW_ACTIVATE:
|
||||||
return this.activateHints(message.keys);
|
return this.activateHints(message.keys);
|
||||||
case messages.FOLLOW_REMOVE_HINTS:
|
case messages.FOLLOW_REMOVE_HINTS:
|
||||||
return this.removeHints(message.keys);
|
return this.removeHints();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTargetElements(win, viewSize, framePosition) {
|
static getTargetElements(
|
||||||
|
win: Window,
|
||||||
|
viewSize:
|
||||||
|
Size, framePosition: Point,
|
||||||
|
): HTMLElement[] {
|
||||||
let all = win.document.querySelectorAll(TARGET_SELECTOR);
|
let all = win.document.querySelectorAll(TARGET_SELECTOR);
|
||||||
let filtered = Array.prototype.filter.call(all, (element) => {
|
let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => {
|
||||||
let style = win.getComputedStyle(element);
|
let style = win.getComputedStyle(element);
|
||||||
|
|
||||||
// AREA's 'display' in Browser style is 'none'
|
// AREA's 'display' in Browser style is 'none'
|
||||||
return (element.tagName === 'AREA' || style.display !== 'none') &&
|
return (element.tagName === 'AREA' || style.display !== 'none') &&
|
||||||
style.visibility !== 'hidden' &&
|
style.visibility !== 'hidden' &&
|
||||||
element.type !== 'hidden' &&
|
(element as HTMLInputElement).type !== 'hidden' &&
|
||||||
element.offsetHeight > 0 &&
|
element.offsetHeight > 0 &&
|
||||||
!isAriaHiddenOrAriaDisabled(win, element) &&
|
!isAriaHiddenOrAriaDisabled(win, element) &&
|
||||||
inViewport(win, element, viewSize, framePosition);
|
inViewport(win, element, viewSize, framePosition);
|
|
@ -1,6 +1,11 @@
|
||||||
import * as dom from 'shared/utils/dom';
|
import * as dom from '../../../shared/utils/dom';
|
||||||
|
|
||||||
const hintPosition = (element) => {
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hintPosition = (element: Element): Point => {
|
||||||
let { left, top, right, bottom } = dom.viewportRect(element);
|
let { left, top, right, bottom } = dom.viewportRect(element);
|
||||||
|
|
||||||
if (element.tagName !== 'AREA') {
|
if (element.tagName !== 'AREA') {
|
||||||
|
@ -14,17 +19,21 @@ const hintPosition = (element) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Hint {
|
export default class Hint {
|
||||||
constructor(target, tag) {
|
private target: HTMLElement;
|
||||||
if (!(document.body instanceof HTMLElement)) {
|
|
||||||
throw new TypeError('target is not an HTMLElement');
|
private element: HTMLElement;
|
||||||
|
|
||||||
|
constructor(target: HTMLElement, tag: string) {
|
||||||
|
let doc = target.ownerDocument;
|
||||||
|
if (doc === null) {
|
||||||
|
throw new TypeError('ownerDocument is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.target = target;
|
|
||||||
|
|
||||||
let doc = target.ownerDocument;
|
|
||||||
let { x, y } = hintPosition(target);
|
let { x, y } = hintPosition(target);
|
||||||
let { scrollX, scrollY } = window;
|
let { scrollX, scrollY } = window;
|
||||||
|
|
||||||
|
this.target = target;
|
||||||
|
|
||||||
this.element = doc.createElement('span');
|
this.element = doc.createElement('span');
|
||||||
this.element.className = 'vimvixen-hint';
|
this.element.className = 'vimvixen-hint';
|
||||||
this.element.textContent = tag;
|
this.element.textContent = tag;
|
||||||
|
@ -35,15 +44,19 @@ export default class Hint {
|
||||||
doc.body.append(this.element);
|
doc.body.append(this.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show(): void {
|
||||||
this.element.style.display = 'inline';
|
this.element.style.display = 'inline';
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide(): void {
|
||||||
this.element.style.display = 'none';
|
this.element.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
remove() {
|
remove(): void {
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTarget(): HTMLElement {
|
||||||
|
return this.target;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,55 +0,0 @@
|
||||||
import InputComponent from './input';
|
|
||||||
import FollowComponent from './follow';
|
|
||||||
import MarkComponent from './mark';
|
|
||||||
import KeymapperComponent from './keymapper';
|
|
||||||
import * as settingActions from 'content/actions/setting';
|
|
||||||
import messages from 'shared/messages';
|
|
||||||
import * as addonActions from '../../actions/addon';
|
|
||||||
import * as blacklists from 'shared/blacklists';
|
|
||||||
|
|
||||||
export default class Common {
|
|
||||||
constructor(win, store) {
|
|
||||||
const input = new InputComponent(win.document.body, store);
|
|
||||||
const follow = new FollowComponent(win, store);
|
|
||||||
const mark = new MarkComponent(win.document.body, store);
|
|
||||||
const keymapper = new KeymapperComponent(store);
|
|
||||||
|
|
||||||
input.onKey(key => follow.key(key));
|
|
||||||
input.onKey(key => mark.key(key));
|
|
||||||
input.onKey(key => keymapper.key(key));
|
|
||||||
|
|
||||||
this.win = win;
|
|
||||||
this.store = store;
|
|
||||||
this.prevEnabled = undefined;
|
|
||||||
this.prevBlacklist = undefined;
|
|
||||||
|
|
||||||
this.reloadSettings();
|
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(message) {
|
|
||||||
let { enabled } = this.store.getState().addon;
|
|
||||||
switch (message.type) {
|
|
||||||
case messages.SETTINGS_CHANGED:
|
|
||||||
return this.reloadSettings();
|
|
||||||
case messages.ADDON_TOGGLE_ENABLED:
|
|
||||||
this.store.dispatch(addonActions.setEnabled(!enabled));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadSettings() {
|
|
||||||
try {
|
|
||||||
this.store.dispatch(settingActions.load()).then(({ value: settings }) => {
|
|
||||||
let enabled = !blacklists.includes(
|
|
||||||
settings.blacklist, this.win.location.href
|
|
||||||
);
|
|
||||||
this.store.dispatch(addonActions.setEnabled(enabled));
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// Sometime sendMessage fails when background script is not ready.
|
|
||||||
console.warn(e);
|
|
||||||
setTimeout(() => this.reloadSettings(), 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
61
src/content/components/common/index.ts
Normal file
61
src/content/components/common/index.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import InputComponent from './input';
|
||||||
|
import FollowComponent from './follow';
|
||||||
|
import MarkComponent from './mark';
|
||||||
|
import KeymapperComponent from './keymapper';
|
||||||
|
import * as settingActions from '../../actions/setting';
|
||||||
|
import * as messages from '../../../shared/messages';
|
||||||
|
import MessageListener from '../../MessageListener';
|
||||||
|
import * as addonActions from '../../actions/addon';
|
||||||
|
import * as blacklists from '../../../shared/blacklists';
|
||||||
|
import * as keys from '../../../shared/utils/keys';
|
||||||
|
import * as actions from '../../actions';
|
||||||
|
|
||||||
|
export default class Common {
|
||||||
|
private win: Window;
|
||||||
|
|
||||||
|
private store: any;
|
||||||
|
|
||||||
|
constructor(win: Window, store: any) {
|
||||||
|
const input = new InputComponent(win.document.body);
|
||||||
|
const follow = new FollowComponent(win);
|
||||||
|
const mark = new MarkComponent(store);
|
||||||
|
const keymapper = new KeymapperComponent(store);
|
||||||
|
|
||||||
|
input.onKey((key: keys.Key) => follow.key(key));
|
||||||
|
input.onKey((key: keys.Key) => mark.key(key));
|
||||||
|
input.onKey((key: keys.Key) => keymapper.key(key));
|
||||||
|
|
||||||
|
this.win = win;
|
||||||
|
this.store = store;
|
||||||
|
|
||||||
|
this.reloadSettings();
|
||||||
|
|
||||||
|
new MessageListener().onBackgroundMessage(this.onMessage.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(message: messages.Message) {
|
||||||
|
let { enabled } = this.store.getState().addon;
|
||||||
|
switch (message.type) {
|
||||||
|
case messages.SETTINGS_CHANGED:
|
||||||
|
return this.reloadSettings();
|
||||||
|
case messages.ADDON_TOGGLE_ENABLED:
|
||||||
|
this.store.dispatch(addonActions.setEnabled(!enabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSettings() {
|
||||||
|
try {
|
||||||
|
this.store.dispatch(settingActions.load())
|
||||||
|
.then((action: actions.SettingAction) => {
|
||||||
|
let enabled = !blacklists.includes(
|
||||||
|
action.settings.blacklist, this.win.location.href
|
||||||
|
);
|
||||||
|
this.store.dispatch(addonActions.setEnabled(enabled));
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Sometime sendMessage fails when background script is not ready.
|
||||||
|
console.warn(e);
|
||||||
|
setTimeout(() => this.reloadSettings(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,16 @@
|
||||||
import * as dom from 'shared/utils/dom';
|
import * as dom from '../../../shared/utils/dom';
|
||||||
import * as keys from 'shared/utils/keys';
|
import * as keys from '../../../shared/utils/keys';
|
||||||
|
|
||||||
const cancelKey = (e) => {
|
const cancelKey = (e: KeyboardEvent): boolean => {
|
||||||
return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
|
return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class InputComponent {
|
export default class InputComponent {
|
||||||
constructor(target) {
|
private pressed: {[key: string]: string} = {};
|
||||||
|
|
||||||
|
private onKeyListeners: ((key: keys.Key) => boolean)[] = [];
|
||||||
|
|
||||||
|
constructor(target: HTMLElement) {
|
||||||
this.pressed = {};
|
this.pressed = {};
|
||||||
this.onKeyListeners = [];
|
this.onKeyListeners = [];
|
||||||
|
|
||||||
|
@ -15,11 +19,11 @@ export default class InputComponent {
|
||||||
target.addEventListener('keyup', this.onKeyUp.bind(this));
|
target.addEventListener('keyup', this.onKeyUp.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onKey(cb) {
|
onKey(cb: (key: keys.Key) => boolean) {
|
||||||
this.onKeyListeners.push(cb);
|
this.onKeyListeners.push(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyPress(e) {
|
onKeyPress(e: KeyboardEvent) {
|
||||||
if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
|
if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +31,7 @@ export default class InputComponent {
|
||||||
this.capture(e);
|
this.capture(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(e) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
|
if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -35,14 +39,19 @@ export default class InputComponent {
|
||||||
this.capture(e);
|
this.capture(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyUp(e) {
|
onKeyUp(e: KeyboardEvent) {
|
||||||
delete this.pressed[e.key];
|
delete this.pressed[e.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
capture(e) {
|
// eslint-disable-next-line max-statements
|
||||||
if (this.fromInput(e)) {
|
capture(e: KeyboardEvent) {
|
||||||
if (cancelKey(e) && e.target.blur) {
|
let target = e.target;
|
||||||
e.target.blur();
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.fromInput(target)) {
|
||||||
|
if (cancelKey(e) && target.blur) {
|
||||||
|
target.blur();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +61,6 @@ export default class InputComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = keys.fromKeyboardEvent(e);
|
let key = keys.fromKeyboardEvent(e);
|
||||||
|
|
||||||
for (let listener of this.onKeyListeners) {
|
for (let listener of this.onKeyListeners) {
|
||||||
let stop = listener(key);
|
let stop = listener(key);
|
||||||
if (stop) {
|
if (stop) {
|
||||||
|
@ -63,13 +71,10 @@ export default class InputComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fromInput(e) {
|
fromInput(e: Element) {
|
||||||
if (!e.target) {
|
return e instanceof HTMLInputElement ||
|
||||||
return false;
|
e instanceof HTMLTextAreaElement ||
|
||||||
}
|
e instanceof HTMLSelectElement ||
|
||||||
return e.target instanceof HTMLInputElement ||
|
dom.isContentEditable(e);
|
||||||
e.target instanceof HTMLTextAreaElement ||
|
|
||||||
e.target instanceof HTMLSelectElement ||
|
|
||||||
dom.isContentEditable(e.target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
import * as inputActions from 'content/actions/input';
|
import * as inputActions from '../../actions/input';
|
||||||
import * as operationActions from 'content/actions/operation';
|
import * as operationActions from '../../actions/operation';
|
||||||
import operations from 'shared/operations';
|
import * as operations from '../../../shared/operations';
|
||||||
import * as keyUtils from 'shared/utils/keys';
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
|
|
||||||
const mapStartsWith = (mapping, keys) => {
|
const mapStartsWith = (
|
||||||
|
mapping: keyUtils.Key[],
|
||||||
|
keys: keyUtils.Key[],
|
||||||
|
): boolean => {
|
||||||
if (mapping.length < keys.length) {
|
if (mapping.length < keys.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -16,26 +19,33 @@ const mapStartsWith = (mapping, keys) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class KeymapperComponent {
|
export default class KeymapperComponent {
|
||||||
constructor(store) {
|
private store: any;
|
||||||
|
|
||||||
|
constructor(store: any) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
// eslint-disable-next-line max-statements
|
||||||
key(key) {
|
key(key: keyUtils.Key): boolean {
|
||||||
this.store.dispatch(inputActions.keyPress(key));
|
this.store.dispatch(inputActions.keyPress(key));
|
||||||
|
|
||||||
let state = this.store.getState();
|
let state = this.store.getState();
|
||||||
let input = state.input;
|
let input = state.input;
|
||||||
let keymaps = new Map(state.setting.keymaps);
|
let keymaps = new Map<keyUtils.Key[], operations.Operation>(
|
||||||
|
state.setting.keymaps.map(
|
||||||
|
(e: {key: keyUtils.Key[], op: operations.Operation}) => [e.key, e.op],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
let matched = Array.from(keymaps.keys()).filter((mapping) => {
|
let matched = Array.from(keymaps.keys()).filter(
|
||||||
return mapStartsWith(mapping, input.keys);
|
(mapping: keyUtils.Key[]) => {
|
||||||
});
|
return mapStartsWith(mapping, input.keys);
|
||||||
|
});
|
||||||
if (!state.addon.enabled) {
|
if (!state.addon.enabled) {
|
||||||
// available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if
|
// available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if
|
||||||
// the addon disabled
|
// the addon disabled
|
||||||
matched = matched.filter((keys) => {
|
matched = matched.filter((keys) => {
|
||||||
let type = keymaps.get(keys).type;
|
let type = (keymaps.get(keys) as operations.Operation).type;
|
||||||
return type === operations.ADDON_ENABLE ||
|
return type === operations.ADDON_ENABLE ||
|
||||||
type === operations.ADDON_TOGGLE_ENABLED;
|
type === operations.ADDON_TOGGLE_ENABLED;
|
||||||
});
|
});
|
||||||
|
@ -47,7 +57,7 @@ export default class KeymapperComponent {
|
||||||
matched.length === 1 && input.keys.length < matched[0].length) {
|
matched.length === 1 && input.keys.length < matched[0].length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let operation = keymaps.get(matched[0]);
|
let operation = keymaps.get(matched[0]) as operations.Operation;
|
||||||
let act = operationActions.exec(
|
let act = operationActions.exec(
|
||||||
operation, state.setting, state.addon.enabled
|
operation, state.setting, state.addon.enabled
|
||||||
);
|
);
|
|
@ -1,74 +0,0 @@
|
||||||
import * as markActions from 'content/actions/mark';
|
|
||||||
import * as scrolls from 'content/scrolls';
|
|
||||||
import * as consoleFrames from 'content/console-frames';
|
|
||||||
import * as properties from 'shared/settings/properties';
|
|
||||||
|
|
||||||
const cancelKey = (key) => {
|
|
||||||
return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
|
|
||||||
};
|
|
||||||
|
|
||||||
const globalKey = (key) => {
|
|
||||||
return (/^[A-Z0-9]$/).test(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class MarkComponent {
|
|
||||||
constructor(body, store) {
|
|
||||||
this.body = body;
|
|
||||||
this.store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
|
||||||
key(key) {
|
|
||||||
let { mark: markStage, setting } = this.store.getState();
|
|
||||||
let smoothscroll = setting.properties.smoothscroll ||
|
|
||||||
properties.defaults.smoothscroll;
|
|
||||||
|
|
||||||
if (!markStage.setMode && !markStage.jumpMode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelKey(key)) {
|
|
||||||
this.store.dispatch(markActions.cancel());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.ctrlKey || key.metaKey || key.altKey) {
|
|
||||||
consoleFrames.postError('Unknown mark');
|
|
||||||
} else if (globalKey(key.key) && markStage.setMode) {
|
|
||||||
this.doSetGlobal(key);
|
|
||||||
} else if (globalKey(key.key) && markStage.jumpMode) {
|
|
||||||
this.doJumpGlobal(key);
|
|
||||||
} else if (markStage.setMode) {
|
|
||||||
this.doSet(key);
|
|
||||||
} else if (markStage.jumpMode) {
|
|
||||||
this.doJump(markStage.marks, key, smoothscroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.store.dispatch(markActions.cancel());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
doSet(key) {
|
|
||||||
let { x, y } = scrolls.getScroll();
|
|
||||||
this.store.dispatch(markActions.setLocal(key.key, x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
doJump(marks, key, smoothscroll) {
|
|
||||||
if (!marks[key.key]) {
|
|
||||||
consoleFrames.postError('Mark is not set');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { x, y } = marks[key.key];
|
|
||||||
scrolls.scrollTo(x, y, smoothscroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
doSetGlobal(key) {
|
|
||||||
let { x, y } = scrolls.getScroll();
|
|
||||||
this.store.dispatch(markActions.setGlobal(key.key, x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
doJumpGlobal(key) {
|
|
||||||
this.store.dispatch(markActions.jumpGlobal(key.key));
|
|
||||||
}
|
|
||||||
}
|
|
79
src/content/components/common/mark.ts
Normal file
79
src/content/components/common/mark.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import * as markActions from '../../actions/mark';
|
||||||
|
import * as scrolls from '../..//scrolls';
|
||||||
|
import * as consoleFrames from '../..//console-frames';
|
||||||
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
|
import Mark from '../../Mark';
|
||||||
|
|
||||||
|
const cancelKey = (key: keyUtils.Key): boolean => {
|
||||||
|
return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const globalKey = (key: string): boolean => {
|
||||||
|
return (/^[A-Z0-9]$/).test(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class MarkComponent {
|
||||||
|
private store: any;
|
||||||
|
|
||||||
|
constructor(store: any) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-statements
|
||||||
|
key(key: keyUtils.Key) {
|
||||||
|
let { mark: markState, setting } = this.store.getState();
|
||||||
|
let smoothscroll = setting.properties.smoothscroll;
|
||||||
|
|
||||||
|
if (!markState.setMode && !markState.jumpMode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelKey(key)) {
|
||||||
|
this.store.dispatch(markActions.cancel());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.ctrlKey || key.metaKey || key.altKey) {
|
||||||
|
consoleFrames.postError('Unknown mark');
|
||||||
|
} else if (globalKey(key.key) && markState.setMode) {
|
||||||
|
this.doSetGlobal(key);
|
||||||
|
} else if (globalKey(key.key) && markState.jumpMode) {
|
||||||
|
this.doJumpGlobal(key);
|
||||||
|
} else if (markState.setMode) {
|
||||||
|
this.doSet(key);
|
||||||
|
} else if (markState.jumpMode) {
|
||||||
|
this.doJump(markState.marks, key, smoothscroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(markActions.cancel());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
doSet(key: keyUtils.Key) {
|
||||||
|
let { x, y } = scrolls.getScroll();
|
||||||
|
this.store.dispatch(markActions.setLocal(key.key, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
doJump(
|
||||||
|
marks: { [key: string]: Mark },
|
||||||
|
key: keyUtils.Key,
|
||||||
|
smoothscroll: boolean,
|
||||||
|
) {
|
||||||
|
if (!marks[key.key]) {
|
||||||
|
consoleFrames.postError('Mark is not set');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { x, y } = marks[key.key];
|
||||||
|
scrolls.scrollTo(x, y, smoothscroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSetGlobal(key: keyUtils.Key) {
|
||||||
|
let { x, y } = scrolls.getScroll();
|
||||||
|
this.store.dispatch(markActions.setGlobal(key.key, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
doJumpGlobal(key: keyUtils.Key) {
|
||||||
|
this.store.dispatch(markActions.jumpGlobal(key.key));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
import * as findActions from 'content/actions/find';
|
|
||||||
import messages from 'shared/messages';
|
|
||||||
|
|
||||||
export default class FindComponent {
|
|
||||||
constructor(win, store) {
|
|
||||||
this.win = win;
|
|
||||||
this.store = store;
|
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(message) {
|
|
||||||
switch (message.type) {
|
|
||||||
case messages.CONSOLE_ENTER_FIND:
|
|
||||||
return this.start(message.text);
|
|
||||||
case messages.FIND_NEXT:
|
|
||||||
return this.next();
|
|
||||||
case messages.FIND_PREV:
|
|
||||||
return this.prev();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start(text) {
|
|
||||||
let state = this.store.getState().find;
|
|
||||||
|
|
||||||
if (text.length === 0) {
|
|
||||||
return this.store.dispatch(findActions.next(state.keyword, true));
|
|
||||||
}
|
|
||||||
return this.store.dispatch(findActions.next(text, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
next() {
|
|
||||||
let state = this.store.getState().find;
|
|
||||||
return this.store.dispatch(findActions.next(state.keyword, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
prev() {
|
|
||||||
let state = this.store.getState().find;
|
|
||||||
return this.store.dispatch(findActions.prev(state.keyword, false));
|
|
||||||
}
|
|
||||||
}
|
|
46
src/content/components/top-content/find.ts
Normal file
46
src/content/components/top-content/find.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import * as findActions from '../../actions/find';
|
||||||
|
import * as messages from '../../../shared/messages';
|
||||||
|
import MessageListener from '../../MessageListener';
|
||||||
|
|
||||||
|
export default class FindComponent {
|
||||||
|
private store: any;
|
||||||
|
|
||||||
|
constructor(store: any) {
|
||||||
|
this.store = store;
|
||||||
|
|
||||||
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(message: messages.Message) {
|
||||||
|
switch (message.type) {
|
||||||
|
case messages.CONSOLE_ENTER_FIND:
|
||||||
|
return this.start(message.text);
|
||||||
|
case messages.FIND_NEXT:
|
||||||
|
return this.next();
|
||||||
|
case messages.FIND_PREV:
|
||||||
|
return this.prev();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(text: string) {
|
||||||
|
let state = this.store.getState().find;
|
||||||
|
|
||||||
|
if (text.length === 0) {
|
||||||
|
return this.store.dispatch(
|
||||||
|
findActions.next(state.keyword as string, true));
|
||||||
|
}
|
||||||
|
return this.store.dispatch(findActions.next(text, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
let state = this.store.getState().find;
|
||||||
|
return this.store.dispatch(
|
||||||
|
findActions.next(state.keyword as string, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
let state = this.store.getState().find;
|
||||||
|
return this.store.dispatch(
|
||||||
|
findActions.prev(state.keyword as string, false));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +1,45 @@
|
||||||
import * as followControllerActions from 'content/actions/follow-controller';
|
import * as followControllerActions from '../../actions/follow-controller';
|
||||||
import messages from 'shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
import HintKeyProducer from 'content/hint-key-producer';
|
import MessageListener, { WebMessageSender } from '../../MessageListener';
|
||||||
import * as properties from 'shared/settings/properties';
|
import HintKeyProducer from '../../hint-key-producer';
|
||||||
|
|
||||||
const broadcastMessage = (win, message) => {
|
const broadcastMessage = (win: Window, message: messages.Message): void => {
|
||||||
let json = JSON.stringify(message);
|
let json = JSON.stringify(message);
|
||||||
let frames = [window.self].concat(Array.from(window.frames));
|
let frames = [win.self].concat(Array.from(win.frames as any));
|
||||||
frames.forEach(frame => frame.postMessage(json, '*'));
|
frames.forEach(frame => frame.postMessage(json, '*'));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FollowController {
|
export default class FollowController {
|
||||||
constructor(win, store) {
|
private win: Window;
|
||||||
|
|
||||||
|
private store: any;
|
||||||
|
|
||||||
|
private state: {
|
||||||
|
enabled?: boolean;
|
||||||
|
newTab?: boolean;
|
||||||
|
background?: boolean;
|
||||||
|
keys?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
private keys: string[];
|
||||||
|
|
||||||
|
private producer: HintKeyProducer | null;
|
||||||
|
|
||||||
|
constructor(win: Window, store: any) {
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.state = {};
|
this.state = {};
|
||||||
this.keys = [];
|
this.keys = [];
|
||||||
this.producer = null;
|
this.producer = null;
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
|
|
||||||
store.subscribe(() => {
|
store.subscribe(() => {
|
||||||
this.update();
|
this.update();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message, sender) {
|
onMessage(message: messages.Message, sender: WebMessageSender) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.FOLLOW_START:
|
case messages.FOLLOW_START:
|
||||||
return this.store.dispatch(
|
return this.store.dispatch(
|
||||||
|
@ -36,7 +51,7 @@ export default class FollowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update(): void {
|
||||||
let prevState = this.state;
|
let prevState = this.state;
|
||||||
this.state = this.store.getState().followController;
|
this.state = this.store.getState().followController;
|
||||||
|
|
||||||
|
@ -49,8 +64,10 @@ export default class FollowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHints() {
|
updateHints(): void {
|
||||||
let shown = this.keys.filter(key => key.startsWith(this.state.keys));
|
let shown = this.keys.filter((key) => {
|
||||||
|
return key.startsWith(this.state.keys as string);
|
||||||
|
});
|
||||||
if (shown.length === 1) {
|
if (shown.length === 1) {
|
||||||
this.activate();
|
this.activate();
|
||||||
this.store.dispatch(followControllerActions.disable());
|
this.store.dispatch(followControllerActions.disable());
|
||||||
|
@ -58,18 +75,18 @@ export default class FollowController {
|
||||||
|
|
||||||
broadcastMessage(this.win, {
|
broadcastMessage(this.win, {
|
||||||
type: messages.FOLLOW_SHOW_HINTS,
|
type: messages.FOLLOW_SHOW_HINTS,
|
||||||
keys: this.state.keys,
|
keys: this.state.keys as string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate(): void {
|
||||||
broadcastMessage(this.win, {
|
broadcastMessage(this.win, {
|
||||||
type: messages.FOLLOW_ACTIVATE,
|
type: messages.FOLLOW_ACTIVATE,
|
||||||
keys: this.state.keys,
|
keys: this.state.keys as string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPress(key, ctrlKey) {
|
keyPress(key: string, ctrlKey: boolean): boolean {
|
||||||
if (key === '[' && ctrlKey) {
|
if (key === '[' && ctrlKey) {
|
||||||
this.store.dispatch(followControllerActions.disable());
|
this.store.dispatch(followControllerActions.disable());
|
||||||
return true;
|
return true;
|
||||||
|
@ -107,25 +124,28 @@ export default class FollowController {
|
||||||
viewSize: { width: viewWidth, height: viewHeight },
|
viewSize: { width: viewWidth, height: viewHeight },
|
||||||
framePosition: { x: 0, y: 0 },
|
framePosition: { x: 0, y: 0 },
|
||||||
}), '*');
|
}), '*');
|
||||||
frameElements.forEach((element) => {
|
frameElements.forEach((ele) => {
|
||||||
let { left: frameX, top: frameY } = element.getBoundingClientRect();
|
let { left: frameX, top: frameY } = ele.getBoundingClientRect();
|
||||||
let message = JSON.stringify({
|
let message = JSON.stringify({
|
||||||
type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
|
type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
|
||||||
viewSize: { width: viewWidth, height: viewHeight },
|
viewSize: { width: viewWidth, height: viewHeight },
|
||||||
framePosition: { x: frameX, y: frameY },
|
framePosition: { x: frameX, y: frameY },
|
||||||
});
|
});
|
||||||
element.contentWindow.postMessage(message, '*');
|
if (ele instanceof HTMLFrameElement && ele.contentWindow ||
|
||||||
|
ele instanceof HTMLIFrameElement && ele.contentWindow) {
|
||||||
|
ele.contentWindow.postMessage(message, '*');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create(count, sender) {
|
create(count: number, sender: WebMessageSender) {
|
||||||
let produced = [];
|
let produced = [];
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < count; ++i) {
|
||||||
produced.push(this.producer.produce());
|
produced.push((this.producer as HintKeyProducer).produce());
|
||||||
}
|
}
|
||||||
this.keys = this.keys.concat(produced);
|
this.keys = this.keys.concat(produced);
|
||||||
|
|
||||||
sender.postMessage(JSON.stringify({
|
(sender as Window).postMessage(JSON.stringify({
|
||||||
type: messages.FOLLOW_CREATE_HINTS,
|
type: messages.FOLLOW_CREATE_HINTS,
|
||||||
keysArray: produced,
|
keysArray: produced,
|
||||||
newTab: this.state.newTab,
|
newTab: this.state.newTab,
|
||||||
|
@ -141,7 +161,6 @@ export default class FollowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
hintchars() {
|
hintchars() {
|
||||||
return this.store.getState().setting.properties.hintchars ||
|
return this.store.getState().setting.properties.hintchars;
|
||||||
properties.defaults.hintchars;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue