From 29974643b6880744d3318395a580c29944d4cf0e Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 13:25:28 +0900 Subject: Install TypeScript --- package-lock.json | 43 +++++++++++++++++++++++++++++++++++++------ package.json | 2 ++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index cbc8dae..15fd9a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -395,6 +395,15 @@ "@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": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", @@ -435,6 +444,16 @@ "@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": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", @@ -448,6 +467,16 @@ "@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": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz", @@ -3948,14 +3977,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3975,8 +4002,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -4124,7 +4150,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10378,6 +10403,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "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": { "version": "3.3.25", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.25.tgz", diff --git a/package.json b/package.json index 98cdc60..9db3296 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@babel/cli": "^7.4.4", "@babel/core": "^7.4.4", "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.3.3", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.5", "chai": "^4.2.0", @@ -51,6 +52,7 @@ "sass-loader": "^7.1.0", "sinon-chrome": "^3.0.1", "style-loader": "^0.23.1", + "typescript": "^3.4.5", "webextensions-api-fake": "^0.7.4", "webpack": "^4.30.0", "webpack-cli": "^3.3.1" -- cgit v1.2.3 From 074b34decaa890812fbf8c06f49016de877fc2e9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 13:35:13 +0900 Subject: Configure Webpack for TypeScript --- tsconfig.json | 31 +++++++++++++++++++++++++++++++ webpack.config.js | 14 +++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tsconfig.json diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bf03cd2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "allowJs": true, + "checkJs": true, + "jsx": "react", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./build", + "removeComments": true, + "importHelpers": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + + "moduleResolution": "node", + "esModuleInterop": true + } +} diff --git a/webpack.config.js b/webpack.config.js index d9c60cc..a845375 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,12 +20,16 @@ config = { module: { rules: [ { - test: [ /\.js$/, /\.jsx$/ ], + test: [ /\.js$/, /\.jsx$/, /\.ts$/, /\.tsx$/], exclude: /node_modules/, loader: 'babel-loader', - query: { - presets: ['@babel/react'] - } + options: { + presets: [ + { plugins: ['@babel/plugin-proposal-class-properties'] }, + '@babel/react', + '@babel/preset-typescript' + ] + }, }, { test: /\.css$/, @@ -39,7 +43,7 @@ config = { }, resolve: { - extensions: [ '.js', '.jsx' ], + extensions: [ '.js', '.jsx', '.ts', '.tsx' ], modules: [path.join(__dirname, 'src'), 'node_modules'] }, -- cgit v1.2.3 From b1df2b141b37f9390e87207554f5722f0cb37da9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 13:44:48 +0900 Subject: Install web-ext-types --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 15fd9a9..5df214a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11131,6 +11131,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": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/webextensions-api-fake/-/webextensions-api-fake-0.7.4.tgz", diff --git a/package.json b/package.json index 9db3296..09354f2 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "sinon-chrome": "^3.0.1", "style-loader": "^0.23.1", "typescript": "^3.4.5", + "web-ext-types": "^3.1.0", "webextensions-api-fake": "^0.7.4", "webpack": "^4.30.0", "webpack-cli": "^3.3.1" -- cgit v1.2.3 From cc6092bbcd58e80a24c5cff10cae240a2701313b Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 13:45:28 +0900 Subject: Configure web-ext-types --- tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index bf03cd2..575601b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,8 @@ "noImplicitReturns": true, "moduleResolution": "node", - "esModuleInterop": true + "esModuleInterop": true, + + "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"] } } -- cgit v1.2.3 From 4bc07b6f53654f7c23e393d2c7d79cd30d831aa3 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 13:55:30 +0900 Subject: Install @typescript-eslint/eslint-plugin --- package-lock.json | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 77 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5df214a..4e2179c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -608,6 +608,61 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@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": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -6439,6 +6494,12 @@ "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", "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": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -8926,6 +8987,12 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -10351,6 +10418,15 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "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": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", diff --git a/package.json b/package.json index 09354f2..4169b90 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@babel/core": "^7.4.4", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", + "@typescript-eslint/eslint-plugin": "^1.7.0", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.5", "chai": "^4.2.0", -- cgit v1.2.3 From 08a318874e979ca115bfcb0a88f4678469332088 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 14:05:19 +0900 Subject: Install types --- package-lock.json | 43 +++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +++ 2 files changed, 46 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4e2179c..3e15f83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -608,6 +608,43 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "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/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-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" + } + }, "@typescript-eslint/eslint-plugin": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.7.0.tgz", @@ -2339,6 +2376,12 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "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": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", diff --git a/package.json b/package.json index 4169b90..f617c14 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,9 @@ "@babel/core": "^7.4.4", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", + "@types/prop-types": "^15.7.1", + "@types/react": "^16.8.15", + "@types/react-redux": "^7.0.8", "@typescript-eslint/eslint-plugin": "^1.7.0", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.5", -- cgit v1.2.3 From 257162e5b6b4993e1dff0d705ffa6f0d809033eb Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 13:59:51 +0900 Subject: Configure eslint for TypeScript --- .eslintrc | 16 +++++++++++----- package.json | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0f41e10..fb60bc2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,12 +5,17 @@ "browser" : true, "webextensions": true }, - "plugins": ["react"], - "parser": "babel-eslint", + "plugins": [ + "react", + "@typescript-eslint" + ], + "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { "jsx": true - } + }, + "sourceType": "module", + "project": "./tsconfig.json" }, "extends": [ "eslint:all", "plugin:react/recommended" ], "rules": { @@ -48,7 +53,7 @@ "no-ternary": "off", "no-undefined": "off", "no-undef-init": "off", - "no-unused-vars": ["error", { "varsIgnorePattern": "h" }], + "no-unused-vars": "off", "no-use-before-define": "off", "no-warning-comments": "off", "object-curly-newline": ["error", { "consistent": true }], @@ -71,6 +76,7 @@ "react/jsx-indent": ["error", 2], "react/prop-types": "off", - "react/react-in-jsx-scope": "off" + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-unused-vars": "error" } } diff --git a/package.json b/package.json index f617c14..590504b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "start": "webpack --mode development -w --debug --devtool inline-source-map", "build": "NODE_ENV=production webpack --mode production --progress --display-error-details", "package": "npm run build && script/package", - "lint": "eslint --ext .jsx,.js src", + "lint": "eslint --ext .js,.jsx,.ts,.tsx src", "test": "karma start", "test:e2e": "mocha --timeout 8000 e2e" }, -- cgit v1.2.3 From c60d0e7392fc708e961614d6b756a045de74f458 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 30 Apr 2019 14:00:07 +0900 Subject: Rename .js/.jsx to .ts/.tsx --- .../controllers/AddonEnabledController.js | 11 -- .../controllers/AddonEnabledController.ts | 11 ++ src/background/controllers/CommandController.js | 99 ---------- src/background/controllers/CommandController.ts | 99 ++++++++++ src/background/controllers/FindController.js | 15 -- src/background/controllers/FindController.ts | 15 ++ src/background/controllers/LinkController.js | 15 -- src/background/controllers/LinkController.ts | 15 ++ src/background/controllers/MarkController.js | 15 -- src/background/controllers/MarkController.ts | 15 ++ src/background/controllers/OperationController.js | 77 -------- src/background/controllers/OperationController.ts | 77 ++++++++ src/background/controllers/SettingController.js | 18 -- src/background/controllers/SettingController.ts | 18 ++ src/background/controllers/VersionController.js | 11 -- src/background/controllers/VersionController.ts | 11 ++ src/background/controllers/version.js | 13 -- src/background/controllers/version.ts | 13 ++ src/background/domains/CommandDocs.js | 12 -- src/background/domains/CommandDocs.ts | 12 ++ src/background/domains/CompletionGroup.js | 14 -- src/background/domains/CompletionGroup.ts | 14 ++ src/background/domains/CompletionItem.js | 24 --- src/background/domains/CompletionItem.ts | 24 +++ src/background/domains/Completions.js | 27 --- src/background/domains/Completions.ts | 27 +++ src/background/domains/GlobalMark.js | 24 --- src/background/domains/GlobalMark.ts | 24 +++ src/background/domains/Setting.js | 51 ----- src/background/domains/Setting.ts | 51 +++++ src/background/index.js | 23 --- src/background/index.ts | 23 +++ src/background/infrastructures/ConsoleClient.js | 37 ---- src/background/infrastructures/ConsoleClient.ts | 37 ++++ .../infrastructures/ContentMessageClient.js | 36 ---- .../infrastructures/ContentMessageClient.ts | 36 ++++ .../infrastructures/ContentMessageListener.js | 135 -------------- .../infrastructures/ContentMessageListener.ts | 135 ++++++++++++++ src/background/infrastructures/MemoryStorage.js | 19 -- src/background/infrastructures/MemoryStorage.ts | 19 ++ src/background/presenters/IndicatorPresenter.js | 12 -- src/background/presenters/IndicatorPresenter.ts | 12 ++ src/background/presenters/NotifyPresenter.js | 23 --- src/background/presenters/NotifyPresenter.ts | 23 +++ src/background/presenters/TabPresenter.js | 102 ---------- src/background/presenters/TabPresenter.ts | 102 ++++++++++ src/background/presenters/WindowPresenter.js | 5 - src/background/presenters/WindowPresenter.ts | 5 + src/background/repositories/BookmarkRepository.js | 13 -- src/background/repositories/BookmarkRepository.ts | 13 ++ .../repositories/BrowserSettingRepository.js | 8 - .../repositories/BrowserSettingRepository.ts | 8 + .../repositories/CompletionsRepository.js | 31 ---- .../repositories/CompletionsRepository.ts | 31 ++++ src/background/repositories/FindRepository.js | 19 -- src/background/repositories/FindRepository.ts | 19 ++ src/background/repositories/MarkRepository.js | 33 ---- src/background/repositories/MarkRepository.ts | 33 ++++ .../repositories/PersistentSettingRepository.js | 12 -- .../repositories/PersistentSettingRepository.ts | 12 ++ src/background/repositories/SettingRepository.js | 23 --- src/background/repositories/SettingRepository.ts | 23 +++ src/background/repositories/VersionRepository.js | 10 - src/background/repositories/VersionRepository.ts | 10 + src/background/usecases/AddonEnabledUseCase.js | 29 --- src/background/usecases/AddonEnabledUseCase.ts | 29 +++ src/background/usecases/CommandUseCase.js | 125 ------------- src/background/usecases/CommandUseCase.ts | 125 +++++++++++++ src/background/usecases/CompletionsUseCase.js | 205 --------------------- src/background/usecases/CompletionsUseCase.ts | 205 +++++++++++++++++++++ src/background/usecases/ConsoleUseCase.js | 61 ------ src/background/usecases/ConsoleUseCase.ts | 61 ++++++ src/background/usecases/FindUseCase.js | 24 --- src/background/usecases/FindUseCase.ts | 24 +++ src/background/usecases/LinkUseCase.js | 19 -- src/background/usecases/LinkUseCase.ts | 19 ++ src/background/usecases/MarkUseCase.js | 39 ---- src/background/usecases/MarkUseCase.ts | 39 ++++ src/background/usecases/SettingUseCase.js | 28 --- src/background/usecases/SettingUseCase.ts | 28 +++ src/background/usecases/TabSelectUseCase.js | 51 ----- src/background/usecases/TabSelectUseCase.ts | 51 +++++ src/background/usecases/TabUseCase.js | 77 -------- src/background/usecases/TabUseCase.ts | 77 ++++++++ src/background/usecases/VersionUseCase.js | 26 --- src/background/usecases/VersionUseCase.ts | 26 +++ src/background/usecases/ZoomUseCase.js | 35 ---- src/background/usecases/ZoomUseCase.ts | 35 ++++ src/background/usecases/filters.js | 72 -------- src/background/usecases/filters.ts | 72 ++++++++ src/background/usecases/parsers.js | 31 ---- src/background/usecases/parsers.ts | 31 ++++ src/console/actions/console.js | 96 ---------- src/console/actions/console.ts | 96 ++++++++++ src/console/actions/index.js | 13 -- src/console/actions/index.ts | 13 ++ src/console/components/Console.jsx | 149 --------------- src/console/components/Console.tsx | 149 +++++++++++++++ src/console/components/console/Completion.jsx | 86 --------- src/console/components/console/Completion.tsx | 86 +++++++++ src/console/components/console/CompletionItem.jsx | 28 --- src/console/components/console/CompletionItem.tsx | 28 +++ src/console/components/console/CompletionTitle.jsx | 14 -- src/console/components/console/CompletionTitle.tsx | 14 ++ src/console/components/console/Input.jsx | 43 ----- src/console/components/console/Input.tsx | 43 +++++ src/console/components/console/Message.jsx | 25 --- src/console/components/console/Message.tsx | 25 +++ src/console/index.jsx | 42 ----- src/console/index.tsx | 42 +++++ src/console/reducers/index.js | 102 ---------- src/console/reducers/index.ts | 102 ++++++++++ src/content/actions/addon.js | 19 -- src/content/actions/addon.ts | 19 ++ src/content/actions/find.js | 68 ------- src/content/actions/find.ts | 68 +++++++ src/content/actions/follow-controller.js | 30 --- src/content/actions/follow-controller.ts | 30 +++ src/content/actions/index.js | 31 ---- src/content/actions/index.ts | 31 ++++ src/content/actions/input.js | 16 -- src/content/actions/input.ts | 16 ++ src/content/actions/mark.js | 46 ----- src/content/actions/mark.ts | 46 +++++ src/content/actions/operation.js | 104 ----------- src/content/actions/operation.ts | 104 +++++++++++ src/content/actions/setting.js | 37 ---- src/content/actions/setting.ts | 37 ++++ src/content/components/common/follow.js | 200 -------------------- src/content/components/common/follow.ts | 200 ++++++++++++++++++++ src/content/components/common/hint.js | 49 ----- src/content/components/common/hint.ts | 49 +++++ src/content/components/common/index.js | 55 ------ src/content/components/common/index.ts | 55 ++++++ src/content/components/common/input.js | 75 -------- src/content/components/common/input.ts | 75 ++++++++ src/content/components/common/keymapper.js | 58 ------ src/content/components/common/keymapper.ts | 58 ++++++ src/content/components/common/mark.js | 74 -------- src/content/components/common/mark.ts | 74 ++++++++ src/content/components/frame-content.js | 3 - src/content/components/frame-content.ts | 3 + src/content/components/top-content/find.js | 41 ----- src/content/components/top-content/find.ts | 41 +++++ .../components/top-content/follow-controller.js | 147 --------------- .../components/top-content/follow-controller.ts | 147 +++++++++++++++ src/content/components/top-content/index.js | 41 ----- src/content/components/top-content/index.ts | 41 +++++ src/content/console-frames.js | 38 ---- src/content/console-frames.ts | 38 ++++ src/content/focuses.js | 13 -- src/content/focuses.ts | 13 ++ src/content/hint-key-producer.js | 33 ---- src/content/hint-key-producer.ts | 33 ++++ src/content/index.js | 21 --- src/content/index.ts | 21 +++ src/content/navigates.js | 78 -------- src/content/navigates.ts | 78 ++++++++ src/content/reducers/addon.js | 15 -- src/content/reducers/addon.ts | 15 ++ src/content/reducers/find.js | 17 -- src/content/reducers/find.ts | 17 ++ src/content/reducers/follow-controller.js | 30 --- src/content/reducers/follow-controller.ts | 30 +++ src/content/reducers/index.js | 11 -- src/content/reducers/index.ts | 11 ++ src/content/reducers/input.js | 18 -- src/content/reducers/input.ts | 18 ++ src/content/reducers/mark.js | 25 --- src/content/reducers/mark.ts | 25 +++ src/content/reducers/setting.js | 16 -- src/content/reducers/setting.ts | 16 ++ src/content/scrolls.js | 174 ----------------- src/content/scrolls.ts | 174 +++++++++++++++++ src/content/site-style.js | 26 --- src/content/site-style.ts | 26 +++ src/content/urls.js | 40 ---- src/content/urls.ts | 40 ++++ src/settings/actions/index.js | 7 - src/settings/actions/index.ts | 7 + src/settings/actions/setting.js | 63 ------- src/settings/actions/setting.ts | 63 +++++++ src/settings/components/form/BlacklistForm.jsx | 63 ------- src/settings/components/form/BlacklistForm.tsx | 63 +++++++ src/settings/components/form/KeymapsForm.jsx | 51 ----- src/settings/components/form/KeymapsForm.tsx | 51 +++++ src/settings/components/form/PropertiesForm.jsx | 65 ------- src/settings/components/form/PropertiesForm.tsx | 65 +++++++ src/settings/components/form/SearchForm.jsx | 92 --------- src/settings/components/form/SearchForm.tsx | 92 +++++++++ src/settings/components/index.jsx | 153 --------------- src/settings/components/index.tsx | 153 +++++++++++++++ src/settings/components/ui/AddButton.jsx | 12 -- src/settings/components/ui/AddButton.tsx | 12 ++ src/settings/components/ui/DeleteButton.jsx | 12 -- src/settings/components/ui/DeleteButton.tsx | 12 ++ src/settings/components/ui/Input.jsx | 60 ------ src/settings/components/ui/Input.tsx | 60 ++++++ src/settings/index.jsx | 22 --- src/settings/index.tsx | 22 +++ src/settings/keymaps.js | 74 -------- src/settings/keymaps.ts | 74 ++++++++ src/settings/reducers/setting.js | 35 ---- src/settings/reducers/setting.ts | 35 ++++ src/shared/blacklists.js | 13 -- src/shared/blacklists.ts | 13 ++ src/shared/messages.js | 71 ------- src/shared/messages.ts | 71 +++++++ src/shared/operations.js | 78 -------- src/shared/operations.ts | 78 ++++++++ src/shared/settings/default.js | 85 --------- src/shared/settings/default.ts | 85 +++++++++ src/shared/settings/properties.js | 24 --- src/shared/settings/properties.ts | 24 +++ src/shared/settings/storage.js | 32 ---- src/shared/settings/storage.ts | 32 ++++ src/shared/settings/validator.js | 76 -------- src/shared/settings/validator.ts | 76 ++++++++ src/shared/settings/values.js | 108 ----------- src/shared/settings/values.ts | 108 +++++++++++ src/shared/urls.js | 43 ----- src/shared/urls.ts | 43 +++++ src/shared/utils/dom.js | 108 ----------- src/shared/utils/dom.ts | 108 +++++++++++ src/shared/utils/keys.js | 89 --------- src/shared/utils/keys.ts | 89 +++++++++ src/shared/utils/re.js | 6 - src/shared/utils/re.ts | 6 + test/background/domains/GlobalMark.test.js | 11 -- test/background/domains/GlobalMark.test.ts | 11 ++ .../infrastructures/MemoryStorage.test.js | 44 ----- .../infrastructures/MemoryStorage.test.ts | 44 +++++ test/background/repositories/Mark.test.js | 26 --- test/background/repositories/Mark.test.ts | 26 +++ test/background/repositories/Version.js | 34 ---- test/background/repositories/Version.ts | 34 ++++ test/background/usecases/filters.test.js | 113 ------------ test/background/usecases/filters.test.ts | 113 ++++++++++++ test/background/usecases/parsers.test.js | 47 ----- test/background/usecases/parsers.test.ts | 47 +++++ test/console/actions/console.test.js | 70 ------- test/console/actions/console.test.ts | 70 +++++++ .../console/components/console/Completion.test.jsx | 168 ----------------- .../console/components/console/Completion.test.tsx | 168 +++++++++++++++++ test/console/reducers/console.test.js | 131 ------------- test/console/reducers/console.test.ts | 131 +++++++++++++ test/content/actions/follow-controller.test.js | 34 ---- test/content/actions/follow-controller.test.ts | 34 ++++ test/content/actions/input.test.js | 19 -- test/content/actions/input.test.ts | 19 ++ test/content/actions/mark.test.js | 35 ---- test/content/actions/mark.test.ts | 35 ++++ test/content/actions/setting.test.js | 35 ---- test/content/actions/setting.test.ts | 35 ++++ test/content/components/common/follow.test.js | 25 --- test/content/components/common/follow.test.ts | 25 +++ test/content/components/common/hint.test.js | 57 ------ test/content/components/common/hint.test.ts | 57 ++++++ test/content/components/common/input.test.js | 70 ------- test/content/components/common/input.test.ts | 70 +++++++ test/content/hint-key-producer.test.js | 24 --- test/content/hint-key-producer.test.ts | 24 +++ test/content/navigates.test.js | 137 -------------- test/content/navigates.test.ts | 137 ++++++++++++++ test/content/reducers/addon.test.js | 17 -- test/content/reducers/addon.test.ts | 17 ++ test/content/reducers/find.test.js | 22 --- test/content/reducers/find.test.ts | 22 +++ test/content/reducers/follow-controller.test.js | 47 ----- test/content/reducers/follow-controller.test.ts | 47 +++++ test/content/reducers/input.test.js | 25 --- test/content/reducers/input.test.ts | 25 +++ test/content/reducers/mark.test.js | 41 ----- test/content/reducers/mark.test.ts | 41 +++++ test/content/reducers/setting.test.js | 17 -- test/content/reducers/setting.test.ts | 17 ++ test/main.js | 6 - test/main.ts | 6 + .../components/form/BlacklistForm.test.jsx | 92 --------- .../components/form/BlacklistForm.test.tsx | 92 +++++++++ test/settings/components/form/KeymapsForm.test.jsx | 64 ------- test/settings/components/form/KeymapsForm.test.tsx | 64 +++++++ .../components/form/PropertiesForm.test.jsx | 104 ----------- .../components/form/PropertiesForm.test.tsx | 104 +++++++++++ .../components/form/SearchEngineForm.test.jsx | 128 ------------- .../components/form/SearchEngineForm.test.tsx | 128 +++++++++++++ test/settings/components/ui/input.test.jsx | 111 ----------- test/settings/components/ui/input.test.tsx | 111 +++++++++++ test/settings/reducers/setting.test.js | 55 ------ test/settings/reducers/setting.test.ts | 55 ++++++ test/shared/blacklists.test.js | 49 ----- test/shared/blacklists.test.ts | 49 +++++ test/shared/settings/validator.test.js | 81 -------- test/shared/settings/validator.test.ts | 81 ++++++++ test/shared/settings/values.test.js | 138 -------------- test/shared/settings/values.test.ts | 138 ++++++++++++++ test/shared/urls.test.js | 48 ----- test/shared/urls.test.ts | 48 +++++ test/shared/utils/keys.test.js | 164 ----------------- test/shared/utils/keys.test.ts | 164 +++++++++++++++++ test/shared/utils/re.test.js | 19 -- test/shared/utils/re.test.ts | 19 ++ 302 files changed, 7786 insertions(+), 7786 deletions(-) delete mode 100644 src/background/controllers/AddonEnabledController.js create mode 100644 src/background/controllers/AddonEnabledController.ts delete mode 100644 src/background/controllers/CommandController.js create mode 100644 src/background/controllers/CommandController.ts delete mode 100644 src/background/controllers/FindController.js create mode 100644 src/background/controllers/FindController.ts delete mode 100644 src/background/controllers/LinkController.js create mode 100644 src/background/controllers/LinkController.ts delete mode 100644 src/background/controllers/MarkController.js create mode 100644 src/background/controllers/MarkController.ts delete mode 100644 src/background/controllers/OperationController.js create mode 100644 src/background/controllers/OperationController.ts delete mode 100644 src/background/controllers/SettingController.js create mode 100644 src/background/controllers/SettingController.ts delete mode 100644 src/background/controllers/VersionController.js create mode 100644 src/background/controllers/VersionController.ts delete mode 100644 src/background/controllers/version.js create mode 100644 src/background/controllers/version.ts delete mode 100644 src/background/domains/CommandDocs.js create mode 100644 src/background/domains/CommandDocs.ts delete mode 100644 src/background/domains/CompletionGroup.js create mode 100644 src/background/domains/CompletionGroup.ts delete mode 100644 src/background/domains/CompletionItem.js create mode 100644 src/background/domains/CompletionItem.ts delete mode 100644 src/background/domains/Completions.js create mode 100644 src/background/domains/Completions.ts delete mode 100644 src/background/domains/GlobalMark.js create mode 100644 src/background/domains/GlobalMark.ts delete mode 100644 src/background/domains/Setting.js create mode 100644 src/background/domains/Setting.ts delete mode 100644 src/background/index.js create mode 100644 src/background/index.ts delete mode 100644 src/background/infrastructures/ConsoleClient.js create mode 100644 src/background/infrastructures/ConsoleClient.ts delete mode 100644 src/background/infrastructures/ContentMessageClient.js create mode 100644 src/background/infrastructures/ContentMessageClient.ts delete mode 100644 src/background/infrastructures/ContentMessageListener.js create mode 100644 src/background/infrastructures/ContentMessageListener.ts delete mode 100644 src/background/infrastructures/MemoryStorage.js create mode 100644 src/background/infrastructures/MemoryStorage.ts delete mode 100644 src/background/presenters/IndicatorPresenter.js create mode 100644 src/background/presenters/IndicatorPresenter.ts delete mode 100644 src/background/presenters/NotifyPresenter.js create mode 100644 src/background/presenters/NotifyPresenter.ts delete mode 100644 src/background/presenters/TabPresenter.js create mode 100644 src/background/presenters/TabPresenter.ts delete mode 100644 src/background/presenters/WindowPresenter.js create mode 100644 src/background/presenters/WindowPresenter.ts delete mode 100644 src/background/repositories/BookmarkRepository.js create mode 100644 src/background/repositories/BookmarkRepository.ts delete mode 100644 src/background/repositories/BrowserSettingRepository.js create mode 100644 src/background/repositories/BrowserSettingRepository.ts delete mode 100644 src/background/repositories/CompletionsRepository.js create mode 100644 src/background/repositories/CompletionsRepository.ts delete mode 100644 src/background/repositories/FindRepository.js create mode 100644 src/background/repositories/FindRepository.ts delete mode 100644 src/background/repositories/MarkRepository.js create mode 100644 src/background/repositories/MarkRepository.ts delete mode 100644 src/background/repositories/PersistentSettingRepository.js create mode 100644 src/background/repositories/PersistentSettingRepository.ts delete mode 100644 src/background/repositories/SettingRepository.js create mode 100644 src/background/repositories/SettingRepository.ts delete mode 100644 src/background/repositories/VersionRepository.js create mode 100644 src/background/repositories/VersionRepository.ts delete mode 100644 src/background/usecases/AddonEnabledUseCase.js create mode 100644 src/background/usecases/AddonEnabledUseCase.ts delete mode 100644 src/background/usecases/CommandUseCase.js create mode 100644 src/background/usecases/CommandUseCase.ts delete mode 100644 src/background/usecases/CompletionsUseCase.js create mode 100644 src/background/usecases/CompletionsUseCase.ts delete mode 100644 src/background/usecases/ConsoleUseCase.js create mode 100644 src/background/usecases/ConsoleUseCase.ts delete mode 100644 src/background/usecases/FindUseCase.js create mode 100644 src/background/usecases/FindUseCase.ts delete mode 100644 src/background/usecases/LinkUseCase.js create mode 100644 src/background/usecases/LinkUseCase.ts delete mode 100644 src/background/usecases/MarkUseCase.js create mode 100644 src/background/usecases/MarkUseCase.ts delete mode 100644 src/background/usecases/SettingUseCase.js create mode 100644 src/background/usecases/SettingUseCase.ts delete mode 100644 src/background/usecases/TabSelectUseCase.js create mode 100644 src/background/usecases/TabSelectUseCase.ts delete mode 100644 src/background/usecases/TabUseCase.js create mode 100644 src/background/usecases/TabUseCase.ts delete mode 100644 src/background/usecases/VersionUseCase.js create mode 100644 src/background/usecases/VersionUseCase.ts delete mode 100644 src/background/usecases/ZoomUseCase.js create mode 100644 src/background/usecases/ZoomUseCase.ts delete mode 100644 src/background/usecases/filters.js create mode 100644 src/background/usecases/filters.ts delete mode 100644 src/background/usecases/parsers.js create mode 100644 src/background/usecases/parsers.ts delete mode 100644 src/console/actions/console.js create mode 100644 src/console/actions/console.ts delete mode 100644 src/console/actions/index.js create mode 100644 src/console/actions/index.ts delete mode 100644 src/console/components/Console.jsx create mode 100644 src/console/components/Console.tsx delete mode 100644 src/console/components/console/Completion.jsx create mode 100644 src/console/components/console/Completion.tsx delete mode 100644 src/console/components/console/CompletionItem.jsx create mode 100644 src/console/components/console/CompletionItem.tsx delete mode 100644 src/console/components/console/CompletionTitle.jsx create mode 100644 src/console/components/console/CompletionTitle.tsx delete mode 100644 src/console/components/console/Input.jsx create mode 100644 src/console/components/console/Input.tsx delete mode 100644 src/console/components/console/Message.jsx create mode 100644 src/console/components/console/Message.tsx delete mode 100644 src/console/index.jsx create mode 100644 src/console/index.tsx delete mode 100644 src/console/reducers/index.js create mode 100644 src/console/reducers/index.ts delete mode 100644 src/content/actions/addon.js create mode 100644 src/content/actions/addon.ts delete mode 100644 src/content/actions/find.js create mode 100644 src/content/actions/find.ts delete mode 100644 src/content/actions/follow-controller.js create mode 100644 src/content/actions/follow-controller.ts delete mode 100644 src/content/actions/index.js create mode 100644 src/content/actions/index.ts delete mode 100644 src/content/actions/input.js create mode 100644 src/content/actions/input.ts delete mode 100644 src/content/actions/mark.js create mode 100644 src/content/actions/mark.ts delete mode 100644 src/content/actions/operation.js create mode 100644 src/content/actions/operation.ts delete mode 100644 src/content/actions/setting.js create mode 100644 src/content/actions/setting.ts delete mode 100644 src/content/components/common/follow.js create mode 100644 src/content/components/common/follow.ts delete mode 100644 src/content/components/common/hint.js create mode 100644 src/content/components/common/hint.ts delete mode 100644 src/content/components/common/index.js create mode 100644 src/content/components/common/index.ts delete mode 100644 src/content/components/common/input.js create mode 100644 src/content/components/common/input.ts delete mode 100644 src/content/components/common/keymapper.js create mode 100644 src/content/components/common/keymapper.ts delete mode 100644 src/content/components/common/mark.js create mode 100644 src/content/components/common/mark.ts delete mode 100644 src/content/components/frame-content.js create mode 100644 src/content/components/frame-content.ts delete mode 100644 src/content/components/top-content/find.js create mode 100644 src/content/components/top-content/find.ts delete mode 100644 src/content/components/top-content/follow-controller.js create mode 100644 src/content/components/top-content/follow-controller.ts delete mode 100644 src/content/components/top-content/index.js create mode 100644 src/content/components/top-content/index.ts delete mode 100644 src/content/console-frames.js create mode 100644 src/content/console-frames.ts delete mode 100644 src/content/focuses.js create mode 100644 src/content/focuses.ts delete mode 100644 src/content/hint-key-producer.js create mode 100644 src/content/hint-key-producer.ts delete mode 100644 src/content/index.js create mode 100644 src/content/index.ts delete mode 100644 src/content/navigates.js create mode 100644 src/content/navigates.ts delete mode 100644 src/content/reducers/addon.js create mode 100644 src/content/reducers/addon.ts delete mode 100644 src/content/reducers/find.js create mode 100644 src/content/reducers/find.ts delete mode 100644 src/content/reducers/follow-controller.js create mode 100644 src/content/reducers/follow-controller.ts delete mode 100644 src/content/reducers/index.js create mode 100644 src/content/reducers/index.ts delete mode 100644 src/content/reducers/input.js create mode 100644 src/content/reducers/input.ts delete mode 100644 src/content/reducers/mark.js create mode 100644 src/content/reducers/mark.ts delete mode 100644 src/content/reducers/setting.js create mode 100644 src/content/reducers/setting.ts delete mode 100644 src/content/scrolls.js create mode 100644 src/content/scrolls.ts delete mode 100644 src/content/site-style.js create mode 100644 src/content/site-style.ts delete mode 100644 src/content/urls.js create mode 100644 src/content/urls.ts delete mode 100644 src/settings/actions/index.js create mode 100644 src/settings/actions/index.ts delete mode 100644 src/settings/actions/setting.js create mode 100644 src/settings/actions/setting.ts delete mode 100644 src/settings/components/form/BlacklistForm.jsx create mode 100644 src/settings/components/form/BlacklistForm.tsx delete mode 100644 src/settings/components/form/KeymapsForm.jsx create mode 100644 src/settings/components/form/KeymapsForm.tsx delete mode 100644 src/settings/components/form/PropertiesForm.jsx create mode 100644 src/settings/components/form/PropertiesForm.tsx delete mode 100644 src/settings/components/form/SearchForm.jsx create mode 100644 src/settings/components/form/SearchForm.tsx delete mode 100644 src/settings/components/index.jsx create mode 100644 src/settings/components/index.tsx delete mode 100644 src/settings/components/ui/AddButton.jsx create mode 100644 src/settings/components/ui/AddButton.tsx delete mode 100644 src/settings/components/ui/DeleteButton.jsx create mode 100644 src/settings/components/ui/DeleteButton.tsx delete mode 100644 src/settings/components/ui/Input.jsx create mode 100644 src/settings/components/ui/Input.tsx delete mode 100644 src/settings/index.jsx create mode 100644 src/settings/index.tsx delete mode 100644 src/settings/keymaps.js create mode 100644 src/settings/keymaps.ts delete mode 100644 src/settings/reducers/setting.js create mode 100644 src/settings/reducers/setting.ts delete mode 100644 src/shared/blacklists.js create mode 100644 src/shared/blacklists.ts delete mode 100644 src/shared/messages.js create mode 100644 src/shared/messages.ts delete mode 100644 src/shared/operations.js create mode 100644 src/shared/operations.ts delete mode 100644 src/shared/settings/default.js create mode 100644 src/shared/settings/default.ts delete mode 100644 src/shared/settings/properties.js create mode 100644 src/shared/settings/properties.ts delete mode 100644 src/shared/settings/storage.js create mode 100644 src/shared/settings/storage.ts delete mode 100644 src/shared/settings/validator.js create mode 100644 src/shared/settings/validator.ts delete mode 100644 src/shared/settings/values.js create mode 100644 src/shared/settings/values.ts delete mode 100644 src/shared/urls.js create mode 100644 src/shared/urls.ts delete mode 100644 src/shared/utils/dom.js create mode 100644 src/shared/utils/dom.ts delete mode 100644 src/shared/utils/keys.js create mode 100644 src/shared/utils/keys.ts delete mode 100644 src/shared/utils/re.js create mode 100644 src/shared/utils/re.ts delete mode 100644 test/background/domains/GlobalMark.test.js create mode 100644 test/background/domains/GlobalMark.test.ts delete mode 100644 test/background/infrastructures/MemoryStorage.test.js create mode 100644 test/background/infrastructures/MemoryStorage.test.ts delete mode 100644 test/background/repositories/Mark.test.js create mode 100644 test/background/repositories/Mark.test.ts delete mode 100644 test/background/repositories/Version.js create mode 100644 test/background/repositories/Version.ts delete mode 100644 test/background/usecases/filters.test.js create mode 100644 test/background/usecases/filters.test.ts delete mode 100644 test/background/usecases/parsers.test.js create mode 100644 test/background/usecases/parsers.test.ts delete mode 100644 test/console/actions/console.test.js create mode 100644 test/console/actions/console.test.ts delete mode 100644 test/console/components/console/Completion.test.jsx create mode 100644 test/console/components/console/Completion.test.tsx delete mode 100644 test/console/reducers/console.test.js create mode 100644 test/console/reducers/console.test.ts delete mode 100644 test/content/actions/follow-controller.test.js create mode 100644 test/content/actions/follow-controller.test.ts delete mode 100644 test/content/actions/input.test.js create mode 100644 test/content/actions/input.test.ts delete mode 100644 test/content/actions/mark.test.js create mode 100644 test/content/actions/mark.test.ts delete mode 100644 test/content/actions/setting.test.js create mode 100644 test/content/actions/setting.test.ts delete mode 100644 test/content/components/common/follow.test.js create mode 100644 test/content/components/common/follow.test.ts delete mode 100644 test/content/components/common/hint.test.js create mode 100644 test/content/components/common/hint.test.ts delete mode 100644 test/content/components/common/input.test.js create mode 100644 test/content/components/common/input.test.ts delete mode 100644 test/content/hint-key-producer.test.js create mode 100644 test/content/hint-key-producer.test.ts delete mode 100644 test/content/navigates.test.js create mode 100644 test/content/navigates.test.ts delete mode 100644 test/content/reducers/addon.test.js create mode 100644 test/content/reducers/addon.test.ts delete mode 100644 test/content/reducers/find.test.js create mode 100644 test/content/reducers/find.test.ts delete mode 100644 test/content/reducers/follow-controller.test.js create mode 100644 test/content/reducers/follow-controller.test.ts delete mode 100644 test/content/reducers/input.test.js create mode 100644 test/content/reducers/input.test.ts delete mode 100644 test/content/reducers/mark.test.js create mode 100644 test/content/reducers/mark.test.ts delete mode 100644 test/content/reducers/setting.test.js create mode 100644 test/content/reducers/setting.test.ts delete mode 100644 test/main.js create mode 100644 test/main.ts delete mode 100644 test/settings/components/form/BlacklistForm.test.jsx create mode 100644 test/settings/components/form/BlacklistForm.test.tsx delete mode 100644 test/settings/components/form/KeymapsForm.test.jsx create mode 100644 test/settings/components/form/KeymapsForm.test.tsx delete mode 100644 test/settings/components/form/PropertiesForm.test.jsx create mode 100644 test/settings/components/form/PropertiesForm.test.tsx delete mode 100644 test/settings/components/form/SearchEngineForm.test.jsx create mode 100644 test/settings/components/form/SearchEngineForm.test.tsx delete mode 100644 test/settings/components/ui/input.test.jsx create mode 100644 test/settings/components/ui/input.test.tsx delete mode 100644 test/settings/reducers/setting.test.js create mode 100644 test/settings/reducers/setting.test.ts delete mode 100644 test/shared/blacklists.test.js create mode 100644 test/shared/blacklists.test.ts delete mode 100644 test/shared/settings/validator.test.js create mode 100644 test/shared/settings/validator.test.ts delete mode 100644 test/shared/settings/values.test.js create mode 100644 test/shared/settings/values.test.ts delete mode 100644 test/shared/urls.test.js create mode 100644 test/shared/urls.test.ts delete mode 100644 test/shared/utils/keys.test.js create mode 100644 test/shared/utils/keys.test.ts delete mode 100644 test/shared/utils/re.test.js create mode 100644 test/shared/utils/re.test.ts diff --git a/src/background/controllers/AddonEnabledController.js b/src/background/controllers/AddonEnabledController.js deleted file mode 100644 index 9a3a521..0000000 --- a/src/background/controllers/AddonEnabledController.js +++ /dev/null @@ -1,11 +0,0 @@ -import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; - -export default class AddonEnabledController { - constructor() { - this.addonEnabledUseCase = new AddonEnabledUseCase(); - } - - indicate(enabled) { - return this.addonEnabledUseCase.indicate(enabled); - } -} diff --git a/src/background/controllers/AddonEnabledController.ts b/src/background/controllers/AddonEnabledController.ts new file mode 100644 index 0000000..9a3a521 --- /dev/null +++ b/src/background/controllers/AddonEnabledController.ts @@ -0,0 +1,11 @@ +import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; + +export default class AddonEnabledController { + constructor() { + this.addonEnabledUseCase = new AddonEnabledUseCase(); + } + + indicate(enabled) { + return this.addonEnabledUseCase.indicate(enabled); + } +} diff --git a/src/background/controllers/CommandController.js b/src/background/controllers/CommandController.js deleted file mode 100644 index b113709..0000000 --- a/src/background/controllers/CommandController.js +++ /dev/null @@ -1,99 +0,0 @@ -import CompletionsUseCase from '../usecases/CompletionsUseCase'; -import CommandUseCase from '../usecases/CommandUseCase'; -import Completions from '../domains/Completions'; - -const trimStart = (str) => { - // NOTE String.trimStart is available on Firefox 61 - return str.replace(/^\s+/, ''); -}; - -export default class CommandController { - constructor() { - this.completionsUseCase = new CompletionsUseCase(); - this.commandIndicator = new CommandUseCase(); - } - - getCompletions(line) { - let trimmed = trimStart(line); - let words = trimmed.split(/ +/); - let name = words[0]; - if (words.length === 1) { - return this.completionsUseCase.queryConsoleCommand(name); - } - let keywords = trimStart(trimmed.slice(name.length)); - switch (words[0]) { - case 'o': - case 'open': - case 't': - case 'tabopen': - case 'w': - case 'winopen': - return this.completionsUseCase.queryOpen(name, keywords); - case 'b': - case 'buffer': - return this.completionsUseCase.queryBuffer(name, keywords); - case 'bd': - case 'bdel': - case 'bdelete': - case 'bdeletes': - return this.completionsUseCase.queryBdelete(name, keywords); - case 'bd!': - case 'bdel!': - case 'bdelete!': - case 'bdeletes!': - return this.completionsUseCase.queryBdeleteForce(name, keywords); - case 'set': - return this.completionsUseCase.querySet(name, keywords); - } - return Promise.resolve(Completions.empty()); - } - - // eslint-disable-next-line complexity - exec(line) { - let trimmed = trimStart(line); - let words = trimmed.split(/ +/); - let name = words[0]; - if (words[0].length === 0) { - return Promise.resolve(); - } - - let keywords = trimStart(trimmed.slice(name.length)); - switch (words[0]) { - case 'o': - case 'open': - return this.commandIndicator.open(keywords); - case 't': - case 'tabopen': - return this.commandIndicator.tabopen(keywords); - case 'w': - case 'winopen': - return this.commandIndicator.winopen(keywords); - case 'b': - case 'buffer': - return this.commandIndicator.buffer(keywords); - case 'bd': - case 'bdel': - case 'bdelete': - return this.commandIndicator.bdelete(false, keywords); - case 'bd!': - case 'bdel!': - case 'bdelete!': - return this.commandIndicator.bdelete(true, keywords); - case 'bdeletes': - return this.commandIndicator.bdeletes(false, keywords); - case 'bdeletes!': - return this.commandIndicator.bdeletes(true, keywords); - case 'addbookmark': - return this.commandIndicator.addbookmark(keywords); - case 'q': - case 'quit': - return this.commandIndicator.quit(); - case 'qa': - case 'quitall': - return this.commandIndicator.quitAll(); - case 'set': - return this.commandIndicator.set(keywords); - } - throw new Error(words[0] + ' command is not defined'); - } -} diff --git a/src/background/controllers/CommandController.ts b/src/background/controllers/CommandController.ts new file mode 100644 index 0000000..b113709 --- /dev/null +++ b/src/background/controllers/CommandController.ts @@ -0,0 +1,99 @@ +import CompletionsUseCase from '../usecases/CompletionsUseCase'; +import CommandUseCase from '../usecases/CommandUseCase'; +import Completions from '../domains/Completions'; + +const trimStart = (str) => { + // NOTE String.trimStart is available on Firefox 61 + return str.replace(/^\s+/, ''); +}; + +export default class CommandController { + constructor() { + this.completionsUseCase = new CompletionsUseCase(); + this.commandIndicator = new CommandUseCase(); + } + + getCompletions(line) { + let trimmed = trimStart(line); + let words = trimmed.split(/ +/); + let name = words[0]; + if (words.length === 1) { + return this.completionsUseCase.queryConsoleCommand(name); + } + let keywords = trimStart(trimmed.slice(name.length)); + switch (words[0]) { + case 'o': + case 'open': + case 't': + case 'tabopen': + case 'w': + case 'winopen': + return this.completionsUseCase.queryOpen(name, keywords); + case 'b': + case 'buffer': + return this.completionsUseCase.queryBuffer(name, keywords); + case 'bd': + case 'bdel': + case 'bdelete': + case 'bdeletes': + return this.completionsUseCase.queryBdelete(name, keywords); + case 'bd!': + case 'bdel!': + case 'bdelete!': + case 'bdeletes!': + return this.completionsUseCase.queryBdeleteForce(name, keywords); + case 'set': + return this.completionsUseCase.querySet(name, keywords); + } + return Promise.resolve(Completions.empty()); + } + + // eslint-disable-next-line complexity + exec(line) { + let trimmed = trimStart(line); + let words = trimmed.split(/ +/); + let name = words[0]; + if (words[0].length === 0) { + return Promise.resolve(); + } + + let keywords = trimStart(trimmed.slice(name.length)); + switch (words[0]) { + case 'o': + case 'open': + return this.commandIndicator.open(keywords); + case 't': + case 'tabopen': + return this.commandIndicator.tabopen(keywords); + case 'w': + case 'winopen': + return this.commandIndicator.winopen(keywords); + case 'b': + case 'buffer': + return this.commandIndicator.buffer(keywords); + case 'bd': + case 'bdel': + case 'bdelete': + return this.commandIndicator.bdelete(false, keywords); + case 'bd!': + case 'bdel!': + case 'bdelete!': + return this.commandIndicator.bdelete(true, keywords); + case 'bdeletes': + return this.commandIndicator.bdeletes(false, keywords); + case 'bdeletes!': + return this.commandIndicator.bdeletes(true, keywords); + case 'addbookmark': + return this.commandIndicator.addbookmark(keywords); + case 'q': + case 'quit': + return this.commandIndicator.quit(); + case 'qa': + case 'quitall': + return this.commandIndicator.quitAll(); + case 'set': + return this.commandIndicator.set(keywords); + } + throw new Error(words[0] + ' command is not defined'); + } +} diff --git a/src/background/controllers/FindController.js b/src/background/controllers/FindController.js deleted file mode 100644 index caeff98..0000000 --- a/src/background/controllers/FindController.js +++ /dev/null @@ -1,15 +0,0 @@ -import FindUseCase from '../usecases/FindUseCase'; - -export default class FindController { - constructor() { - this.findUseCase = new FindUseCase(); - } - - getKeyword() { - return this.findUseCase.getKeyword(); - } - - setKeyword(keyword) { - return this.findUseCase.setKeyword(keyword); - } -} diff --git a/src/background/controllers/FindController.ts b/src/background/controllers/FindController.ts new file mode 100644 index 0000000..caeff98 --- /dev/null +++ b/src/background/controllers/FindController.ts @@ -0,0 +1,15 @@ +import FindUseCase from '../usecases/FindUseCase'; + +export default class FindController { + constructor() { + this.findUseCase = new FindUseCase(); + } + + getKeyword() { + return this.findUseCase.getKeyword(); + } + + setKeyword(keyword) { + return this.findUseCase.setKeyword(keyword); + } +} diff --git a/src/background/controllers/LinkController.js b/src/background/controllers/LinkController.js deleted file mode 100644 index 7e395b1..0000000 --- a/src/background/controllers/LinkController.js +++ /dev/null @@ -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); - } -} diff --git a/src/background/controllers/LinkController.ts b/src/background/controllers/LinkController.ts new file mode 100644 index 0000000..7e395b1 --- /dev/null +++ b/src/background/controllers/LinkController.ts @@ -0,0 +1,15 @@ +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); + } +} diff --git a/src/background/controllers/MarkController.js b/src/background/controllers/MarkController.js deleted file mode 100644 index 0478369..0000000 --- a/src/background/controllers/MarkController.js +++ /dev/null @@ -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); - } -} diff --git a/src/background/controllers/MarkController.ts b/src/background/controllers/MarkController.ts new file mode 100644 index 0000000..0478369 --- /dev/null +++ b/src/background/controllers/MarkController.ts @@ -0,0 +1,15 @@ +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); + } +} diff --git a/src/background/controllers/OperationController.js b/src/background/controllers/OperationController.js deleted file mode 100644 index 416aa9c..0000000 --- a/src/background/controllers/OperationController.js +++ /dev/null @@ -1,77 +0,0 @@ -import operations from '../../shared/operations'; -import FindUseCase from '../usecases/FindUseCase'; -import ConsoleUseCase from '../usecases/ConsoleUseCase'; -import TabUseCase from '../usecases/TabUseCase'; -import TabSelectUseCase from '../usecases/TabSelectUseCase'; -import ZoomUseCase from '../usecases/ZoomUseCase'; - -export default class OperationController { - constructor() { - this.findUseCase = new FindUseCase(); - this.consoleUseCase = new ConsoleUseCase(); - this.tabUseCase = new TabUseCase(); - this.tabSelectUseCase = new TabSelectUseCase(); - this.zoomUseCase = new ZoomUseCase(); - } - - // eslint-disable-next-line complexity, max-lines-per-function - exec(operation) { - switch (operation.type) { - case operations.TAB_CLOSE: - return this.tabUseCase.close(false); - case operations.TAB_CLOSE_RIGHT: - return this.tabUseCase.closeRight(); - case operations.TAB_CLOSE_FORCE: - return this.tabUseCase.close(true); - case operations.TAB_REOPEN: - return this.tabUseCase.reopen(); - case operations.TAB_PREV: - return this.tabSelectUseCase.selectPrev(1); - case operations.TAB_NEXT: - return this.tabSelectUseCase.selectNext(1); - case operations.TAB_FIRST: - return this.tabSelectUseCase.selectFirst(); - case operations.TAB_LAST: - return this.tabSelectUseCase.selectLast(); - case operations.TAB_PREV_SEL: - return this.tabSelectUseCase.selectPrevSelected(); - case operations.TAB_RELOAD: - return this.tabUseCase.reload(operation.cache); - case operations.TAB_PIN: - return this.tabUseCase.setPinned(true); - case operations.TAB_UNPIN: - return this.tabUseCase.setPinned(false); - case operations.TAB_TOGGLE_PINNED: - return this.tabUseCase.togglePinned(); - case operations.TAB_DUPLICATE: - return this.tabUseCase.duplicate(); - case operations.PAGE_SOURCE: - return this.tabUseCase.openPageSource(); - case operations.PAGE_HOME: - return this.tabUseCase.openHome(operation.newTab); - case operations.ZOOM_IN: - return this.zoomUseCase.zoomIn(); - case operations.ZOOM_OUT: - return this.zoomUseCase.zoomOut(); - case operations.ZOOM_NEUTRAL: - return this.zoomUseCase.zoomNutoral(); - case operations.COMMAND_SHOW: - return this.consoleUseCase.showCommand(); - case operations.COMMAND_SHOW_OPEN: - return this.consoleUseCase.showOpenCommand(operation.alter); - case operations.COMMAND_SHOW_TABOPEN: - return this.consoleUseCase.showTabopenCommand(operation.alter); - case operations.COMMAND_SHOW_WINOPEN: - return this.consoleUseCase.showWinopenCommand(operation.alter); - case operations.COMMAND_SHOW_BUFFER: - return this.consoleUseCase.showBufferCommand(); - case operations.COMMAND_SHOW_ADDBOOKMARK: - return this.consoleUseCase.showAddbookmarkCommand(operation.alter); - case operations.FIND_START: - return this.findUseCase.findStart(); - case operations.CANCEL: - return this.consoleUseCase.hideConsole(); - } - } -} - diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts new file mode 100644 index 0000000..416aa9c --- /dev/null +++ b/src/background/controllers/OperationController.ts @@ -0,0 +1,77 @@ +import operations from '../../shared/operations'; +import FindUseCase from '../usecases/FindUseCase'; +import ConsoleUseCase from '../usecases/ConsoleUseCase'; +import TabUseCase from '../usecases/TabUseCase'; +import TabSelectUseCase from '../usecases/TabSelectUseCase'; +import ZoomUseCase from '../usecases/ZoomUseCase'; + +export default class OperationController { + constructor() { + this.findUseCase = new FindUseCase(); + this.consoleUseCase = new ConsoleUseCase(); + this.tabUseCase = new TabUseCase(); + this.tabSelectUseCase = new TabSelectUseCase(); + this.zoomUseCase = new ZoomUseCase(); + } + + // eslint-disable-next-line complexity, max-lines-per-function + exec(operation) { + switch (operation.type) { + case operations.TAB_CLOSE: + return this.tabUseCase.close(false); + case operations.TAB_CLOSE_RIGHT: + return this.tabUseCase.closeRight(); + case operations.TAB_CLOSE_FORCE: + return this.tabUseCase.close(true); + case operations.TAB_REOPEN: + return this.tabUseCase.reopen(); + case operations.TAB_PREV: + return this.tabSelectUseCase.selectPrev(1); + case operations.TAB_NEXT: + return this.tabSelectUseCase.selectNext(1); + case operations.TAB_FIRST: + return this.tabSelectUseCase.selectFirst(); + case operations.TAB_LAST: + return this.tabSelectUseCase.selectLast(); + case operations.TAB_PREV_SEL: + return this.tabSelectUseCase.selectPrevSelected(); + case operations.TAB_RELOAD: + return this.tabUseCase.reload(operation.cache); + case operations.TAB_PIN: + return this.tabUseCase.setPinned(true); + case operations.TAB_UNPIN: + return this.tabUseCase.setPinned(false); + case operations.TAB_TOGGLE_PINNED: + return this.tabUseCase.togglePinned(); + case operations.TAB_DUPLICATE: + return this.tabUseCase.duplicate(); + case operations.PAGE_SOURCE: + return this.tabUseCase.openPageSource(); + case operations.PAGE_HOME: + return this.tabUseCase.openHome(operation.newTab); + case operations.ZOOM_IN: + return this.zoomUseCase.zoomIn(); + case operations.ZOOM_OUT: + return this.zoomUseCase.zoomOut(); + case operations.ZOOM_NEUTRAL: + return this.zoomUseCase.zoomNutoral(); + case operations.COMMAND_SHOW: + return this.consoleUseCase.showCommand(); + case operations.COMMAND_SHOW_OPEN: + return this.consoleUseCase.showOpenCommand(operation.alter); + case operations.COMMAND_SHOW_TABOPEN: + return this.consoleUseCase.showTabopenCommand(operation.alter); + case operations.COMMAND_SHOW_WINOPEN: + return this.consoleUseCase.showWinopenCommand(operation.alter); + case operations.COMMAND_SHOW_BUFFER: + return this.consoleUseCase.showBufferCommand(); + case operations.COMMAND_SHOW_ADDBOOKMARK: + return this.consoleUseCase.showAddbookmarkCommand(operation.alter); + case operations.FIND_START: + return this.findUseCase.findStart(); + case operations.CANCEL: + return this.consoleUseCase.hideConsole(); + } + } +} + diff --git a/src/background/controllers/SettingController.js b/src/background/controllers/SettingController.js deleted file mode 100644 index e895d72..0000000 --- a/src/background/controllers/SettingController.js +++ /dev/null @@ -1,18 +0,0 @@ -import SettingUseCase from '../usecases/SettingUseCase'; -import ContentMessageClient from '../infrastructures/ContentMessageClient'; - -export default class SettingController { - constructor() { - this.settingUseCase = new SettingUseCase(); - this.contentMessageClient = new ContentMessageClient(); - } - - getSetting() { - return this.settingUseCase.get(); - } - - async reload() { - await this.settingUseCase.reload(); - this.contentMessageClient.broadcastSettingsChanged(); - } -} diff --git a/src/background/controllers/SettingController.ts b/src/background/controllers/SettingController.ts new file mode 100644 index 0000000..e895d72 --- /dev/null +++ b/src/background/controllers/SettingController.ts @@ -0,0 +1,18 @@ +import SettingUseCase from '../usecases/SettingUseCase'; +import ContentMessageClient from '../infrastructures/ContentMessageClient'; + +export default class SettingController { + constructor() { + this.settingUseCase = new SettingUseCase(); + this.contentMessageClient = new ContentMessageClient(); + } + + getSetting() { + return this.settingUseCase.get(); + } + + async reload() { + await this.settingUseCase.reload(); + this.contentMessageClient.broadcastSettingsChanged(); + } +} diff --git a/src/background/controllers/VersionController.js b/src/background/controllers/VersionController.js deleted file mode 100644 index c596f9b..0000000 --- a/src/background/controllers/VersionController.js +++ /dev/null @@ -1,11 +0,0 @@ -import VersionUseCase from '../usecases/VersionUseCase'; - -export default class VersionController { - constructor() { - this.versionUseCase = new VersionUseCase(); - } - - notify() { - this.versionUseCase.notify(); - } -} diff --git a/src/background/controllers/VersionController.ts b/src/background/controllers/VersionController.ts new file mode 100644 index 0000000..c596f9b --- /dev/null +++ b/src/background/controllers/VersionController.ts @@ -0,0 +1,11 @@ +import VersionUseCase from '../usecases/VersionUseCase'; + +export default class VersionController { + constructor() { + this.versionUseCase = new VersionUseCase(); + } + + notify() { + this.versionUseCase.notify(); + } +} diff --git a/src/background/controllers/version.js b/src/background/controllers/version.js deleted file mode 100644 index ec0f634..0000000 --- a/src/background/controllers/version.js +++ /dev/null @@ -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(); - }); - } -} diff --git a/src/background/controllers/version.ts b/src/background/controllers/version.ts new file mode 100644 index 0000000..ec0f634 --- /dev/null +++ b/src/background/controllers/version.ts @@ -0,0 +1,13 @@ +import VersionInteractor from '../usecases/version'; + +export default class VersionController { + constructor() { + this.versionInteractor = new VersionInteractor(); + } + + notifyIfUpdated() { + browser.runtime.onInstalled.addListener(() => { + return this.versionInteractor.notify(); + }); + } +} diff --git a/src/background/domains/CommandDocs.js b/src/background/domains/CommandDocs.js deleted file mode 100644 index 734c68e..0000000 --- a/src/background/domains/CommandDocs.js +++ /dev/null @@ -1,12 +0,0 @@ -export default { - set: 'Set a value of the property', - open: 'Open a URL or search by keywords in current tab', - tabopen: 'Open a URL or search by keywords in new tab', - winopen: 'Open a URL or search by keywords in new window', - buffer: 'Select tabs by matched keywords', - bdelete: 'Close a certain tab matched by keywords', - bdeletes: 'Close all tabs matched by keywords', - quit: 'Close the current tab', - quitall: 'Close all tabs', -}; - diff --git a/src/background/domains/CommandDocs.ts b/src/background/domains/CommandDocs.ts new file mode 100644 index 0000000..734c68e --- /dev/null +++ b/src/background/domains/CommandDocs.ts @@ -0,0 +1,12 @@ +export default { + set: 'Set a value of the property', + open: 'Open a URL or search by keywords in current tab', + tabopen: 'Open a URL or search by keywords in new tab', + winopen: 'Open a URL or search by keywords in new window', + buffer: 'Select tabs by matched keywords', + bdelete: 'Close a certain tab matched by keywords', + bdeletes: 'Close all tabs matched by keywords', + quit: 'Close the current tab', + quitall: 'Close all tabs', +}; + diff --git a/src/background/domains/CompletionGroup.js b/src/background/domains/CompletionGroup.js deleted file mode 100644 index 1749d72..0000000 --- a/src/background/domains/CompletionGroup.js +++ /dev/null @@ -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; - } -} diff --git a/src/background/domains/CompletionGroup.ts b/src/background/domains/CompletionGroup.ts new file mode 100644 index 0000000..1749d72 --- /dev/null +++ b/src/background/domains/CompletionGroup.ts @@ -0,0 +1,14 @@ +export default class CompletionGroup { + constructor(name, items) { + this.name0 = name; + this.items0 = items; + } + + get name() { + return this.name0; + } + + get items() { + return this.items0; + } +} diff --git a/src/background/domains/CompletionItem.js b/src/background/domains/CompletionItem.js deleted file mode 100644 index c7ad8a1..0000000 --- a/src/background/domains/CompletionItem.js +++ /dev/null @@ -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; - } -} diff --git a/src/background/domains/CompletionItem.ts b/src/background/domains/CompletionItem.ts new file mode 100644 index 0000000..c7ad8a1 --- /dev/null +++ b/src/background/domains/CompletionItem.ts @@ -0,0 +1,24 @@ +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; + } +} diff --git a/src/background/domains/Completions.js b/src/background/domains/Completions.js deleted file mode 100644 index f399743..0000000 --- a/src/background/domains/Completions.js +++ /dev/null @@ -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([]); diff --git a/src/background/domains/Completions.ts b/src/background/domains/Completions.ts new file mode 100644 index 0000000..f399743 --- /dev/null +++ b/src/background/domains/Completions.ts @@ -0,0 +1,27 @@ +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([]); diff --git a/src/background/domains/GlobalMark.js b/src/background/domains/GlobalMark.js deleted file mode 100644 index f0586f1..0000000 --- a/src/background/domains/GlobalMark.js +++ /dev/null @@ -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; - } -} diff --git a/src/background/domains/GlobalMark.ts b/src/background/domains/GlobalMark.ts new file mode 100644 index 0000000..f0586f1 --- /dev/null +++ b/src/background/domains/GlobalMark.ts @@ -0,0 +1,24 @@ +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; + } +} diff --git a/src/background/domains/Setting.js b/src/background/domains/Setting.js deleted file mode 100644 index 106ec0f..0000000 --- a/src/background/domains/Setting.js +++ /dev/null @@ -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: {}, - }); - } -} diff --git a/src/background/domains/Setting.ts b/src/background/domains/Setting.ts new file mode 100644 index 0000000..106ec0f --- /dev/null +++ b/src/background/domains/Setting.ts @@ -0,0 +1,51 @@ +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: {}, + }); + } +} diff --git a/src/background/index.js b/src/background/index.js deleted file mode 100644 index f9efd4d..0000000 --- a/src/background/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import ContentMessageListener from './infrastructures/ContentMessageListener'; -import SettingController from './controllers/SettingController'; -import VersionController from './controllers/VersionController'; - -let settingController = new SettingController(); -settingController.reload(); - -browser.runtime.onInstalled.addListener((details) => { - if (details.reason !== 'install' && details.reason !== 'update') { - return; - } - new VersionController().notify(); -}); - -new ContentMessageListener().run(); -browser.storage.onChanged.addListener((changes, area) => { - if (area !== 'local') { - return; - } - if (changes.settings) { - settingController.reload(); - } -}); diff --git a/src/background/index.ts b/src/background/index.ts new file mode 100644 index 0000000..f9efd4d --- /dev/null +++ b/src/background/index.ts @@ -0,0 +1,23 @@ +import ContentMessageListener from './infrastructures/ContentMessageListener'; +import SettingController from './controllers/SettingController'; +import VersionController from './controllers/VersionController'; + +let settingController = new SettingController(); +settingController.reload(); + +browser.runtime.onInstalled.addListener((details) => { + if (details.reason !== 'install' && details.reason !== 'update') { + return; + } + new VersionController().notify(); +}); + +new ContentMessageListener().run(); +browser.storage.onChanged.addListener((changes, area) => { + if (area !== 'local') { + return; + } + if (changes.settings) { + settingController.reload(); + } +}); diff --git a/src/background/infrastructures/ConsoleClient.js b/src/background/infrastructures/ConsoleClient.js deleted file mode 100644 index f691515..0000000 --- a/src/background/infrastructures/ConsoleClient.js +++ /dev/null @@ -1,37 +0,0 @@ -import messages from '../../shared/messages'; - -export default class ConsoleClient { - showCommand(tabId, command) { - return browser.tabs.sendMessage(tabId, { - type: messages.CONSOLE_SHOW_COMMAND, - command, - }); - } - - showFind(tabId) { - return browser.tabs.sendMessage(tabId, { - type: messages.CONSOLE_SHOW_FIND - }); - } - - showInfo(tabId, message) { - return browser.tabs.sendMessage(tabId, { - type: messages.CONSOLE_SHOW_INFO, - text: message, - }); - } - - showError(tabId, message) { - return browser.tabs.sendMessage(tabId, { - type: messages.CONSOLE_SHOW_ERROR, - text: message, - }); - } - - hide(tabId) { - return browser.tabs.sendMessage(tabId, { - type: messages.CONSOLE_HIDE, - }); - } -} - diff --git a/src/background/infrastructures/ConsoleClient.ts b/src/background/infrastructures/ConsoleClient.ts new file mode 100644 index 0000000..f691515 --- /dev/null +++ b/src/background/infrastructures/ConsoleClient.ts @@ -0,0 +1,37 @@ +import messages from '../../shared/messages'; + +export default class ConsoleClient { + showCommand(tabId, command) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_COMMAND, + command, + }); + } + + showFind(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_FIND + }); + } + + showInfo(tabId, message) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_INFO, + text: message, + }); + } + + showError(tabId, message) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_ERROR, + text: message, + }); + } + + hide(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_HIDE, + }); + } +} + diff --git a/src/background/infrastructures/ContentMessageClient.js b/src/background/infrastructures/ContentMessageClient.js deleted file mode 100644 index 0fab5a3..0000000 --- a/src/background/infrastructures/ContentMessageClient.js +++ /dev/null @@ -1,36 +0,0 @@ -import messages from '../../shared/messages'; - -export default class ContentMessageClient { - async broadcastSettingsChanged() { - let tabs = await browser.tabs.query({}); - for (let tab of tabs) { - if (tab.url.startsWith('about:')) { - continue; - } - browser.tabs.sendMessage(tab.id, { - type: messages.SETTINGS_CHANGED, - }); - } - } - - async getAddonEnabled(tabId) { - let { enabled } = await browser.tabs.sendMessage(tabId, { - type: messages.ADDON_ENABLED_QUERY, - }); - return enabled; - } - - toggleAddonEnabled(tabId) { - return browser.tabs.sendMessage(tabId, { - type: messages.ADDON_TOGGLE_ENABLED, - }); - } - - scrollTo(tabId, x, y) { - return browser.tabs.sendMessage(tabId, { - type: messages.TAB_SCROLL_TO, - x, - y, - }); - } -} diff --git a/src/background/infrastructures/ContentMessageClient.ts b/src/background/infrastructures/ContentMessageClient.ts new file mode 100644 index 0000000..0fab5a3 --- /dev/null +++ b/src/background/infrastructures/ContentMessageClient.ts @@ -0,0 +1,36 @@ +import messages from '../../shared/messages'; + +export default class ContentMessageClient { + async broadcastSettingsChanged() { + let tabs = await browser.tabs.query({}); + for (let tab of tabs) { + if (tab.url.startsWith('about:')) { + continue; + } + browser.tabs.sendMessage(tab.id, { + type: messages.SETTINGS_CHANGED, + }); + } + } + + async getAddonEnabled(tabId) { + let { enabled } = await browser.tabs.sendMessage(tabId, { + type: messages.ADDON_ENABLED_QUERY, + }); + return enabled; + } + + toggleAddonEnabled(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.ADDON_TOGGLE_ENABLED, + }); + } + + scrollTo(tabId, x, y) { + return browser.tabs.sendMessage(tabId, { + type: messages.TAB_SCROLL_TO, + x, + y, + }); + } +} diff --git a/src/background/infrastructures/ContentMessageListener.js b/src/background/infrastructures/ContentMessageListener.js deleted file mode 100644 index 5b0f62e..0000000 --- a/src/background/infrastructures/ContentMessageListener.js +++ /dev/null @@ -1,135 +0,0 @@ -import messages from '../../shared/messages'; -import CommandController from '../controllers/CommandController'; -import SettingController from '../controllers/SettingController'; -import FindController from '../controllers/FindController'; -import AddonEnabledController from '../controllers/AddonEnabledController'; -import LinkController from '../controllers/LinkController'; -import OperationController from '../controllers/OperationController'; -import MarkController from '../controllers/MarkController'; - -export default class ContentMessageListener { - constructor() { - this.settingController = new SettingController(); - this.commandController = new CommandController(); - this.findController = new FindController(); - this.addonEnabledController = new AddonEnabledController(); - this.linkController = new LinkController(); - this.backgroundOperationController = new OperationController(); - this.markController = new MarkController(); - - this.consolePorts = {}; - } - - run() { - browser.runtime.onMessage.addListener((message, sender) => { - try { - let ret = this.onMessage(message, sender); - if (!(ret instanceof Promise)) { - return {}; - } - return ret.catch((e) => { - return browser.tabs.sendMessage(sender.tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: e.message, - }); - }); - } catch (e) { - return browser.tabs.sendMessage(sender.tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: e.message, - }); - } - }); - browser.runtime.onConnect.addListener(this.onConnected.bind(this)); - } - - onMessage(message, sender) { - switch (message.type) { - case messages.CONSOLE_QUERY_COMPLETIONS: - return this.onConsoleQueryCompletions(message.text); - case messages.CONSOLE_ENTER_COMMAND: - return this.onConsoleEnterCommand(message.text); - case messages.SETTINGS_QUERY: - return this.onSettingsQuery(); - case messages.FIND_GET_KEYWORD: - return this.onFindGetKeyword(); - case messages.FIND_SET_KEYWORD: - return this.onFindSetKeyword(message.keyword); - case messages.ADDON_ENABLED_RESPONSE: - return this.onAddonEnabledResponse(message.enabled); - case messages.OPEN_URL: - return this.onOpenUrl( - message.newTab, message.url, sender.tab.id, message.background); - case messages.BACKGROUND_OPERATION: - return this.onBackgroundOperation(message.operation); - case messages.MARK_SET_GLOBAL: - return this.onMarkSetGlobal(message.key, message.x, message.y); - case messages.MARK_JUMP_GLOBAL: - return this.onMarkJumpGlobal(message.key); - case messages.CONSOLE_FRAME_MESSAGE: - return this.onConsoleFrameMessage(sender.tab.id, message.message); - } - } - - async onConsoleQueryCompletions(line) { - let completions = await this.commandController.getCompletions(line); - return Promise.resolve(completions.serialize()); - } - - onConsoleEnterCommand(text) { - return this.commandController.exec(text); - } - - - onSettingsQuery() { - return this.settingController.getSetting(); - } - - onFindGetKeyword() { - return this.findController.getKeyword(); - } - - onFindSetKeyword(keyword) { - return this.findController.setKeyword(keyword); - } - - onAddonEnabledResponse(enabled) { - return this.addonEnabledController.indicate(enabled); - } - - onOpenUrl(newTab, url, openerId, background) { - if (newTab) { - return this.linkController.openNewTab(url, openerId, background); - } - return this.linkController.openToTab(url, openerId); - } - - onBackgroundOperation(operation) { - return this.backgroundOperationController.exec(operation); - } - - onMarkSetGlobal(key, x, y) { - return this.markController.setGlobal(key, x, y); - } - - onMarkJumpGlobal(key) { - return this.markController.jumpGlobal(key); - } - - onConsoleFrameMessage(tabId, message) { - let port = this.consolePorts[tabId]; - if (!port) { - return; - } - port.postMessage(message); - } - - onConnected(port) { - if (port.name !== 'vimvixen-console') { - return; - } - - let id = port.sender.tab.id; - this.consolePorts[id] = port; - } -} diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts new file mode 100644 index 0000000..5b0f62e --- /dev/null +++ b/src/background/infrastructures/ContentMessageListener.ts @@ -0,0 +1,135 @@ +import messages from '../../shared/messages'; +import CommandController from '../controllers/CommandController'; +import SettingController from '../controllers/SettingController'; +import FindController from '../controllers/FindController'; +import AddonEnabledController from '../controllers/AddonEnabledController'; +import LinkController from '../controllers/LinkController'; +import OperationController from '../controllers/OperationController'; +import MarkController from '../controllers/MarkController'; + +export default class ContentMessageListener { + constructor() { + this.settingController = new SettingController(); + this.commandController = new CommandController(); + this.findController = new FindController(); + this.addonEnabledController = new AddonEnabledController(); + this.linkController = new LinkController(); + this.backgroundOperationController = new OperationController(); + this.markController = new MarkController(); + + this.consolePorts = {}; + } + + run() { + browser.runtime.onMessage.addListener((message, sender) => { + try { + let ret = this.onMessage(message, sender); + if (!(ret instanceof Promise)) { + return {}; + } + return ret.catch((e) => { + return browser.tabs.sendMessage(sender.tab.id, { + type: messages.CONSOLE_SHOW_ERROR, + text: e.message, + }); + }); + } catch (e) { + return browser.tabs.sendMessage(sender.tab.id, { + type: messages.CONSOLE_SHOW_ERROR, + text: e.message, + }); + } + }); + browser.runtime.onConnect.addListener(this.onConnected.bind(this)); + } + + onMessage(message, sender) { + switch (message.type) { + case messages.CONSOLE_QUERY_COMPLETIONS: + return this.onConsoleQueryCompletions(message.text); + case messages.CONSOLE_ENTER_COMMAND: + return this.onConsoleEnterCommand(message.text); + case messages.SETTINGS_QUERY: + return this.onSettingsQuery(); + case messages.FIND_GET_KEYWORD: + return this.onFindGetKeyword(); + case messages.FIND_SET_KEYWORD: + return this.onFindSetKeyword(message.keyword); + case messages.ADDON_ENABLED_RESPONSE: + return this.onAddonEnabledResponse(message.enabled); + case messages.OPEN_URL: + return this.onOpenUrl( + message.newTab, message.url, sender.tab.id, message.background); + case messages.BACKGROUND_OPERATION: + return this.onBackgroundOperation(message.operation); + case messages.MARK_SET_GLOBAL: + return this.onMarkSetGlobal(message.key, message.x, message.y); + case messages.MARK_JUMP_GLOBAL: + return this.onMarkJumpGlobal(message.key); + case messages.CONSOLE_FRAME_MESSAGE: + return this.onConsoleFrameMessage(sender.tab.id, message.message); + } + } + + async onConsoleQueryCompletions(line) { + let completions = await this.commandController.getCompletions(line); + return Promise.resolve(completions.serialize()); + } + + onConsoleEnterCommand(text) { + return this.commandController.exec(text); + } + + + onSettingsQuery() { + return this.settingController.getSetting(); + } + + onFindGetKeyword() { + return this.findController.getKeyword(); + } + + onFindSetKeyword(keyword) { + return this.findController.setKeyword(keyword); + } + + onAddonEnabledResponse(enabled) { + return this.addonEnabledController.indicate(enabled); + } + + onOpenUrl(newTab, url, openerId, background) { + if (newTab) { + return this.linkController.openNewTab(url, openerId, background); + } + return this.linkController.openToTab(url, openerId); + } + + onBackgroundOperation(operation) { + return this.backgroundOperationController.exec(operation); + } + + onMarkSetGlobal(key, x, y) { + return this.markController.setGlobal(key, x, y); + } + + onMarkJumpGlobal(key) { + return this.markController.jumpGlobal(key); + } + + onConsoleFrameMessage(tabId, message) { + let port = this.consolePorts[tabId]; + if (!port) { + return; + } + port.postMessage(message); + } + + onConnected(port) { + if (port.name !== 'vimvixen-console') { + return; + } + + let id = port.sender.tab.id; + this.consolePorts[id] = port; + } +} diff --git a/src/background/infrastructures/MemoryStorage.js b/src/background/infrastructures/MemoryStorage.js deleted file mode 100644 index 3a7e4f2..0000000 --- a/src/background/infrastructures/MemoryStorage.js +++ /dev/null @@ -1,19 +0,0 @@ -const db = {}; - -export default class MemoryStorage { - set(name, value) { - let data = JSON.stringify(value); - if (typeof data === 'undefined') { - throw new Error('value is not serializable'); - } - db[name] = data; - } - - get(name) { - let data = db[name]; - if (!data) { - return undefined; - } - return JSON.parse(data); - } -} diff --git a/src/background/infrastructures/MemoryStorage.ts b/src/background/infrastructures/MemoryStorage.ts new file mode 100644 index 0000000..3a7e4f2 --- /dev/null +++ b/src/background/infrastructures/MemoryStorage.ts @@ -0,0 +1,19 @@ +const db = {}; + +export default class MemoryStorage { + set(name, value) { + let data = JSON.stringify(value); + if (typeof data === 'undefined') { + throw new Error('value is not serializable'); + } + db[name] = data; + } + + get(name) { + let data = db[name]; + if (!data) { + return undefined; + } + return JSON.parse(data); + } +} diff --git a/src/background/presenters/IndicatorPresenter.js b/src/background/presenters/IndicatorPresenter.js deleted file mode 100644 index 5737519..0000000 --- a/src/background/presenters/IndicatorPresenter.js +++ /dev/null @@ -1,12 +0,0 @@ -export default class IndicatorPresenter { - indicate(enabled) { - let path = enabled - ? 'resources/enabled_32x32.png' - : 'resources/disabled_32x32.png'; - return browser.browserAction.setIcon({ path }); - } - - onClick(listener) { - browser.browserAction.onClicked.addListener(listener); - } -} diff --git a/src/background/presenters/IndicatorPresenter.ts b/src/background/presenters/IndicatorPresenter.ts new file mode 100644 index 0000000..5737519 --- /dev/null +++ b/src/background/presenters/IndicatorPresenter.ts @@ -0,0 +1,12 @@ +export default class IndicatorPresenter { + indicate(enabled) { + let path = enabled + ? 'resources/enabled_32x32.png' + : 'resources/disabled_32x32.png'; + return browser.browserAction.setIcon({ path }); + } + + onClick(listener) { + browser.browserAction.onClicked.addListener(listener); + } +} diff --git a/src/background/presenters/NotifyPresenter.js b/src/background/presenters/NotifyPresenter.js deleted file mode 100644 index a81f227..0000000 --- a/src/background/presenters/NotifyPresenter.js +++ /dev/null @@ -1,23 +0,0 @@ -const NOTIFICATION_ID = 'vimvixen-update'; - -export default class NotifyPresenter { - notify(title, message, onclick) { - const listener = (id) => { - if (id !== NOTIFICATION_ID) { - return; - } - - onclick(); - - browser.notifications.onClicked.removeListener(listener); - }; - browser.notifications.onClicked.addListener(listener); - - return browser.notifications.create(NOTIFICATION_ID, { - 'type': 'basic', - 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), - title, - message, - }); - } -} diff --git a/src/background/presenters/NotifyPresenter.ts b/src/background/presenters/NotifyPresenter.ts new file mode 100644 index 0000000..a81f227 --- /dev/null +++ b/src/background/presenters/NotifyPresenter.ts @@ -0,0 +1,23 @@ +const NOTIFICATION_ID = 'vimvixen-update'; + +export default class NotifyPresenter { + notify(title, message, onclick) { + const listener = (id) => { + if (id !== NOTIFICATION_ID) { + return; + } + + onclick(); + + browser.notifications.onClicked.removeListener(listener); + }; + browser.notifications.onClicked.addListener(listener); + + return browser.notifications.create(NOTIFICATION_ID, { + 'type': 'basic', + 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), + title, + message, + }); + } +} diff --git a/src/background/presenters/TabPresenter.js b/src/background/presenters/TabPresenter.js deleted file mode 100644 index 744be39..0000000 --- a/src/background/presenters/TabPresenter.js +++ /dev/null @@ -1,102 +0,0 @@ -import MemoryStorage from '../infrastructures/MemoryStorage'; - -const CURRENT_SELECTED_KEY = 'tabs.current.selected'; -const LAST_SELECTED_KEY = 'tabs.last.selected'; - -export default class TabPresenter { - open(url, tabId) { - return browser.tabs.update(tabId, { url }); - } - - create(url, opts) { - return browser.tabs.create({ url, ...opts }); - } - - async getCurrent() { - let tabs = await browser.tabs.query({ - active: true, currentWindow: true - }); - return tabs[0]; - } - - getAll() { - return browser.tabs.query({ currentWindow: true }); - } - - async getLastSelectedId() { - let cache = new MemoryStorage(); - let tabId = await cache.get(LAST_SELECTED_KEY); - if (tabId === null || typeof tabId === 'undefined') { - return; - } - return tabId; - } - - async getByKeyword(keyword, excludePinned = false) { - let tabs = await browser.tabs.query({ currentWindow: true }); - return tabs.filter((t) => { - return t.url.toLowerCase().includes(keyword.toLowerCase()) || - t.title && t.title.toLowerCase().includes(keyword.toLowerCase()); - }).filter((t) => { - return !(excludePinned && t.pinned); - }); - } - - select(tabId) { - return browser.tabs.update(tabId, { active: true }); - } - - remove(ids) { - return browser.tabs.remove(ids); - } - - async reopen() { - let window = await browser.windows.getCurrent(); - let sessions = await browser.sessions.getRecentlyClosed(); - let session = sessions.find((s) => { - return s.tab && s.tab.windowId === window.id; - }); - if (!session) { - return; - } - if (session.tab) { - return browser.sessions.restore(session.tab.sessionId); - } - return browser.sessions.restore(session.window.sessionId); - } - - reload(tabId, cache) { - return browser.tabs.reload(tabId, { bypassCache: cache }); - } - - setPinned(tabId, pinned) { - return browser.tabs.update(tabId, { pinned }); - } - - duplicate(id) { - return browser.tabs.duplicate(id); - } - - getZoom(tabId) { - return browser.tabs.getZoom(tabId); - } - - setZoom(tabId, factor) { - return browser.tabs.setZoom(tabId, factor); - } - - onSelected(listener) { - browser.tabs.onActivated.addListener(listener); - } -} - -let tabPresenter = new TabPresenter(); -tabPresenter.onSelected((tab) => { - let cache = new MemoryStorage(); - - let lastId = cache.get(CURRENT_SELECTED_KEY); - if (lastId) { - cache.set(LAST_SELECTED_KEY, lastId); - } - cache.set(CURRENT_SELECTED_KEY, tab.tabId); -}); diff --git a/src/background/presenters/TabPresenter.ts b/src/background/presenters/TabPresenter.ts new file mode 100644 index 0000000..744be39 --- /dev/null +++ b/src/background/presenters/TabPresenter.ts @@ -0,0 +1,102 @@ +import MemoryStorage from '../infrastructures/MemoryStorage'; + +const CURRENT_SELECTED_KEY = 'tabs.current.selected'; +const LAST_SELECTED_KEY = 'tabs.last.selected'; + +export default class TabPresenter { + open(url, tabId) { + return browser.tabs.update(tabId, { url }); + } + + create(url, opts) { + return browser.tabs.create({ url, ...opts }); + } + + async getCurrent() { + let tabs = await browser.tabs.query({ + active: true, currentWindow: true + }); + return tabs[0]; + } + + getAll() { + return browser.tabs.query({ currentWindow: true }); + } + + async getLastSelectedId() { + let cache = new MemoryStorage(); + let tabId = await cache.get(LAST_SELECTED_KEY); + if (tabId === null || typeof tabId === 'undefined') { + return; + } + return tabId; + } + + async getByKeyword(keyword, excludePinned = false) { + let tabs = await browser.tabs.query({ currentWindow: true }); + return tabs.filter((t) => { + return t.url.toLowerCase().includes(keyword.toLowerCase()) || + t.title && t.title.toLowerCase().includes(keyword.toLowerCase()); + }).filter((t) => { + return !(excludePinned && t.pinned); + }); + } + + select(tabId) { + return browser.tabs.update(tabId, { active: true }); + } + + remove(ids) { + return browser.tabs.remove(ids); + } + + async reopen() { + let window = await browser.windows.getCurrent(); + let sessions = await browser.sessions.getRecentlyClosed(); + let session = sessions.find((s) => { + return s.tab && s.tab.windowId === window.id; + }); + if (!session) { + return; + } + if (session.tab) { + return browser.sessions.restore(session.tab.sessionId); + } + return browser.sessions.restore(session.window.sessionId); + } + + reload(tabId, cache) { + return browser.tabs.reload(tabId, { bypassCache: cache }); + } + + setPinned(tabId, pinned) { + return browser.tabs.update(tabId, { pinned }); + } + + duplicate(id) { + return browser.tabs.duplicate(id); + } + + getZoom(tabId) { + return browser.tabs.getZoom(tabId); + } + + setZoom(tabId, factor) { + return browser.tabs.setZoom(tabId, factor); + } + + onSelected(listener) { + browser.tabs.onActivated.addListener(listener); + } +} + +let tabPresenter = new TabPresenter(); +tabPresenter.onSelected((tab) => { + let cache = new MemoryStorage(); + + let lastId = cache.get(CURRENT_SELECTED_KEY); + if (lastId) { + cache.set(LAST_SELECTED_KEY, lastId); + } + cache.set(CURRENT_SELECTED_KEY, tab.tabId); +}); diff --git a/src/background/presenters/WindowPresenter.js b/src/background/presenters/WindowPresenter.js deleted file mode 100644 index a82c4a2..0000000 --- a/src/background/presenters/WindowPresenter.js +++ /dev/null @@ -1,5 +0,0 @@ -export default class WindowPresenter { - create(url) { - return browser.windows.create({ url }); - } -} diff --git a/src/background/presenters/WindowPresenter.ts b/src/background/presenters/WindowPresenter.ts new file mode 100644 index 0000000..a82c4a2 --- /dev/null +++ b/src/background/presenters/WindowPresenter.ts @@ -0,0 +1,5 @@ +export default class WindowPresenter { + create(url) { + return browser.windows.create({ url }); + } +} diff --git a/src/background/repositories/BookmarkRepository.js b/src/background/repositories/BookmarkRepository.js deleted file mode 100644 index 99f7ec4..0000000 --- a/src/background/repositories/BookmarkRepository.js +++ /dev/null @@ -1,13 +0,0 @@ -export default class BookmarkRepository { - async create(title, url) { - let item = await browser.bookmarks.create({ - type: 'bookmark', - title, - url, - }); - if (!item) { - throw new Error('Could not create a bookmark'); - } - return item; - } -} diff --git a/src/background/repositories/BookmarkRepository.ts b/src/background/repositories/BookmarkRepository.ts new file mode 100644 index 0000000..99f7ec4 --- /dev/null +++ b/src/background/repositories/BookmarkRepository.ts @@ -0,0 +1,13 @@ +export default class BookmarkRepository { + async create(title, url) { + let item = await browser.bookmarks.create({ + type: 'bookmark', + title, + url, + }); + if (!item) { + throw new Error('Could not create a bookmark'); + } + return item; + } +} diff --git a/src/background/repositories/BrowserSettingRepository.js b/src/background/repositories/BrowserSettingRepository.js deleted file mode 100644 index a9d2c06..0000000 --- a/src/background/repositories/BrowserSettingRepository.js +++ /dev/null @@ -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); - } -} diff --git a/src/background/repositories/BrowserSettingRepository.ts b/src/background/repositories/BrowserSettingRepository.ts new file mode 100644 index 0000000..a9d2c06 --- /dev/null +++ b/src/background/repositories/BrowserSettingRepository.ts @@ -0,0 +1,8 @@ +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); + } +} diff --git a/src/background/repositories/CompletionsRepository.js b/src/background/repositories/CompletionsRepository.js deleted file mode 100644 index 1318d36..0000000 --- a/src/background/repositories/CompletionsRepository.js +++ /dev/null @@ -1,31 +0,0 @@ -export default class CompletionsRepository { - async queryBookmarks(keywords) { - let items = await browser.bookmarks.search({ query: keywords }); - return items.filter((item) => { - let url = undefined; - try { - url = new URL(item.url); - } catch (e) { - return false; - } - return item.type === 'bookmark' && url.protocol !== 'place:'; - }); - } - - queryHistories(keywords) { - return browser.history.search({ - text: keywords, - startTime: 0, - }); - } - - async queryTabs(keywords, excludePinned) { - let tabs = await browser.tabs.query({ currentWindow: true }); - return tabs.filter((t) => { - return t.url.toLowerCase().includes(keywords.toLowerCase()) || - t.title && t.title.toLowerCase().includes(keywords.toLowerCase()); - }).filter((t) => { - return !(excludePinned && t.pinned); - }); - } -} diff --git a/src/background/repositories/CompletionsRepository.ts b/src/background/repositories/CompletionsRepository.ts new file mode 100644 index 0000000..1318d36 --- /dev/null +++ b/src/background/repositories/CompletionsRepository.ts @@ -0,0 +1,31 @@ +export default class CompletionsRepository { + async queryBookmarks(keywords) { + let items = await browser.bookmarks.search({ query: keywords }); + return items.filter((item) => { + let url = undefined; + try { + url = new URL(item.url); + } catch (e) { + return false; + } + return item.type === 'bookmark' && url.protocol !== 'place:'; + }); + } + + queryHistories(keywords) { + return browser.history.search({ + text: keywords, + startTime: 0, + }); + } + + async queryTabs(keywords, excludePinned) { + let tabs = await browser.tabs.query({ currentWindow: true }); + return tabs.filter((t) => { + return t.url.toLowerCase().includes(keywords.toLowerCase()) || + t.title && t.title.toLowerCase().includes(keywords.toLowerCase()); + }).filter((t) => { + return !(excludePinned && t.pinned); + }); + } +} diff --git a/src/background/repositories/FindRepository.js b/src/background/repositories/FindRepository.js deleted file mode 100644 index 74ec914..0000000 --- a/src/background/repositories/FindRepository.js +++ /dev/null @@ -1,19 +0,0 @@ -import MemoryStorage from '../infrastructures/MemoryStorage'; - -const FIND_KEYWORD_KEY = 'find-keyword'; - -export default class FindRepository { - constructor() { - this.cache = new MemoryStorage(); - } - - getKeyword() { - return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY)); - } - - setKeyword(keyword) { - this.cache.set(FIND_KEYWORD_KEY, keyword); - return Promise.resolve(); - } -} - diff --git a/src/background/repositories/FindRepository.ts b/src/background/repositories/FindRepository.ts new file mode 100644 index 0000000..74ec914 --- /dev/null +++ b/src/background/repositories/FindRepository.ts @@ -0,0 +1,19 @@ +import MemoryStorage from '../infrastructures/MemoryStorage'; + +const FIND_KEYWORD_KEY = 'find-keyword'; + +export default class FindRepository { + constructor() { + this.cache = new MemoryStorage(); + } + + getKeyword() { + return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY)); + } + + setKeyword(keyword) { + this.cache.set(FIND_KEYWORD_KEY, keyword); + return Promise.resolve(); + } +} + diff --git a/src/background/repositories/MarkRepository.js b/src/background/repositories/MarkRepository.js deleted file mode 100644 index 282c712..0000000 --- a/src/background/repositories/MarkRepository.js +++ /dev/null @@ -1,33 +0,0 @@ -import MemoryStorage from '../infrastructures/MemoryStorage'; -import GlobalMark from '../domains/GlobalMark'; - -const MARK_KEY = 'mark'; - -export default class MarkRepository { - constructor() { - this.cache = new MemoryStorage(); - } - - getMark(key) { - let marks = this.getOrEmptyMarks(); - let data = marks[key]; - if (!data) { - return Promise.resolve(undefined); - } - let mark = new GlobalMark(data.tabId, data.url, data.x, data.y); - return Promise.resolve(mark); - } - - setMark(key, mark) { - let marks = this.getOrEmptyMarks(); - marks[key] = { tabId: mark.tabId, url: mark.url, x: mark.x, y: mark.y }; - this.cache.set(MARK_KEY, marks); - - return Promise.resolve(); - } - - getOrEmptyMarks() { - return this.cache.get(MARK_KEY) || {}; - } -} - diff --git a/src/background/repositories/MarkRepository.ts b/src/background/repositories/MarkRepository.ts new file mode 100644 index 0000000..282c712 --- /dev/null +++ b/src/background/repositories/MarkRepository.ts @@ -0,0 +1,33 @@ +import MemoryStorage from '../infrastructures/MemoryStorage'; +import GlobalMark from '../domains/GlobalMark'; + +const MARK_KEY = 'mark'; + +export default class MarkRepository { + constructor() { + this.cache = new MemoryStorage(); + } + + getMark(key) { + let marks = this.getOrEmptyMarks(); + let data = marks[key]; + if (!data) { + return Promise.resolve(undefined); + } + let mark = new GlobalMark(data.tabId, data.url, data.x, data.y); + return Promise.resolve(mark); + } + + setMark(key, mark) { + let marks = this.getOrEmptyMarks(); + marks[key] = { tabId: mark.tabId, url: mark.url, x: mark.x, y: mark.y }; + this.cache.set(MARK_KEY, marks); + + return Promise.resolve(); + } + + getOrEmptyMarks() { + return this.cache.get(MARK_KEY) || {}; + } +} + diff --git a/src/background/repositories/PersistentSettingRepository.js b/src/background/repositories/PersistentSettingRepository.js deleted file mode 100644 index 4cab107..0000000 --- a/src/background/repositories/PersistentSettingRepository.js +++ /dev/null @@ -1,12 +0,0 @@ -import Setting from '../domains/Setting'; - -export default class SettingRepository { - async load() { - let { settings } = await browser.storage.local.get('settings'); - if (!settings) { - return null; - } - return Setting.deserialize(settings); - } -} - diff --git a/src/background/repositories/PersistentSettingRepository.ts b/src/background/repositories/PersistentSettingRepository.ts new file mode 100644 index 0000000..4cab107 --- /dev/null +++ b/src/background/repositories/PersistentSettingRepository.ts @@ -0,0 +1,12 @@ +import Setting from '../domains/Setting'; + +export default class SettingRepository { + async load() { + let { settings } = await browser.storage.local.get('settings'); + if (!settings) { + return null; + } + return Setting.deserialize(settings); + } +} + diff --git a/src/background/repositories/SettingRepository.js b/src/background/repositories/SettingRepository.js deleted file mode 100644 index c4667a9..0000000 --- a/src/background/repositories/SettingRepository.js +++ /dev/null @@ -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); - } -} diff --git a/src/background/repositories/SettingRepository.ts b/src/background/repositories/SettingRepository.ts new file mode 100644 index 0000000..c4667a9 --- /dev/null +++ b/src/background/repositories/SettingRepository.ts @@ -0,0 +1,23 @@ +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); + } +} diff --git a/src/background/repositories/VersionRepository.js b/src/background/repositories/VersionRepository.js deleted file mode 100644 index 4c71d05..0000000 --- a/src/background/repositories/VersionRepository.js +++ /dev/null @@ -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 }); - } -} diff --git a/src/background/repositories/VersionRepository.ts b/src/background/repositories/VersionRepository.ts new file mode 100644 index 0000000..4c71d05 --- /dev/null +++ b/src/background/repositories/VersionRepository.ts @@ -0,0 +1,10 @@ +export default class VersionRepository { + async get() { + let { version } = await browser.storage.local.get('version'); + return version; + } + + update(version) { + return browser.storage.local.set({ version }); + } +} diff --git a/src/background/usecases/AddonEnabledUseCase.js b/src/background/usecases/AddonEnabledUseCase.js deleted file mode 100644 index bb2c347..0000000 --- a/src/background/usecases/AddonEnabledUseCase.js +++ /dev/null @@ -1,29 +0,0 @@ -import IndicatorPresenter from '../presenters/IndicatorPresenter'; -import TabPresenter from '../presenters/TabPresenter'; -import ContentMessageClient from '../infrastructures/ContentMessageClient'; - -export default class AddonEnabledUseCase { - constructor() { - this.indicatorPresentor = new IndicatorPresenter(); - - this.indicatorPresentor.onClick(tab => this.onIndicatorClick(tab.id)); - - this.tabPresenter = new TabPresenter(); - this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId)); - - this.contentMessageClient = new ContentMessageClient(); - } - - indicate(enabled) { - return this.indicatorPresentor.indicate(enabled); - } - - onIndicatorClick(tabId) { - return this.contentMessageClient.toggleAddonEnabled(tabId); - } - - async onTabSelected(tabId) { - let enabled = await this.contentMessageClient.getAddonEnabled(tabId); - return this.indicatorPresentor.indicate(enabled); - } -} diff --git a/src/background/usecases/AddonEnabledUseCase.ts b/src/background/usecases/AddonEnabledUseCase.ts new file mode 100644 index 0000000..bb2c347 --- /dev/null +++ b/src/background/usecases/AddonEnabledUseCase.ts @@ -0,0 +1,29 @@ +import IndicatorPresenter from '../presenters/IndicatorPresenter'; +import TabPresenter from '../presenters/TabPresenter'; +import ContentMessageClient from '../infrastructures/ContentMessageClient'; + +export default class AddonEnabledUseCase { + constructor() { + this.indicatorPresentor = new IndicatorPresenter(); + + this.indicatorPresentor.onClick(tab => this.onIndicatorClick(tab.id)); + + this.tabPresenter = new TabPresenter(); + this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId)); + + this.contentMessageClient = new ContentMessageClient(); + } + + indicate(enabled) { + return this.indicatorPresentor.indicate(enabled); + } + + onIndicatorClick(tabId) { + return this.contentMessageClient.toggleAddonEnabled(tabId); + } + + async onTabSelected(tabId) { + let enabled = await this.contentMessageClient.getAddonEnabled(tabId); + return this.indicatorPresentor.indicate(enabled); + } +} diff --git a/src/background/usecases/CommandUseCase.js b/src/background/usecases/CommandUseCase.js deleted file mode 100644 index 9ec46fe..0000000 --- a/src/background/usecases/CommandUseCase.js +++ /dev/null @@ -1,125 +0,0 @@ -import * as parsers from './parsers'; -import * as urls from '../../shared/urls'; -import TabPresenter from '../presenters/TabPresenter'; -import WindowPresenter from '../presenters/WindowPresenter'; -import SettingRepository from '../repositories/SettingRepository'; -import BookmarkRepository from '../repositories/BookmarkRepository'; -import ConsoleClient from '../infrastructures/ConsoleClient'; -import ContentMessageClient from '../infrastructures/ContentMessageClient'; -import * as properties from 'shared/settings/properties'; - -export default class CommandIndicator { - constructor() { - this.tabPresenter = new TabPresenter(); - this.windowPresenter = new WindowPresenter(); - this.settingRepository = new SettingRepository(); - this.bookmarkRepository = new BookmarkRepository(); - this.consoleClient = new ConsoleClient(); - - this.contentMessageClient = new ContentMessageClient(); - } - - async open(keywords) { - let url = await this.urlOrSearch(keywords); - return this.tabPresenter.open(url); - } - - async tabopen(keywords) { - let url = await this.urlOrSearch(keywords); - return this.tabPresenter.create(url); - } - - async winopen(keywords) { - let url = await this.urlOrSearch(keywords); - return this.windowPresenter.create(url); - } - - // eslint-disable-next-line max-statements - async buffer(keywords) { - if (keywords.length === 0) { - return; - } - - if (!isNaN(keywords)) { - let tabs = await this.tabPresenter.getAll(); - let index = parseInt(keywords, 10) - 1; - if (index < 0 || tabs.length <= index) { - throw new RangeError(`tab ${index + 1} does not exist`); - } - return this.tabPresenter.select(tabs[index].id); - } else if (keywords.trim() === '%') { - // Select current window - return; - } else if (keywords.trim() === '#') { - // Select last selected window - let lastId = await this.tabPresenter.getLastSelectedId(); - if (typeof lastId === 'undefined' || lastId === null) { - throw new Error('No last selected tab'); - } - return this.tabPresenter.select(lastId); - } - - let current = await this.tabPresenter.getCurrent(); - let tabs = await this.tabPresenter.getByKeyword(keywords); - if (tabs.length === 0) { - throw new RangeError('No matching buffer for ' + keywords); - } - for (let tab of tabs) { - if (tab.index > current.index) { - return this.tabPresenter.select(tab.id); - } - } - return this.tabPresenter.select(tabs[0].id); - } - - async bdelete(force, keywords) { - let excludePinned = !force; - let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned); - if (tabs.length === 0) { - throw new Error('No matching buffer for ' + keywords); - } else if (tabs.length > 1) { - throw new Error('More than one match for ' + keywords); - } - return this.tabPresenter.remove([tabs[0].id]); - } - - async bdeletes(force, keywords) { - let excludePinned = !force; - let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned); - let ids = tabs.map(tab => tab.id); - return this.tabPresenter.remove(ids); - } - - async quit() { - let tab = await this.tabPresenter.getCurrent(); - return this.tabPresenter.remove([tab.id]); - } - - async quitAll() { - let tabs = await this.tabPresenter.getAll(); - let ids = tabs.map(tab => tab.id); - this.tabPresenter.remove(ids); - } - - async addbookmark(title) { - let tab = await this.tabPresenter.getCurrent(); - let item = await this.bookmarkRepository.create(title, tab.url); - let message = 'Saved current page: ' + item.url; - return this.consoleClient.showInfo(tab.id, message); - } - - async set(keywords) { - if (keywords.length === 0) { - return; - } - let [name, value] = parsers.parseSetOption(keywords, properties.types); - await this.settingRepository.setProperty(name, value); - - return this.contentMessageClient.broadcastSettingsChanged(); - } - - async urlOrSearch(keywords) { - let settings = await this.settingRepository.get(); - return urls.searchUrl(keywords, settings.search); - } -} diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts new file mode 100644 index 0000000..9ec46fe --- /dev/null +++ b/src/background/usecases/CommandUseCase.ts @@ -0,0 +1,125 @@ +import * as parsers from './parsers'; +import * as urls from '../../shared/urls'; +import TabPresenter from '../presenters/TabPresenter'; +import WindowPresenter from '../presenters/WindowPresenter'; +import SettingRepository from '../repositories/SettingRepository'; +import BookmarkRepository from '../repositories/BookmarkRepository'; +import ConsoleClient from '../infrastructures/ConsoleClient'; +import ContentMessageClient from '../infrastructures/ContentMessageClient'; +import * as properties from 'shared/settings/properties'; + +export default class CommandIndicator { + constructor() { + this.tabPresenter = new TabPresenter(); + this.windowPresenter = new WindowPresenter(); + this.settingRepository = new SettingRepository(); + this.bookmarkRepository = new BookmarkRepository(); + this.consoleClient = new ConsoleClient(); + + this.contentMessageClient = new ContentMessageClient(); + } + + async open(keywords) { + let url = await this.urlOrSearch(keywords); + return this.tabPresenter.open(url); + } + + async tabopen(keywords) { + let url = await this.urlOrSearch(keywords); + return this.tabPresenter.create(url); + } + + async winopen(keywords) { + let url = await this.urlOrSearch(keywords); + return this.windowPresenter.create(url); + } + + // eslint-disable-next-line max-statements + async buffer(keywords) { + if (keywords.length === 0) { + return; + } + + if (!isNaN(keywords)) { + let tabs = await this.tabPresenter.getAll(); + let index = parseInt(keywords, 10) - 1; + if (index < 0 || tabs.length <= index) { + throw new RangeError(`tab ${index + 1} does not exist`); + } + return this.tabPresenter.select(tabs[index].id); + } else if (keywords.trim() === '%') { + // Select current window + return; + } else if (keywords.trim() === '#') { + // Select last selected window + let lastId = await this.tabPresenter.getLastSelectedId(); + if (typeof lastId === 'undefined' || lastId === null) { + throw new Error('No last selected tab'); + } + return this.tabPresenter.select(lastId); + } + + let current = await this.tabPresenter.getCurrent(); + let tabs = await this.tabPresenter.getByKeyword(keywords); + if (tabs.length === 0) { + throw new RangeError('No matching buffer for ' + keywords); + } + for (let tab of tabs) { + if (tab.index > current.index) { + return this.tabPresenter.select(tab.id); + } + } + return this.tabPresenter.select(tabs[0].id); + } + + async bdelete(force, keywords) { + let excludePinned = !force; + let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned); + if (tabs.length === 0) { + throw new Error('No matching buffer for ' + keywords); + } else if (tabs.length > 1) { + throw new Error('More than one match for ' + keywords); + } + return this.tabPresenter.remove([tabs[0].id]); + } + + async bdeletes(force, keywords) { + let excludePinned = !force; + let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned); + let ids = tabs.map(tab => tab.id); + return this.tabPresenter.remove(ids); + } + + async quit() { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.remove([tab.id]); + } + + async quitAll() { + let tabs = await this.tabPresenter.getAll(); + let ids = tabs.map(tab => tab.id); + this.tabPresenter.remove(ids); + } + + async addbookmark(title) { + let tab = await this.tabPresenter.getCurrent(); + let item = await this.bookmarkRepository.create(title, tab.url); + let message = 'Saved current page: ' + item.url; + return this.consoleClient.showInfo(tab.id, message); + } + + async set(keywords) { + if (keywords.length === 0) { + return; + } + let [name, value] = parsers.parseSetOption(keywords, properties.types); + await this.settingRepository.setProperty(name, value); + + return this.contentMessageClient.broadcastSettingsChanged(); + } + + async urlOrSearch(keywords) { + let settings = await this.settingRepository.get(); + return urls.searchUrl(keywords, settings.search); + } +} diff --git a/src/background/usecases/CompletionsUseCase.js b/src/background/usecases/CompletionsUseCase.js deleted file mode 100644 index 7dc30ac..0000000 --- a/src/background/usecases/CompletionsUseCase.js +++ /dev/null @@ -1,205 +0,0 @@ -import CompletionItem from '../domains/CompletionItem'; -import CompletionGroup from '../domains/CompletionGroup'; -import Completions from '../domains/Completions'; -import CommandDocs from '../domains/CommandDocs'; -import CompletionsRepository from '../repositories/CompletionsRepository'; -import * as filters from './filters'; -import SettingRepository from '../repositories/SettingRepository'; -import TabPresenter from '../presenters/TabPresenter'; -import * as properties from '../../shared/settings/properties'; - -const COMPLETION_ITEM_LIMIT = 10; - -export default class CompletionsUseCase { - constructor() { - this.tabPresenter = new TabPresenter(); - this.completionsRepository = new CompletionsRepository(); - this.settingRepository = new SettingRepository(); - } - - queryConsoleCommand(prefix) { - let keys = Object.keys(CommandDocs); - let items = keys - .filter(name => name.startsWith(prefix)) - .map(name => ({ - caption: name, - content: name, - url: CommandDocs[name], - })); - - if (items.length === 0) { - return Promise.resolve(Completions.empty()); - } - return Promise.resolve( - new Completions([new CompletionGroup('Console Command', items)]) - ); - } - - async queryOpen(name, keywords) { - let settings = await this.settingRepository.get(); - let groups = []; - - let complete = settings.properties.complete || properties.defaults.complete; - for (let c of complete) { - if (c === 's') { - // eslint-disable-next-line no-await-in-loop - let engines = await this.querySearchEngineItems(name, keywords); - if (engines.length > 0) { - groups.push(new CompletionGroup('Search Engines', engines)); - } - } else if (c === 'h') { - // eslint-disable-next-line no-await-in-loop - let histories = await this.queryHistoryItems(name, keywords); - if (histories.length > 0) { - groups.push(new CompletionGroup('History', histories)); - } - } else if (c === 'b') { - // eslint-disable-next-line no-await-in-loop - let bookmarks = await this.queryBookmarkItems(name, keywords); - if (bookmarks.length > 0) { - groups.push(new CompletionGroup('Bookmarks', bookmarks)); - } - } - } - return new Completions(groups); - } - - // eslint-disable-next-line max-statements - async queryBuffer(name, keywords) { - let lastId = await this.tabPresenter.getLastSelectedId(); - let trimmed = keywords.trim(); - let tabs = []; - if (trimmed.length > 0 && !isNaN(trimmed)) { - let all = await this.tabPresenter.getAll(); - let index = parseInt(trimmed, 10) - 1; - if (index >= 0 && index < all.length) { - tabs = [all[index]]; - } - } else if (trimmed === '%') { - let all = await this.tabPresenter.getAll(); - let tab = all.find(t => t.active); - tabs = [tab]; - } else if (trimmed === '#') { - if (typeof lastId !== 'undefined' && lastId !== null) { - let all = await this.tabPresenter.getAll(); - let tab = all.find(t => t.id === lastId); - tabs = [tab]; - } - } else { - tabs = await this.completionsRepository.queryTabs(keywords, false); - } - const flag = (tab) => { - if (tab.active) { - return '%'; - } else if (tab.id === lastId) { - return '#'; - } - return ' '; - }; - let items = tabs.map(tab => new CompletionItem({ - caption: tab.index + 1 + ': ' + flag(tab) + ' ' + tab.title, - content: name + ' ' + tab.title, - url: tab.url, - icon: tab.favIconUrl - })); - if (items.length === 0) { - return Promise.resolve(Completions.empty()); - } - return new Completions([new CompletionGroup('Buffers', items)]); - } - - queryBdelete(name, keywords) { - return this.queryTabs(name, true, keywords); - } - - queryBdeleteForce(name, keywords) { - return this.queryTabs(name, false, keywords); - } - - querySet(name, keywords) { - let items = Object.keys(properties.docs).map((key) => { - if (properties.types[key] === 'boolean') { - return [ - new CompletionItem({ - caption: key, - content: name + ' ' + key, - url: 'Enable ' + properties.docs[key], - }), - new CompletionItem({ - caption: 'no' + key, - content: name + ' no' + key, - url: 'Disable ' + properties.docs[key], - }), - ]; - } - return [ - new CompletionItem({ - caption: key, - content: name + ' ' + key, - url: 'Set ' + properties.docs[key], - }) - ]; - }); - items = items.reduce((acc, val) => acc.concat(val), []); - items = items.filter((item) => { - return item.caption.startsWith(keywords); - }); - if (items.length === 0) { - return Promise.resolve(Completions.empty()); - } - return Promise.resolve( - new Completions([new CompletionGroup('Properties', items)]) - ); - } - - async queryTabs(name, excludePinned, args) { - let tabs = await this.completionsRepository.queryTabs(args, excludePinned); - let items = tabs.map(tab => new CompletionItem({ - caption: tab.title, - content: name + ' ' + tab.title, - url: tab.url, - icon: tab.favIconUrl - })); - if (items.length === 0) { - return Promise.resolve(Completions.empty()); - } - return new Completions([new CompletionGroup('Buffers', items)]); - } - - async querySearchEngineItems(name, keywords) { - let settings = await this.settingRepository.get(); - let engines = Object.keys(settings.search.engines) - .filter(key => key.startsWith(keywords)); - return engines.map(key => new CompletionItem({ - caption: key, - content: name + ' ' + key, - })); - } - - async queryHistoryItems(name, keywords) { - let histories = await this.completionsRepository.queryHistories(keywords); - histories = [histories] - .map(filters.filterBlankTitle) - .map(filters.filterHttp) - .map(filters.filterByTailingSlash) - .map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT)) - .map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0] - .sort((x, y) => x.visitCount < y.visitCount) - .slice(0, COMPLETION_ITEM_LIMIT); - return histories.map(page => new CompletionItem({ - caption: page.title, - content: name + ' ' + page.url, - url: page.url - })); - } - - async queryBookmarkItems(name, keywords) { - let bookmarks = await this.completionsRepository.queryBookmarks(keywords); - return bookmarks.slice(0, COMPLETION_ITEM_LIMIT) - .map(page => new CompletionItem({ - caption: page.title, - content: name + ' ' + page.url, - url: page.url - })); - } -} diff --git a/src/background/usecases/CompletionsUseCase.ts b/src/background/usecases/CompletionsUseCase.ts new file mode 100644 index 0000000..7dc30ac --- /dev/null +++ b/src/background/usecases/CompletionsUseCase.ts @@ -0,0 +1,205 @@ +import CompletionItem from '../domains/CompletionItem'; +import CompletionGroup from '../domains/CompletionGroup'; +import Completions from '../domains/Completions'; +import CommandDocs from '../domains/CommandDocs'; +import CompletionsRepository from '../repositories/CompletionsRepository'; +import * as filters from './filters'; +import SettingRepository from '../repositories/SettingRepository'; +import TabPresenter from '../presenters/TabPresenter'; +import * as properties from '../../shared/settings/properties'; + +const COMPLETION_ITEM_LIMIT = 10; + +export default class CompletionsUseCase { + constructor() { + this.tabPresenter = new TabPresenter(); + this.completionsRepository = new CompletionsRepository(); + this.settingRepository = new SettingRepository(); + } + + queryConsoleCommand(prefix) { + let keys = Object.keys(CommandDocs); + let items = keys + .filter(name => name.startsWith(prefix)) + .map(name => ({ + caption: name, + content: name, + url: CommandDocs[name], + })); + + if (items.length === 0) { + return Promise.resolve(Completions.empty()); + } + return Promise.resolve( + new Completions([new CompletionGroup('Console Command', items)]) + ); + } + + async queryOpen(name, keywords) { + let settings = await this.settingRepository.get(); + let groups = []; + + let complete = settings.properties.complete || properties.defaults.complete; + for (let c of complete) { + if (c === 's') { + // eslint-disable-next-line no-await-in-loop + let engines = await this.querySearchEngineItems(name, keywords); + if (engines.length > 0) { + groups.push(new CompletionGroup('Search Engines', engines)); + } + } else if (c === 'h') { + // eslint-disable-next-line no-await-in-loop + let histories = await this.queryHistoryItems(name, keywords); + if (histories.length > 0) { + groups.push(new CompletionGroup('History', histories)); + } + } else if (c === 'b') { + // eslint-disable-next-line no-await-in-loop + let bookmarks = await this.queryBookmarkItems(name, keywords); + if (bookmarks.length > 0) { + groups.push(new CompletionGroup('Bookmarks', bookmarks)); + } + } + } + return new Completions(groups); + } + + // eslint-disable-next-line max-statements + async queryBuffer(name, keywords) { + let lastId = await this.tabPresenter.getLastSelectedId(); + let trimmed = keywords.trim(); + let tabs = []; + if (trimmed.length > 0 && !isNaN(trimmed)) { + let all = await this.tabPresenter.getAll(); + let index = parseInt(trimmed, 10) - 1; + if (index >= 0 && index < all.length) { + tabs = [all[index]]; + } + } else if (trimmed === '%') { + let all = await this.tabPresenter.getAll(); + let tab = all.find(t => t.active); + tabs = [tab]; + } else if (trimmed === '#') { + if (typeof lastId !== 'undefined' && lastId !== null) { + let all = await this.tabPresenter.getAll(); + let tab = all.find(t => t.id === lastId); + tabs = [tab]; + } + } else { + tabs = await this.completionsRepository.queryTabs(keywords, false); + } + const flag = (tab) => { + if (tab.active) { + return '%'; + } else if (tab.id === lastId) { + return '#'; + } + return ' '; + }; + let items = tabs.map(tab => new CompletionItem({ + caption: tab.index + 1 + ': ' + flag(tab) + ' ' + tab.title, + content: name + ' ' + tab.title, + url: tab.url, + icon: tab.favIconUrl + })); + if (items.length === 0) { + return Promise.resolve(Completions.empty()); + } + return new Completions([new CompletionGroup('Buffers', items)]); + } + + queryBdelete(name, keywords) { + return this.queryTabs(name, true, keywords); + } + + queryBdeleteForce(name, keywords) { + return this.queryTabs(name, false, keywords); + } + + querySet(name, keywords) { + let items = Object.keys(properties.docs).map((key) => { + if (properties.types[key] === 'boolean') { + return [ + new CompletionItem({ + caption: key, + content: name + ' ' + key, + url: 'Enable ' + properties.docs[key], + }), + new CompletionItem({ + caption: 'no' + key, + content: name + ' no' + key, + url: 'Disable ' + properties.docs[key], + }), + ]; + } + return [ + new CompletionItem({ + caption: key, + content: name + ' ' + key, + url: 'Set ' + properties.docs[key], + }) + ]; + }); + items = items.reduce((acc, val) => acc.concat(val), []); + items = items.filter((item) => { + return item.caption.startsWith(keywords); + }); + if (items.length === 0) { + return Promise.resolve(Completions.empty()); + } + return Promise.resolve( + new Completions([new CompletionGroup('Properties', items)]) + ); + } + + async queryTabs(name, excludePinned, args) { + let tabs = await this.completionsRepository.queryTabs(args, excludePinned); + let items = tabs.map(tab => new CompletionItem({ + caption: tab.title, + content: name + ' ' + tab.title, + url: tab.url, + icon: tab.favIconUrl + })); + if (items.length === 0) { + return Promise.resolve(Completions.empty()); + } + return new Completions([new CompletionGroup('Buffers', items)]); + } + + async querySearchEngineItems(name, keywords) { + let settings = await this.settingRepository.get(); + let engines = Object.keys(settings.search.engines) + .filter(key => key.startsWith(keywords)); + return engines.map(key => new CompletionItem({ + caption: key, + content: name + ' ' + key, + })); + } + + async queryHistoryItems(name, keywords) { + let histories = await this.completionsRepository.queryHistories(keywords); + histories = [histories] + .map(filters.filterBlankTitle) + .map(filters.filterHttp) + .map(filters.filterByTailingSlash) + .map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT)) + .map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0] + .sort((x, y) => x.visitCount < y.visitCount) + .slice(0, COMPLETION_ITEM_LIMIT); + return histories.map(page => new CompletionItem({ + caption: page.title, + content: name + ' ' + page.url, + url: page.url + })); + } + + async queryBookmarkItems(name, keywords) { + let bookmarks = await this.completionsRepository.queryBookmarks(keywords); + return bookmarks.slice(0, COMPLETION_ITEM_LIMIT) + .map(page => new CompletionItem({ + caption: page.title, + content: name + ' ' + page.url, + url: page.url + })); + } +} diff --git a/src/background/usecases/ConsoleUseCase.js b/src/background/usecases/ConsoleUseCase.js deleted file mode 100644 index e8e5d4a..0000000 --- a/src/background/usecases/ConsoleUseCase.js +++ /dev/null @@ -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); - } -} diff --git a/src/background/usecases/ConsoleUseCase.ts b/src/background/usecases/ConsoleUseCase.ts new file mode 100644 index 0000000..e8e5d4a --- /dev/null +++ b/src/background/usecases/ConsoleUseCase.ts @@ -0,0 +1,61 @@ +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); + } +} diff --git a/src/background/usecases/FindUseCase.js b/src/background/usecases/FindUseCase.js deleted file mode 100644 index 224e4a9..0000000 --- a/src/background/usecases/FindUseCase.js +++ /dev/null @@ -1,24 +0,0 @@ -import FindRepository from '../repositories/FindRepository'; -import TabPresenter from '../presenters/TabPresenter'; -import ConsoleClient from '../infrastructures/ConsoleClient'; - -export default class FindUseCase { - constructor() { - this.tabPresenter = new TabPresenter(); - this.findRepository = new FindRepository(); - this.consoleClient = new ConsoleClient(); - } - - getKeyword() { - return this.findRepository.getKeyword(); - } - - setKeyword(keyword) { - return this.findRepository.setKeyword(keyword); - } - - async findStart() { - let tab = await this.tabPresenter.getCurrent(); - return this.consoleClient.showFind(tab.id); - } -} diff --git a/src/background/usecases/FindUseCase.ts b/src/background/usecases/FindUseCase.ts new file mode 100644 index 0000000..224e4a9 --- /dev/null +++ b/src/background/usecases/FindUseCase.ts @@ -0,0 +1,24 @@ +import FindRepository from '../repositories/FindRepository'; +import TabPresenter from '../presenters/TabPresenter'; +import ConsoleClient from '../infrastructures/ConsoleClient'; + +export default class FindUseCase { + constructor() { + this.tabPresenter = new TabPresenter(); + this.findRepository = new FindRepository(); + this.consoleClient = new ConsoleClient(); + } + + getKeyword() { + return this.findRepository.getKeyword(); + } + + setKeyword(keyword) { + return this.findRepository.setKeyword(keyword); + } + + async findStart() { + let tab = await this.tabPresenter.getCurrent(); + return this.consoleClient.showFind(tab.id); + } +} diff --git a/src/background/usecases/LinkUseCase.js b/src/background/usecases/LinkUseCase.js deleted file mode 100644 index 89412c5..0000000 --- a/src/background/usecases/LinkUseCase.js +++ /dev/null @@ -1,19 +0,0 @@ -import SettingRepository from '../repositories/SettingRepository'; -import TabPresenter from '../presenters/TabPresenter'; - -export default class LinkUseCase { - constructor() { - this.settingRepository = new SettingRepository(); - this.tabPresenter = new TabPresenter(); - } - - openToTab(url, tabId) { - return this.tabPresenter.open(url, tabId); - } - - openNewTab(url, openerId, background) { - return this.tabPresenter.create(url, { - openerTabId: openerId, active: !background - }); - } -} diff --git a/src/background/usecases/LinkUseCase.ts b/src/background/usecases/LinkUseCase.ts new file mode 100644 index 0000000..89412c5 --- /dev/null +++ b/src/background/usecases/LinkUseCase.ts @@ -0,0 +1,19 @@ +import SettingRepository from '../repositories/SettingRepository'; +import TabPresenter from '../presenters/TabPresenter'; + +export default class LinkUseCase { + constructor() { + this.settingRepository = new SettingRepository(); + this.tabPresenter = new TabPresenter(); + } + + openToTab(url, tabId) { + return this.tabPresenter.open(url, tabId); + } + + openNewTab(url, openerId, background) { + return this.tabPresenter.create(url, { + openerTabId: openerId, active: !background + }); + } +} diff --git a/src/background/usecases/MarkUseCase.js b/src/background/usecases/MarkUseCase.js deleted file mode 100644 index 39c796b..0000000 --- a/src/background/usecases/MarkUseCase.js +++ /dev/null @@ -1,39 +0,0 @@ -import GlobalMark from '../domains/GlobalMark'; -import TabPresenter from '../presenters/TabPresenter'; -import MarkRepository from '../repositories/MarkRepository'; -import ConsoleClient from '../infrastructures/ConsoleClient'; -import ContentMessageClient from '../infrastructures/ContentMessageClient'; - -export default class MarkUseCase { - constructor() { - this.tabPresenter = new TabPresenter(); - this.markRepository = new MarkRepository(); - this.consoleClient = new ConsoleClient(); - this.contentMessageClient = new ContentMessageClient(); - } - - async setGlobal(key, x, y) { - let tab = await this.tabPresenter.getCurrent(); - let mark = new GlobalMark(tab.id, tab.url, x, y); - return this.markRepository.setMark(key, mark); - } - - async jumpGlobal(key) { - let current = await this.tabPresenter.getCurrent(); - - let mark = await this.markRepository.getMark(key); - if (!mark) { - return this.consoleClient.showError(current.id, 'Mark is not set'); - } - - return this.contentMessageClient.scrollTo( - mark.tabId, mark.x, mark.y - ).then(() => { - return this.tabPresenter.select(mark.tabId); - }).catch(async() => { - 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, mark2); - }); - } -} diff --git a/src/background/usecases/MarkUseCase.ts b/src/background/usecases/MarkUseCase.ts new file mode 100644 index 0000000..39c796b --- /dev/null +++ b/src/background/usecases/MarkUseCase.ts @@ -0,0 +1,39 @@ +import GlobalMark from '../domains/GlobalMark'; +import TabPresenter from '../presenters/TabPresenter'; +import MarkRepository from '../repositories/MarkRepository'; +import ConsoleClient from '../infrastructures/ConsoleClient'; +import ContentMessageClient from '../infrastructures/ContentMessageClient'; + +export default class MarkUseCase { + constructor() { + this.tabPresenter = new TabPresenter(); + this.markRepository = new MarkRepository(); + this.consoleClient = new ConsoleClient(); + this.contentMessageClient = new ContentMessageClient(); + } + + async setGlobal(key, x, y) { + let tab = await this.tabPresenter.getCurrent(); + let mark = new GlobalMark(tab.id, tab.url, x, y); + return this.markRepository.setMark(key, mark); + } + + async jumpGlobal(key) { + let current = await this.tabPresenter.getCurrent(); + + let mark = await this.markRepository.getMark(key); + if (!mark) { + return this.consoleClient.showError(current.id, 'Mark is not set'); + } + + return this.contentMessageClient.scrollTo( + mark.tabId, mark.x, mark.y + ).then(() => { + return this.tabPresenter.select(mark.tabId); + }).catch(async() => { + 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, mark2); + }); + } +} diff --git a/src/background/usecases/SettingUseCase.js b/src/background/usecases/SettingUseCase.js deleted file mode 100644 index 9e17408..0000000 --- a/src/background/usecases/SettingUseCase.js +++ /dev/null @@ -1,28 +0,0 @@ -import Setting from '../domains/Setting'; -// eslint-disable-next-line max-len -import PersistentSettingRepository from '../repositories/PersistentSettingRepository'; -import SettingRepository from '../repositories/SettingRepository'; - -export default class SettingUseCase { - constructor() { - this.persistentSettingRepository = new PersistentSettingRepository(); - this.settingRepository = new SettingRepository(); - } - - get() { - return this.settingRepository.get(); - } - - async reload() { - let settings = await this.persistentSettingRepository.load(); - if (!settings) { - settings = Setting.defaultSettings(); - } - - let value = settings.value(); - - this.settingRepository.update(value); - - return value; - } -} diff --git a/src/background/usecases/SettingUseCase.ts b/src/background/usecases/SettingUseCase.ts new file mode 100644 index 0000000..9e17408 --- /dev/null +++ b/src/background/usecases/SettingUseCase.ts @@ -0,0 +1,28 @@ +import Setting from '../domains/Setting'; +// eslint-disable-next-line max-len +import PersistentSettingRepository from '../repositories/PersistentSettingRepository'; +import SettingRepository from '../repositories/SettingRepository'; + +export default class SettingUseCase { + constructor() { + this.persistentSettingRepository = new PersistentSettingRepository(); + this.settingRepository = new SettingRepository(); + } + + get() { + return this.settingRepository.get(); + } + + async reload() { + let settings = await this.persistentSettingRepository.load(); + if (!settings) { + settings = Setting.defaultSettings(); + } + + let value = settings.value(); + + this.settingRepository.update(value); + + return value; + } +} diff --git a/src/background/usecases/TabSelectUseCase.js b/src/background/usecases/TabSelectUseCase.js deleted file mode 100644 index 16b3e14..0000000 --- a/src/background/usecases/TabSelectUseCase.js +++ /dev/null @@ -1,51 +0,0 @@ -import TabPresenter from '../presenters/TabPresenter'; - -export default class TabSelectUseCase { - constructor() { - this.tabPresenter = new TabPresenter(); - } - - async selectPrev(count) { - let tabs = await this.tabPresenter.getAll(); - if (tabs.length < 2) { - return; - } - let tab = tabs.find(t => t.active); - if (!tab) { - return; - } - let select = (tab.index - count + tabs.length) % tabs.length; - return this.tabPresenter.select(tabs[select].id); - } - - async selectNext(count) { - let tabs = await this.tabPresenter.getAll(); - if (tabs.length < 2) { - return; - } - let tab = tabs.find(t => t.active); - if (!tab) { - return; - } - let select = (tab.index + count) % tabs.length; - return this.tabPresenter.select(tabs[select].id); - } - - async selectFirst() { - let tabs = await this.tabPresenter.getAll(); - return this.tabPresenter.select(tabs[0].id); - } - - async selectLast() { - let tabs = await this.tabPresenter.getAll(); - return this.tabPresenter.select(tabs[tabs.length - 1].id); - } - - async selectPrevSelected() { - let tabId = await this.tabPresenter.getLastSelectedId(); - if (tabId === null || typeof tabId === 'undefined') { - return; - } - this.tabPresenter.select(tabId); - } -} diff --git a/src/background/usecases/TabSelectUseCase.ts b/src/background/usecases/TabSelectUseCase.ts new file mode 100644 index 0000000..16b3e14 --- /dev/null +++ b/src/background/usecases/TabSelectUseCase.ts @@ -0,0 +1,51 @@ +import TabPresenter from '../presenters/TabPresenter'; + +export default class TabSelectUseCase { + constructor() { + this.tabPresenter = new TabPresenter(); + } + + async selectPrev(count) { + let tabs = await this.tabPresenter.getAll(); + if (tabs.length < 2) { + return; + } + let tab = tabs.find(t => t.active); + if (!tab) { + return; + } + let select = (tab.index - count + tabs.length) % tabs.length; + return this.tabPresenter.select(tabs[select].id); + } + + async selectNext(count) { + let tabs = await this.tabPresenter.getAll(); + if (tabs.length < 2) { + return; + } + let tab = tabs.find(t => t.active); + if (!tab) { + return; + } + let select = (tab.index + count) % tabs.length; + return this.tabPresenter.select(tabs[select].id); + } + + async selectFirst() { + let tabs = await this.tabPresenter.getAll(); + return this.tabPresenter.select(tabs[0].id); + } + + async selectLast() { + let tabs = await this.tabPresenter.getAll(); + return this.tabPresenter.select(tabs[tabs.length - 1].id); + } + + async selectPrevSelected() { + let tabId = await this.tabPresenter.getLastSelectedId(); + if (tabId === null || typeof tabId === 'undefined') { + return; + } + this.tabPresenter.select(tabId); + } +} diff --git a/src/background/usecases/TabUseCase.js b/src/background/usecases/TabUseCase.js deleted file mode 100644 index d930842..0000000 --- a/src/background/usecases/TabUseCase.js +++ /dev/null @@ -1,77 +0,0 @@ -import TabPresenter from '../presenters/TabPresenter'; -import BrowserSettingRepository from '../repositories/BrowserSettingRepository'; - -export default class TabUseCase { - constructor() { - this.tabPresenter = new TabPresenter(); - this.browserSettingRepository = new BrowserSettingRepository(); - } - - async close(force) { - let tab = await this.tabPresenter.getCurrent(); - if (!force && tab.pinned) { - return; - } - return this.tabPresenter.remove([tab.id]); - } - - async closeRight() { - let tabs = await this.tabPresenter.getAll(); - tabs.sort((t1, t2) => t1.index - t2.index); - let index = tabs.findIndex(t => t.active); - if (index < 0) { - return; - } - for (let i = index + 1; i < tabs.length; ++i) { - let tab = tabs[i]; - if (!tab.pinned) { - this.tabPresenter.remove(tab.id); - } - } - } - - reopen() { - return this.tabPresenter.reopen(); - } - - async reload(cache) { - let tab = await this.tabPresenter.getCurrent(); - return this.tabPresenter.reload(tab.id, cache); - } - - async setPinned(pinned) { - let tab = await this.tabPresenter.getCurrent(); - return this.tabPresenter.setPinned(tab.id, pinned); - } - - async togglePinned() { - let tab = await this.tabPresenter.getCurrent(); - return this.tabPresenter.setPinned(tab.id, !tab.pinned); - } - - async duplicate() { - let tab = await this.tabPresenter.getCurrent(); - return this.tabPresenter.duplicate(tab.id); - } - - async openPageSource() { - let tab = await this.tabPresenter.getCurrent(); - let url = 'view-source:' + tab.url; - return this.tabPresenter.create(url); - } - - async openHome(newTab) { - let tab = await this.tabPresenter.getCurrent(); - let urls = await this.browserSettingRepository.getHomepageUrls(); - if (urls.length === 1 && urls[0] === 'about:home') { - // eslint-disable-next-line max-len - throw new Error('Cannot open Firefox Home (about:home) by WebExtensions, set your custom URLs'); - } - if (urls.length === 1 && !newTab) { - return this.tabPresenter.open(urls[0], tab.id); - } - for (let url of urls) { - this.tabPresenter.create(url); - } - } -} diff --git a/src/background/usecases/TabUseCase.ts b/src/background/usecases/TabUseCase.ts new file mode 100644 index 0000000..d930842 --- /dev/null +++ b/src/background/usecases/TabUseCase.ts @@ -0,0 +1,77 @@ +import TabPresenter from '../presenters/TabPresenter'; +import BrowserSettingRepository from '../repositories/BrowserSettingRepository'; + +export default class TabUseCase { + constructor() { + this.tabPresenter = new TabPresenter(); + this.browserSettingRepository = new BrowserSettingRepository(); + } + + async close(force) { + let tab = await this.tabPresenter.getCurrent(); + if (!force && tab.pinned) { + return; + } + return this.tabPresenter.remove([tab.id]); + } + + async closeRight() { + let tabs = await this.tabPresenter.getAll(); + tabs.sort((t1, t2) => t1.index - t2.index); + let index = tabs.findIndex(t => t.active); + if (index < 0) { + return; + } + for (let i = index + 1; i < tabs.length; ++i) { + let tab = tabs[i]; + if (!tab.pinned) { + this.tabPresenter.remove(tab.id); + } + } + } + + reopen() { + return this.tabPresenter.reopen(); + } + + async reload(cache) { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.reload(tab.id, cache); + } + + async setPinned(pinned) { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.setPinned(tab.id, pinned); + } + + async togglePinned() { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.setPinned(tab.id, !tab.pinned); + } + + async duplicate() { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.duplicate(tab.id); + } + + async openPageSource() { + let tab = await this.tabPresenter.getCurrent(); + let url = 'view-source:' + tab.url; + return this.tabPresenter.create(url); + } + + async openHome(newTab) { + let tab = await this.tabPresenter.getCurrent(); + let urls = await this.browserSettingRepository.getHomepageUrls(); + if (urls.length === 1 && urls[0] === 'about:home') { + // eslint-disable-next-line max-len + throw new Error('Cannot open Firefox Home (about:home) by WebExtensions, set your custom URLs'); + } + if (urls.length === 1 && !newTab) { + return this.tabPresenter.open(urls[0], tab.id); + } + for (let url of urls) { + this.tabPresenter.create(url); + } + } +} diff --git a/src/background/usecases/VersionUseCase.js b/src/background/usecases/VersionUseCase.js deleted file mode 100644 index ed5112b..0000000 --- a/src/background/usecases/VersionUseCase.js +++ /dev/null @@ -1,26 +0,0 @@ -import manifest from '../../../manifest.json'; -import TabPresenter from '../presenters/TabPresenter'; -import NotifyPresenter from '../presenters/NotifyPresenter'; - -export default class VersionUseCase { - constructor() { - this.tabPresenter = new TabPresenter(); - this.notifyPresenter = new NotifyPresenter(); - } - - notify() { - let title = `Vim Vixen ${manifest.version} has been installed`; - let message = 'Click here to see release notes'; - let url = this.releaseNoteUrl(manifest.version); - this.notifyPresenter.notify(title, message, () => { - this.tabPresenter.create(url); - }); - } - - releaseNoteUrl(version) { - if (version) { - return `https://github.com/ueokande/vim-vixen/releases/tag/${version}`; - } - return 'https://github.com/ueokande/vim-vixen/releases/'; - } -} diff --git a/src/background/usecases/VersionUseCase.ts b/src/background/usecases/VersionUseCase.ts new file mode 100644 index 0000000..ed5112b --- /dev/null +++ b/src/background/usecases/VersionUseCase.ts @@ -0,0 +1,26 @@ +import manifest from '../../../manifest.json'; +import TabPresenter from '../presenters/TabPresenter'; +import NotifyPresenter from '../presenters/NotifyPresenter'; + +export default class VersionUseCase { + constructor() { + this.tabPresenter = new TabPresenter(); + this.notifyPresenter = new NotifyPresenter(); + } + + notify() { + let title = `Vim Vixen ${manifest.version} has been installed`; + let message = 'Click here to see release notes'; + let url = this.releaseNoteUrl(manifest.version); + this.notifyPresenter.notify(title, message, () => { + this.tabPresenter.create(url); + }); + } + + releaseNoteUrl(version) { + if (version) { + return `https://github.com/ueokande/vim-vixen/releases/tag/${version}`; + } + return 'https://github.com/ueokande/vim-vixen/releases/'; + } +} diff --git a/src/background/usecases/ZoomUseCase.js b/src/background/usecases/ZoomUseCase.js deleted file mode 100644 index 692d6d9..0000000 --- a/src/background/usecases/ZoomUseCase.js +++ /dev/null @@ -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); - } - -} diff --git a/src/background/usecases/ZoomUseCase.ts b/src/background/usecases/ZoomUseCase.ts new file mode 100644 index 0000000..692d6d9 --- /dev/null +++ b/src/background/usecases/ZoomUseCase.ts @@ -0,0 +1,35 @@ +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); + } + +} diff --git a/src/background/usecases/filters.js b/src/background/usecases/filters.js deleted file mode 100644 index d057dca..0000000 --- a/src/background/usecases/filters.js +++ /dev/null @@ -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 -}; diff --git a/src/background/usecases/filters.ts b/src/background/usecases/filters.ts new file mode 100644 index 0000000..d057dca --- /dev/null +++ b/src/background/usecases/filters.ts @@ -0,0 +1,72 @@ +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 +}; diff --git a/src/background/usecases/parsers.js b/src/background/usecases/parsers.js deleted file mode 100644 index 43c8177..0000000 --- a/src/background/usecases/parsers.js +++ /dev/null @@ -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 }; diff --git a/src/background/usecases/parsers.ts b/src/background/usecases/parsers.ts new file mode 100644 index 0000000..43c8177 --- /dev/null +++ b/src/background/usecases/parsers.ts @@ -0,0 +1,31 @@ +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 }; diff --git a/src/console/actions/console.js b/src/console/actions/console.js deleted file mode 100644 index 3713a76..0000000 --- a/src/console/actions/console.js +++ /dev/null @@ -1,96 +0,0 @@ -import messages from 'shared/messages'; -import actions from 'console/actions'; - -const hide = () => { - return { - type: actions.CONSOLE_HIDE, - }; -}; - -const showCommand = (text) => { - return { - type: actions.CONSOLE_SHOW_COMMAND, - text: text - }; -}; - -const showFind = () => { - return { - type: actions.CONSOLE_SHOW_FIND, - }; -}; - -const showError = (text) => { - return { - type: actions.CONSOLE_SHOW_ERROR, - text: text - }; -}; - -const showInfo = (text) => { - return { - type: actions.CONSOLE_SHOW_INFO, - text: text - }; -}; - -const hideCommand = () => { - window.top.postMessage(JSON.stringify({ - type: messages.CONSOLE_UNFOCUS, - }), '*'); - return { - type: actions.CONSOLE_HIDE_COMMAND, - }; -}; - -const enterCommand = async(text) => { - await browser.runtime.sendMessage({ - type: messages.CONSOLE_ENTER_COMMAND, - text, - }); - return hideCommand(text); -}; - -const enterFind = (text) => { - window.top.postMessage(JSON.stringify({ - type: messages.CONSOLE_ENTER_FIND, - text, - }), '*'); - return hideCommand(); -}; - -const setConsoleText = (consoleText) => { - return { - type: actions.CONSOLE_SET_CONSOLE_TEXT, - consoleText, - }; -}; - -const getCompletions = async(text) => { - let completions = await browser.runtime.sendMessage({ - type: messages.CONSOLE_QUERY_COMPLETIONS, - text, - }); - return { - type: actions.CONSOLE_SET_COMPLETIONS, - completions, - completionSource: text, - }; -}; - -const completionNext = () => { - return { - type: actions.CONSOLE_COMPLETION_NEXT, - }; -}; - -const completionPrev = () => { - return { - type: actions.CONSOLE_COMPLETION_PREV, - }; -}; - -export { - hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText, - enterCommand, enterFind, getCompletions, completionNext, completionPrev -}; diff --git a/src/console/actions/console.ts b/src/console/actions/console.ts new file mode 100644 index 0000000..3713a76 --- /dev/null +++ b/src/console/actions/console.ts @@ -0,0 +1,96 @@ +import messages from 'shared/messages'; +import actions from 'console/actions'; + +const hide = () => { + return { + type: actions.CONSOLE_HIDE, + }; +}; + +const showCommand = (text) => { + return { + type: actions.CONSOLE_SHOW_COMMAND, + text: text + }; +}; + +const showFind = () => { + return { + type: actions.CONSOLE_SHOW_FIND, + }; +}; + +const showError = (text) => { + return { + type: actions.CONSOLE_SHOW_ERROR, + text: text + }; +}; + +const showInfo = (text) => { + return { + type: actions.CONSOLE_SHOW_INFO, + text: text + }; +}; + +const hideCommand = () => { + window.top.postMessage(JSON.stringify({ + type: messages.CONSOLE_UNFOCUS, + }), '*'); + return { + type: actions.CONSOLE_HIDE_COMMAND, + }; +}; + +const enterCommand = async(text) => { + await browser.runtime.sendMessage({ + type: messages.CONSOLE_ENTER_COMMAND, + text, + }); + return hideCommand(text); +}; + +const enterFind = (text) => { + window.top.postMessage(JSON.stringify({ + type: messages.CONSOLE_ENTER_FIND, + text, + }), '*'); + return hideCommand(); +}; + +const setConsoleText = (consoleText) => { + return { + type: actions.CONSOLE_SET_CONSOLE_TEXT, + consoleText, + }; +}; + +const getCompletions = async(text) => { + let completions = await browser.runtime.sendMessage({ + type: messages.CONSOLE_QUERY_COMPLETIONS, + text, + }); + return { + type: actions.CONSOLE_SET_COMPLETIONS, + completions, + completionSource: text, + }; +}; + +const completionNext = () => { + return { + type: actions.CONSOLE_COMPLETION_NEXT, + }; +}; + +const completionPrev = () => { + return { + type: actions.CONSOLE_COMPLETION_PREV, + }; +}; + +export { + hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText, + enterCommand, enterFind, getCompletions, completionNext, completionPrev +}; diff --git a/src/console/actions/index.js b/src/console/actions/index.js deleted file mode 100644 index b394179..0000000 --- a/src/console/actions/index.js +++ /dev/null @@ -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', -}; diff --git a/src/console/actions/index.ts b/src/console/actions/index.ts new file mode 100644 index 0000000..b394179 --- /dev/null +++ b/src/console/actions/index.ts @@ -0,0 +1,13 @@ +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', +}; diff --git a/src/console/components/Console.jsx b/src/console/components/Console.jsx deleted file mode 100644 index 5427e43..0000000 --- a/src/console/components/Console.jsx +++ /dev/null @@ -1,149 +0,0 @@ -import './console.scss'; -import { connect } from 'react-redux'; -import React from 'react'; -import PropTypes from 'prop-types'; -import Input from './console/Input'; -import Completion from './console/Completion'; -import Message from './console/Message'; -import * as consoleActions from '../../console/actions/console'; - -const COMPLETION_MAX_ITEMS = 33; - -class Console extends React.Component { - onBlur() { - if (this.props.mode === 'command' || this.props.mode === 'find') { - return this.props.dispatch(consoleActions.hideCommand()); - } - } - - doEnter(e) { - e.stopPropagation(); - e.preventDefault(); - - let value = e.target.value; - if (this.props.mode === 'command') { - return this.props.dispatch(consoleActions.enterCommand(value)); - } else if (this.props.mode === 'find') { - return this.props.dispatch(consoleActions.enterFind(value)); - } - } - - selectNext(e) { - this.props.dispatch(consoleActions.completionNext()); - e.stopPropagation(); - e.preventDefault(); - } - - selectPrev(e) { - this.props.dispatch(consoleActions.completionPrev()); - e.stopPropagation(); - e.preventDefault(); - } - - onKeyDown(e) { - if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) { - this.props.dispatch(consoleActions.hideCommand()); - } - switch (e.keyCode) { - case KeyboardEvent.DOM_VK_ESCAPE: - return this.props.dispatch(consoleActions.hideCommand()); - case KeyboardEvent.DOM_VK_RETURN: - return this.doEnter(e); - case KeyboardEvent.DOM_VK_TAB: - if (e.shiftKey) { - this.props.dispatch(consoleActions.completionPrev()); - } else { - this.props.dispatch(consoleActions.completionNext()); - } - e.stopPropagation(); - e.preventDefault(); - break; - case KeyboardEvent.DOM_VK_OPEN_BRACKET: - if (e.ctrlKey) { - return this.props.dispatch(consoleActions.hideCommand()); - } - break; - case KeyboardEvent.DOM_VK_M: - if (e.ctrlKey) { - return this.doEnter(e); - } - break; - case KeyboardEvent.DOM_VK_N: - if (e.ctrlKey) { - this.selectNext(e); - } - break; - case KeyboardEvent.DOM_VK_P: - if (e.ctrlKey) { - this.selectPrev(e); - } - break; - } - } - - onChange(e) { - let text = e.target.value; - this.props.dispatch(consoleActions.setConsoleText(text)); - if (this.props.mode === 'command') { - this.props.dispatch(consoleActions.getCompletions(text)); - } - } - - - componentDidUpdate(prevProps) { - if (!this.input) { - return; - } - if (prevProps.mode !== 'command' && this.props.mode === 'command') { - this.props.dispatch( - consoleActions.getCompletions(this.props.consoleText)); - this.focus(); - } else if (prevProps.mode !== 'find' && this.props.mode === 'find') { - this.focus(); - } - } - - render() { - switch (this.props.mode) { - case 'command': - case 'find': - return
- - { this.input = c; }} - mode={this.props.mode} - onBlur={this.onBlur.bind(this)} - onKeyDown={this.onKeyDown.bind(this)} - onChange={this.onChange.bind(this)} - value={this.props.consoleText} - /> -
; - case 'info': - case 'error': - return - { this.props.messageText } - ; - default: - return null; - } - } - - focus() { - window.focus(); - this.input.focus(); - } -} - -Console.propTypes = { - mode: PropTypes.string, - consoleText: PropTypes.string, - messageText: PropTypes.string, - children: PropTypes.string, -}; - -const mapStateToProps = state => state; -export default connect(mapStateToProps)(Console); diff --git a/src/console/components/Console.tsx b/src/console/components/Console.tsx new file mode 100644 index 0000000..5427e43 --- /dev/null +++ b/src/console/components/Console.tsx @@ -0,0 +1,149 @@ +import './console.scss'; +import { connect } from 'react-redux'; +import React from 'react'; +import PropTypes from 'prop-types'; +import Input from './console/Input'; +import Completion from './console/Completion'; +import Message from './console/Message'; +import * as consoleActions from '../../console/actions/console'; + +const COMPLETION_MAX_ITEMS = 33; + +class Console extends React.Component { + onBlur() { + if (this.props.mode === 'command' || this.props.mode === 'find') { + return this.props.dispatch(consoleActions.hideCommand()); + } + } + + doEnter(e) { + e.stopPropagation(); + e.preventDefault(); + + let value = e.target.value; + if (this.props.mode === 'command') { + return this.props.dispatch(consoleActions.enterCommand(value)); + } else if (this.props.mode === 'find') { + return this.props.dispatch(consoleActions.enterFind(value)); + } + } + + selectNext(e) { + this.props.dispatch(consoleActions.completionNext()); + e.stopPropagation(); + e.preventDefault(); + } + + selectPrev(e) { + this.props.dispatch(consoleActions.completionPrev()); + e.stopPropagation(); + e.preventDefault(); + } + + onKeyDown(e) { + if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) { + this.props.dispatch(consoleActions.hideCommand()); + } + switch (e.keyCode) { + case KeyboardEvent.DOM_VK_ESCAPE: + return this.props.dispatch(consoleActions.hideCommand()); + case KeyboardEvent.DOM_VK_RETURN: + return this.doEnter(e); + case KeyboardEvent.DOM_VK_TAB: + if (e.shiftKey) { + this.props.dispatch(consoleActions.completionPrev()); + } else { + this.props.dispatch(consoleActions.completionNext()); + } + e.stopPropagation(); + e.preventDefault(); + break; + case KeyboardEvent.DOM_VK_OPEN_BRACKET: + if (e.ctrlKey) { + return this.props.dispatch(consoleActions.hideCommand()); + } + break; + case KeyboardEvent.DOM_VK_M: + if (e.ctrlKey) { + return this.doEnter(e); + } + break; + case KeyboardEvent.DOM_VK_N: + if (e.ctrlKey) { + this.selectNext(e); + } + break; + case KeyboardEvent.DOM_VK_P: + if (e.ctrlKey) { + this.selectPrev(e); + } + break; + } + } + + onChange(e) { + let text = e.target.value; + this.props.dispatch(consoleActions.setConsoleText(text)); + if (this.props.mode === 'command') { + this.props.dispatch(consoleActions.getCompletions(text)); + } + } + + + componentDidUpdate(prevProps) { + if (!this.input) { + return; + } + if (prevProps.mode !== 'command' && this.props.mode === 'command') { + this.props.dispatch( + consoleActions.getCompletions(this.props.consoleText)); + this.focus(); + } else if (prevProps.mode !== 'find' && this.props.mode === 'find') { + this.focus(); + } + } + + render() { + switch (this.props.mode) { + case 'command': + case 'find': + return
+ + { this.input = c; }} + mode={this.props.mode} + onBlur={this.onBlur.bind(this)} + onKeyDown={this.onKeyDown.bind(this)} + onChange={this.onChange.bind(this)} + value={this.props.consoleText} + /> +
; + case 'info': + case 'error': + return + { this.props.messageText } + ; + default: + return null; + } + } + + focus() { + window.focus(); + this.input.focus(); + } +} + +Console.propTypes = { + mode: PropTypes.string, + consoleText: PropTypes.string, + messageText: PropTypes.string, + children: PropTypes.string, +}; + +const mapStateToProps = state => state; +export default connect(mapStateToProps)(Console); diff --git a/src/console/components/console/Completion.jsx b/src/console/components/console/Completion.jsx deleted file mode 100644 index 5477cb6..0000000 --- a/src/console/components/console/Completion.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import CompletionItem from './CompletionItem'; -import CompletionTitle from './CompletionTitle'; - -class Completion extends React.Component { - constructor() { - super(); - this.state = { viewOffset: 0, select: -1 }; - } - - static getDerivedStateFromProps(nextProps, prevState) { - if (prevState.select === nextProps.select) { - return null; - } - - let viewSelect = (() => { - let index = 0; - for (let i = 0; i < nextProps.completions.length; ++i) { - ++index; - let g = nextProps.completions[i]; - if (nextProps.select + i + 1 < index + g.items.length) { - return nextProps.select + i + 1; - } - index += g.items.length; - } - })(); - - let viewOffset = 0; - if (nextProps.select < 0) { - viewOffset = 0; - } else if (prevState.select < nextProps.select) { - viewOffset = Math.max(prevState.viewOffset, - viewSelect - nextProps.size + 1); - } else if (prevState.select > nextProps.select) { - viewOffset = Math.min(prevState.viewOffset, viewSelect); - } - return { viewOffset, select: nextProps.select }; - } - - render() { - let eles = []; - let index = 0; - - for (let group of this.props.completions) { - eles.push(); - for (let item of group.items) { - eles.push(); - ++index; - } - } - - let viewOffset = this.state.viewOffset; - eles = eles.slice(viewOffset, viewOffset + this.props.size); - - return ( -
    - { eles } -
- ); - } -} - -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; diff --git a/src/console/components/console/Completion.tsx b/src/console/components/console/Completion.tsx new file mode 100644 index 0000000..5477cb6 --- /dev/null +++ b/src/console/components/console/Completion.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import CompletionItem from './CompletionItem'; +import CompletionTitle from './CompletionTitle'; + +class Completion extends React.Component { + constructor() { + super(); + this.state = { viewOffset: 0, select: -1 }; + } + + static getDerivedStateFromProps(nextProps, prevState) { + if (prevState.select === nextProps.select) { + return null; + } + + let viewSelect = (() => { + let index = 0; + for (let i = 0; i < nextProps.completions.length; ++i) { + ++index; + let g = nextProps.completions[i]; + if (nextProps.select + i + 1 < index + g.items.length) { + return nextProps.select + i + 1; + } + index += g.items.length; + } + })(); + + let viewOffset = 0; + if (nextProps.select < 0) { + viewOffset = 0; + } else if (prevState.select < nextProps.select) { + viewOffset = Math.max(prevState.viewOffset, + viewSelect - nextProps.size + 1); + } else if (prevState.select > nextProps.select) { + viewOffset = Math.min(prevState.viewOffset, viewSelect); + } + return { viewOffset, select: nextProps.select }; + } + + render() { + let eles = []; + let index = 0; + + for (let group of this.props.completions) { + eles.push(); + for (let item of group.items) { + eles.push(); + ++index; + } + } + + let viewOffset = this.state.viewOffset; + eles = eles.slice(viewOffset, viewOffset + this.props.size); + + return ( +
    + { eles } +
+ ); + } +} + +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; diff --git a/src/console/components/console/CompletionItem.jsx b/src/console/components/console/CompletionItem.jsx deleted file mode 100644 index 3dc552b..0000000 --- a/src/console/components/console/CompletionItem.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const CompletionItem = (props) => { - let className = 'vimvixen-console-completion-item'; - if (props.highlight) { - className += ' vimvixen-completion-selected'; - } - return
  • - {props.caption} - {props.url} -
  • ; -}; - -CompletionItem.propTypes = { - highlight: PropTypes.bool, - caption: PropTypes.string, - url: PropTypes.string, -}; - -export default CompletionItem; diff --git a/src/console/components/console/CompletionItem.tsx b/src/console/components/console/CompletionItem.tsx new file mode 100644 index 0000000..3dc552b --- /dev/null +++ b/src/console/components/console/CompletionItem.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const CompletionItem = (props) => { + let className = 'vimvixen-console-completion-item'; + if (props.highlight) { + className += ' vimvixen-completion-selected'; + } + return
  • + {props.caption} + {props.url} +
  • ; +}; + +CompletionItem.propTypes = { + highlight: PropTypes.bool, + caption: PropTypes.string, + url: PropTypes.string, +}; + +export default CompletionItem; diff --git a/src/console/components/console/CompletionTitle.jsx b/src/console/components/console/CompletionTitle.jsx deleted file mode 100644 index 4fcba3f..0000000 --- a/src/console/components/console/CompletionTitle.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const CompletionTitle = (props) => { - return
  • - {props.title} -
  • ; -}; - -CompletionTitle.propTypes = { - title: PropTypes.string, -}; - -export default CompletionTitle; diff --git a/src/console/components/console/CompletionTitle.tsx b/src/console/components/console/CompletionTitle.tsx new file mode 100644 index 0000000..4fcba3f --- /dev/null +++ b/src/console/components/console/CompletionTitle.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const CompletionTitle = (props) => { + return
  • + {props.title} +
  • ; +}; + +CompletionTitle.propTypes = { + title: PropTypes.string, +}; + +export default CompletionTitle; diff --git a/src/console/components/console/Input.jsx b/src/console/components/console/Input.jsx deleted file mode 100644 index cbd3348..0000000 --- a/src/console/components/console/Input.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -class Input extends React.Component { - focus() { - this.input.focus(); - } - - render() { - let prompt = ''; - if (this.props.mode === 'command') { - prompt = ':'; - } else if (this.props.mode === 'find') { - prompt = '/'; - } - - return ( -
    - - { prompt } - - { this.input = c; }} - onBlur={this.props.onBlur} - onKeyDown={this.props.onKeyDown} - onChange={this.props.onChange} - value={this.props.value} - /> -
    - ); - } -} - -Input.propTypes = { - mode: PropTypes.string, - value: PropTypes.string, - onBlur: PropTypes.func, - onKeyDown: PropTypes.func, - onChange: PropTypes.func, -}; - -export default Input; diff --git a/src/console/components/console/Input.tsx b/src/console/components/console/Input.tsx new file mode 100644 index 0000000..cbd3348 --- /dev/null +++ b/src/console/components/console/Input.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +class Input extends React.Component { + focus() { + this.input.focus(); + } + + render() { + let prompt = ''; + if (this.props.mode === 'command') { + prompt = ':'; + } else if (this.props.mode === 'find') { + prompt = '/'; + } + + return ( +
    + + { prompt } + + { this.input = c; }} + onBlur={this.props.onBlur} + onKeyDown={this.props.onKeyDown} + onChange={this.props.onChange} + value={this.props.value} + /> +
    + ); + } +} + +Input.propTypes = { + mode: PropTypes.string, + value: PropTypes.string, + onBlur: PropTypes.func, + onKeyDown: PropTypes.func, + onChange: PropTypes.func, +}; + +export default Input; diff --git a/src/console/components/console/Message.jsx b/src/console/components/console/Message.jsx deleted file mode 100644 index dd96248..0000000 --- a/src/console/components/console/Message.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const Message = (props) => { - switch (props.mode) { - case 'error': - return ( -

    - { props.children } -

    - ); - case 'info': - return ( -

    - { props.children } -

    - ); - } -}; - -Message.propTypes = { - children: PropTypes.string, -}; - -export default Message; diff --git a/src/console/components/console/Message.tsx b/src/console/components/console/Message.tsx new file mode 100644 index 0000000..dd96248 --- /dev/null +++ b/src/console/components/console/Message.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Message = (props) => { + switch (props.mode) { + case 'error': + return ( +

    + { props.children } +

    + ); + case 'info': + return ( +

    + { props.children } +

    + ); + } +}; + +Message.propTypes = { + children: PropTypes.string, +}; + +export default Message; diff --git a/src/console/index.jsx b/src/console/index.jsx deleted file mode 100644 index 3190a9a..0000000 --- a/src/console/index.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import messages from 'shared/messages'; -import reducers from 'console/reducers'; -import { createStore, applyMiddleware } from 'redux'; -import promise from 'redux-promise'; -import * as consoleActions from 'console/actions/console'; -import { Provider } from 'react-redux'; -import Console from './components/Console'; -import React from 'react'; -import ReactDOM from 'react-dom'; - -const store = createStore( - reducers, - applyMiddleware(promise), -); - -window.addEventListener('load', () => { - let wrapper = document.getElementById('vimvixen-console'); - ReactDOM.render( - - - , - wrapper); -}); - -const onMessage = (message) => { - switch (message.type) { - case messages.CONSOLE_SHOW_COMMAND: - return store.dispatch(consoleActions.showCommand(message.command)); - case messages.CONSOLE_SHOW_FIND: - return store.dispatch(consoleActions.showFind()); - case messages.CONSOLE_SHOW_ERROR: - return store.dispatch(consoleActions.showError(message.text)); - case messages.CONSOLE_SHOW_INFO: - return store.dispatch(consoleActions.showInfo(message.text)); - case messages.CONSOLE_HIDE: - return store.dispatch(consoleActions.hide()); - } -}; - -browser.runtime.onMessage.addListener(onMessage); -let port = browser.runtime.connect({ name: 'vimvixen-console' }); -port.onMessage.addListener(onMessage); diff --git a/src/console/index.tsx b/src/console/index.tsx new file mode 100644 index 0000000..3190a9a --- /dev/null +++ b/src/console/index.tsx @@ -0,0 +1,42 @@ +import messages from 'shared/messages'; +import reducers from 'console/reducers'; +import { createStore, applyMiddleware } from 'redux'; +import promise from 'redux-promise'; +import * as consoleActions from 'console/actions/console'; +import { Provider } from 'react-redux'; +import Console from './components/Console'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const store = createStore( + reducers, + applyMiddleware(promise), +); + +window.addEventListener('load', () => { + let wrapper = document.getElementById('vimvixen-console'); + ReactDOM.render( + + + , + wrapper); +}); + +const onMessage = (message) => { + switch (message.type) { + case messages.CONSOLE_SHOW_COMMAND: + return store.dispatch(consoleActions.showCommand(message.command)); + case messages.CONSOLE_SHOW_FIND: + return store.dispatch(consoleActions.showFind()); + case messages.CONSOLE_SHOW_ERROR: + return store.dispatch(consoleActions.showError(message.text)); + case messages.CONSOLE_SHOW_INFO: + return store.dispatch(consoleActions.showInfo(message.text)); + case messages.CONSOLE_HIDE: + return store.dispatch(consoleActions.hide()); + } +}; + +browser.runtime.onMessage.addListener(onMessage); +let port = browser.runtime.connect({ name: 'vimvixen-console' }); +port.onMessage.addListener(onMessage); diff --git a/src/console/reducers/index.js b/src/console/reducers/index.js deleted file mode 100644 index 614a72f..0000000 --- a/src/console/reducers/index.js +++ /dev/null @@ -1,102 +0,0 @@ -import actions from 'console/actions'; - -const defaultState = { - mode: '', - messageText: '', - consoleText: '', - completionSource: '', - completions: [], - select: -1, - viewIndex: 0, -}; - -const nextSelection = (state) => { - if (state.completions.length === 0) { - return -1; - } - if (state.select < 0) { - return 0; - } - - let length = state.completions - .map(g => g.items.length) - .reduce((x, y) => x + y); - if (state.select + 1 < length) { - return state.select + 1; - } - return -1; -}; - -const prevSelection = (state) => { - let length = state.completions - .map(g => g.items.length) - .reduce((x, y) => x + y); - if (state.select < 0) { - return length - 1; - } - return state.select - 1; -}; - -const nextConsoleText = (completions, select, defaults) => { - if (select < 0) { - return defaults; - } - let items = completions.map(g => g.items).reduce((g1, g2) => g1.concat(g2)); - return items[select].content; -}; - -// eslint-disable-next-line max-lines-per-function -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.CONSOLE_HIDE: - return { ...state, - mode: '', }; - case actions.CONSOLE_SHOW_COMMAND: - return { ...state, - mode: 'command', - consoleText: action.text, - completions: []}; - case actions.CONSOLE_SHOW_FIND: - return { ...state, - mode: 'find', - consoleText: '', - completions: []}; - case actions.CONSOLE_SHOW_ERROR: - return { ...state, - mode: 'error', - messageText: action.text, }; - case actions.CONSOLE_SHOW_INFO: - return { ...state, - mode: 'info', - messageText: action.text, }; - case actions.CONSOLE_HIDE_COMMAND: - return { - ...state, - mode: state.mode === 'command' || state.mode === 'find' ? '' : state.mode, - }; - case actions.CONSOLE_SET_CONSOLE_TEXT: - return { ...state, - consoleText: action.consoleText, }; - case actions.CONSOLE_SET_COMPLETIONS: - return { ...state, - completions: action.completions, - completionSource: action.completionSource, - select: -1 }; - case actions.CONSOLE_COMPLETION_NEXT: { - let select = nextSelection(state); - return { ...state, - select: select, - consoleText: nextConsoleText( - state.completions, select, state.completionSource) }; - } - case actions.CONSOLE_COMPLETION_PREV: { - let select = prevSelection(state); - return { ...state, - select: select, - consoleText: nextConsoleText( - state.completions, select, state.completionSource) }; - } - default: - return state; - } -} diff --git a/src/console/reducers/index.ts b/src/console/reducers/index.ts new file mode 100644 index 0000000..614a72f --- /dev/null +++ b/src/console/reducers/index.ts @@ -0,0 +1,102 @@ +import actions from 'console/actions'; + +const defaultState = { + mode: '', + messageText: '', + consoleText: '', + completionSource: '', + completions: [], + select: -1, + viewIndex: 0, +}; + +const nextSelection = (state) => { + if (state.completions.length === 0) { + return -1; + } + if (state.select < 0) { + return 0; + } + + let length = state.completions + .map(g => g.items.length) + .reduce((x, y) => x + y); + if (state.select + 1 < length) { + return state.select + 1; + } + return -1; +}; + +const prevSelection = (state) => { + let length = state.completions + .map(g => g.items.length) + .reduce((x, y) => x + y); + if (state.select < 0) { + return length - 1; + } + return state.select - 1; +}; + +const nextConsoleText = (completions, select, defaults) => { + if (select < 0) { + return defaults; + } + let items = completions.map(g => g.items).reduce((g1, g2) => g1.concat(g2)); + return items[select].content; +}; + +// eslint-disable-next-line max-lines-per-function +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.CONSOLE_HIDE: + return { ...state, + mode: '', }; + case actions.CONSOLE_SHOW_COMMAND: + return { ...state, + mode: 'command', + consoleText: action.text, + completions: []}; + case actions.CONSOLE_SHOW_FIND: + return { ...state, + mode: 'find', + consoleText: '', + completions: []}; + case actions.CONSOLE_SHOW_ERROR: + return { ...state, + mode: 'error', + messageText: action.text, }; + case actions.CONSOLE_SHOW_INFO: + return { ...state, + mode: 'info', + messageText: action.text, }; + case actions.CONSOLE_HIDE_COMMAND: + return { + ...state, + mode: state.mode === 'command' || state.mode === 'find' ? '' : state.mode, + }; + case actions.CONSOLE_SET_CONSOLE_TEXT: + return { ...state, + consoleText: action.consoleText, }; + case actions.CONSOLE_SET_COMPLETIONS: + return { ...state, + completions: action.completions, + completionSource: action.completionSource, + select: -1 }; + case actions.CONSOLE_COMPLETION_NEXT: { + let select = nextSelection(state); + return { ...state, + select: select, + consoleText: nextConsoleText( + state.completions, select, state.completionSource) }; + } + case actions.CONSOLE_COMPLETION_PREV: { + let select = prevSelection(state); + return { ...state, + select: select, + consoleText: nextConsoleText( + state.completions, select, state.completionSource) }; + } + default: + return state; + } +} diff --git a/src/content/actions/addon.js b/src/content/actions/addon.js deleted file mode 100644 index b30cf16..0000000 --- a/src/content/actions/addon.js +++ /dev/null @@ -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 }; diff --git a/src/content/actions/addon.ts b/src/content/actions/addon.ts new file mode 100644 index 0000000..b30cf16 --- /dev/null +++ b/src/content/actions/addon.ts @@ -0,0 +1,19 @@ +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 }; diff --git a/src/content/actions/find.js b/src/content/actions/find.js deleted file mode 100644 index e08d7e5..0000000 --- a/src/content/actions/find.js +++ /dev/null @@ -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 }; diff --git a/src/content/actions/find.ts b/src/content/actions/find.ts new file mode 100644 index 0000000..e08d7e5 --- /dev/null +++ b/src/content/actions/find.ts @@ -0,0 +1,68 @@ +// +// 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 }; diff --git a/src/content/actions/follow-controller.js b/src/content/actions/follow-controller.js deleted file mode 100644 index 006b248..0000000 --- a/src/content/actions/follow-controller.js +++ /dev/null @@ -1,30 +0,0 @@ -import actions from 'content/actions'; - -const enable = (newTab, background) => { - return { - type: actions.FOLLOW_CONTROLLER_ENABLE, - newTab, - background, - }; -}; - -const disable = () => { - return { - type: actions.FOLLOW_CONTROLLER_DISABLE, - }; -}; - -const keyPress = (key) => { - return { - type: actions.FOLLOW_CONTROLLER_KEY_PRESS, - key: key - }; -}; - -const backspace = () => { - return { - type: actions.FOLLOW_CONTROLLER_BACKSPACE, - }; -}; - -export { enable, disable, keyPress, backspace }; diff --git a/src/content/actions/follow-controller.ts b/src/content/actions/follow-controller.ts new file mode 100644 index 0000000..006b248 --- /dev/null +++ b/src/content/actions/follow-controller.ts @@ -0,0 +1,30 @@ +import actions from 'content/actions'; + +const enable = (newTab, background) => { + return { + type: actions.FOLLOW_CONTROLLER_ENABLE, + newTab, + background, + }; +}; + +const disable = () => { + return { + type: actions.FOLLOW_CONTROLLER_DISABLE, + }; +}; + +const keyPress = (key) => { + return { + type: actions.FOLLOW_CONTROLLER_KEY_PRESS, + key: key + }; +}; + +const backspace = () => { + return { + type: actions.FOLLOW_CONTROLLER_BACKSPACE, + }; +}; + +export { enable, disable, keyPress, backspace }; diff --git a/src/content/actions/index.js b/src/content/actions/index.js deleted file mode 100644 index 0a16fdf..0000000 --- a/src/content/actions/index.js +++ /dev/null @@ -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', -}; diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts new file mode 100644 index 0000000..0a16fdf --- /dev/null +++ b/src/content/actions/index.ts @@ -0,0 +1,31 @@ +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', +}; diff --git a/src/content/actions/input.js b/src/content/actions/input.js deleted file mode 100644 index 465a486..0000000 --- a/src/content/actions/input.js +++ /dev/null @@ -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 }; diff --git a/src/content/actions/input.ts b/src/content/actions/input.ts new file mode 100644 index 0000000..465a486 --- /dev/null +++ b/src/content/actions/input.ts @@ -0,0 +1,16 @@ +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 }; diff --git a/src/content/actions/mark.js b/src/content/actions/mark.js deleted file mode 100644 index 712a811..0000000 --- a/src/content/actions/mark.js +++ /dev/null @@ -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, -}; diff --git a/src/content/actions/mark.ts b/src/content/actions/mark.ts new file mode 100644 index 0000000..712a811 --- /dev/null +++ b/src/content/actions/mark.ts @@ -0,0 +1,46 @@ +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, +}; diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js deleted file mode 100644 index ed9b2cf..0000000 --- a/src/content/actions/operation.js +++ /dev/null @@ -1,104 +0,0 @@ -import operations from 'shared/operations'; -import messages from 'shared/messages'; -import * as scrolls from 'content/scrolls'; -import * as navigates from 'content/navigates'; -import * as focuses from 'content/focuses'; -import * as urls from 'content/urls'; -import * as consoleFrames from 'content/console-frames'; -import * as addonActions from './addon'; -import * as markActions from './mark'; -import * as properties from 'shared/settings/properties'; - -// eslint-disable-next-line complexity, max-lines-per-function -const exec = (operation, settings, addonEnabled) => { - let smoothscroll = settings.properties.smoothscroll || - properties.defaults.smoothscroll; - switch (operation.type) { - case operations.ADDON_ENABLE: - return addonActions.enable(); - case operations.ADDON_DISABLE: - return addonActions.disable(); - case operations.ADDON_TOGGLE_ENABLED: - return addonActions.setEnabled(!addonEnabled); - case operations.FIND_NEXT: - window.top.postMessage(JSON.stringify({ - type: messages.FIND_NEXT, - }), '*'); - break; - case operations.FIND_PREV: - window.top.postMessage(JSON.stringify({ - type: messages.FIND_PREV, - }), '*'); - break; - case operations.SCROLL_VERTICALLY: - scrolls.scrollVertically(operation.count, smoothscroll); - break; - case operations.SCROLL_HORIZONALLY: - scrolls.scrollHorizonally(operation.count, smoothscroll); - break; - case operations.SCROLL_PAGES: - scrolls.scrollPages(operation.count, smoothscroll); - break; - case operations.SCROLL_TOP: - scrolls.scrollToTop(smoothscroll); - break; - case operations.SCROLL_BOTTOM: - scrolls.scrollToBottom(smoothscroll); - break; - case operations.SCROLL_HOME: - scrolls.scrollToHome(smoothscroll); - break; - case operations.SCROLL_END: - scrolls.scrollToEnd(smoothscroll); - break; - case operations.FOLLOW_START: - window.top.postMessage(JSON.stringify({ - type: messages.FOLLOW_START, - newTab: operation.newTab, - background: operation.background, - }), '*'); - break; - case operations.MARK_SET_PREFIX: - return markActions.startSet(); - case operations.MARK_JUMP_PREFIX: - return markActions.startJump(); - case operations.NAVIGATE_HISTORY_PREV: - navigates.historyPrev(window); - break; - case operations.NAVIGATE_HISTORY_NEXT: - navigates.historyNext(window); - break; - case operations.NAVIGATE_LINK_PREV: - navigates.linkPrev(window); - break; - case operations.NAVIGATE_LINK_NEXT: - navigates.linkNext(window); - break; - case operations.NAVIGATE_PARENT: - navigates.parent(window); - break; - case operations.NAVIGATE_ROOT: - navigates.root(window); - break; - case operations.FOCUS_INPUT: - focuses.focusInput(); - break; - case operations.URLS_YANK: - urls.yank(window); - consoleFrames.postInfo('Yanked ' + window.location.href); - break; - case operations.URLS_PASTE: - urls.paste( - window, operation.newTab ? operation.newTab : false, settings.search - ); - break; - default: - browser.runtime.sendMessage({ - type: messages.BACKGROUND_OPERATION, - operation, - }); - } - return { type: '' }; -}; - -export { exec }; diff --git a/src/content/actions/operation.ts b/src/content/actions/operation.ts new file mode 100644 index 0000000..ed9b2cf --- /dev/null +++ b/src/content/actions/operation.ts @@ -0,0 +1,104 @@ +import operations from 'shared/operations'; +import messages from 'shared/messages'; +import * as scrolls from 'content/scrolls'; +import * as navigates from 'content/navigates'; +import * as focuses from 'content/focuses'; +import * as urls from 'content/urls'; +import * as consoleFrames from 'content/console-frames'; +import * as addonActions from './addon'; +import * as markActions from './mark'; +import * as properties from 'shared/settings/properties'; + +// eslint-disable-next-line complexity, max-lines-per-function +const exec = (operation, settings, addonEnabled) => { + let smoothscroll = settings.properties.smoothscroll || + properties.defaults.smoothscroll; + switch (operation.type) { + case operations.ADDON_ENABLE: + return addonActions.enable(); + case operations.ADDON_DISABLE: + return addonActions.disable(); + case operations.ADDON_TOGGLE_ENABLED: + return addonActions.setEnabled(!addonEnabled); + case operations.FIND_NEXT: + window.top.postMessage(JSON.stringify({ + type: messages.FIND_NEXT, + }), '*'); + break; + case operations.FIND_PREV: + window.top.postMessage(JSON.stringify({ + type: messages.FIND_PREV, + }), '*'); + break; + case operations.SCROLL_VERTICALLY: + scrolls.scrollVertically(operation.count, smoothscroll); + break; + case operations.SCROLL_HORIZONALLY: + scrolls.scrollHorizonally(operation.count, smoothscroll); + break; + case operations.SCROLL_PAGES: + scrolls.scrollPages(operation.count, smoothscroll); + break; + case operations.SCROLL_TOP: + scrolls.scrollToTop(smoothscroll); + break; + case operations.SCROLL_BOTTOM: + scrolls.scrollToBottom(smoothscroll); + break; + case operations.SCROLL_HOME: + scrolls.scrollToHome(smoothscroll); + break; + case operations.SCROLL_END: + scrolls.scrollToEnd(smoothscroll); + break; + case operations.FOLLOW_START: + window.top.postMessage(JSON.stringify({ + type: messages.FOLLOW_START, + newTab: operation.newTab, + background: operation.background, + }), '*'); + break; + case operations.MARK_SET_PREFIX: + return markActions.startSet(); + case operations.MARK_JUMP_PREFIX: + return markActions.startJump(); + case operations.NAVIGATE_HISTORY_PREV: + navigates.historyPrev(window); + break; + case operations.NAVIGATE_HISTORY_NEXT: + navigates.historyNext(window); + break; + case operations.NAVIGATE_LINK_PREV: + navigates.linkPrev(window); + break; + case operations.NAVIGATE_LINK_NEXT: + navigates.linkNext(window); + break; + case operations.NAVIGATE_PARENT: + navigates.parent(window); + break; + case operations.NAVIGATE_ROOT: + navigates.root(window); + break; + case operations.FOCUS_INPUT: + focuses.focusInput(); + break; + case operations.URLS_YANK: + urls.yank(window); + consoleFrames.postInfo('Yanked ' + window.location.href); + break; + case operations.URLS_PASTE: + urls.paste( + window, operation.newTab ? operation.newTab : false, settings.search + ); + break; + default: + browser.runtime.sendMessage({ + type: messages.BACKGROUND_OPERATION, + operation, + }); + } + return { type: '' }; +}; + +export { exec }; diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js deleted file mode 100644 index 1c15dd7..0000000 --- a/src/content/actions/setting.js +++ /dev/null @@ -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 = { - '': { type: operations.CANCEL }, - '': { 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 }; diff --git a/src/content/actions/setting.ts b/src/content/actions/setting.ts new file mode 100644 index 0000000..1c15dd7 --- /dev/null +++ b/src/content/actions/setting.ts @@ -0,0 +1,37 @@ +import actions from 'content/actions'; +import * as keyUtils from 'shared/utils/keys'; +import operations from 'shared/operations'; +import messages from 'shared/messages'; + +const reservedKeymaps = { + '': { type: operations.CANCEL }, + '': { 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 }; diff --git a/src/content/components/common/follow.js b/src/content/components/common/follow.js deleted file mode 100644 index 63ce603..0000000 --- a/src/content/components/common/follow.js +++ /dev/null @@ -1,200 +0,0 @@ -import messages from 'shared/messages'; -import Hint from './hint'; -import * as dom from 'shared/utils/dom'; - -const TARGET_SELECTOR = [ - 'a', 'button', 'input', 'textarea', 'area', - '[contenteditable=true]', '[contenteditable=""]', '[tabindex]', - '[role="button"]', 'summary' -].join(','); - - -const inViewport = (win, element, viewSize, framePosition) => { - let { - top, left, bottom, right - } = dom.viewportRect(element); - let doc = win.document; - let frameWidth = doc.documentElement.clientWidth; - let frameHeight = doc.documentElement.clientHeight; - - if (right < 0 || bottom < 0 || top > frameHeight || left > frameWidth) { - // out of frame - return false; - } - if (right + framePosition.x < 0 || bottom + framePosition.y < 0 || - left + framePosition.x > viewSize.width || - top + framePosition.y > viewSize.height) { - // out of viewport - return false; - } - return true; -}; - -const isAriaHiddenOrAriaDisabled = (win, element) => { - if (!element || win.document.documentElement === element) { - return false; - } - for (let attr of ['aria-hidden', 'aria-disabled']) { - if (element.hasAttribute(attr)) { - let hidden = element.getAttribute(attr).toLowerCase(); - if (hidden === '' || hidden === 'true') { - return true; - } - } - } - return isAriaHiddenOrAriaDisabled(win, element.parentNode); -}; - -export default class Follow { - constructor(win, store) { - this.win = win; - this.store = store; - this.newTab = false; - this.background = false; - this.hints = {}; - this.targets = []; - - messages.onMessage(this.onMessage.bind(this)); - } - - key(key) { - if (Object.keys(this.hints).length === 0) { - return false; - } - this.win.parent.postMessage(JSON.stringify({ - type: messages.FOLLOW_KEY_PRESS, - key: key.key, - ctrlKey: key.ctrlKey, - }), '*'); - return true; - } - - openLink(element) { - // Browser prevent new tab by link with target='_blank' - if (!this.newTab && element.getAttribute('target') !== '_blank') { - element.click(); - return; - } - - let href = element.getAttribute('href'); - - // eslint-disable-next-line no-script-url - if (!href || href === '#' || href.toLowerCase().startsWith('javascript:')) { - return; - } - return browser.runtime.sendMessage({ - type: messages.OPEN_URL, - url: element.href, - newTab: true, - background: this.background, - }); - } - - countHints(sender, viewSize, framePosition) { - this.targets = Follow.getTargetElements(this.win, viewSize, framePosition); - sender.postMessage(JSON.stringify({ - type: messages.FOLLOW_RESPONSE_COUNT_TARGETS, - count: this.targets.length, - }), '*'); - } - - createHints(keysArray, newTab, background) { - if (keysArray.length !== this.targets.length) { - throw new Error('illegal hint count'); - } - - this.newTab = newTab; - this.background = background; - this.hints = {}; - for (let i = 0; i < keysArray.length; ++i) { - let keys = keysArray[i]; - let hint = new Hint(this.targets[i], keys); - this.hints[keys] = hint; - } - } - - showHints(keys) { - Object.keys(this.hints).filter(key => key.startsWith(keys)) - .forEach(key => this.hints[key].show()); - Object.keys(this.hints).filter(key => !key.startsWith(keys)) - .forEach(key => this.hints[key].hide()); - } - - removeHints() { - Object.keys(this.hints).forEach((key) => { - this.hints[key].remove(); - }); - this.hints = {}; - this.targets = []; - } - - activateHints(keys) { - let hint = this.hints[keys]; - if (!hint) { - return; - } - let element = hint.target; - switch (element.tagName.toLowerCase()) { - case 'a': - case 'area': - return this.openLink(element); - case 'input': - switch (element.type) { - case 'file': - case 'checkbox': - case 'radio': - case 'submit': - case 'reset': - case 'button': - case 'image': - case 'color': - return element.click(); - default: - return element.focus(); - } - case 'textarea': - return element.focus(); - case 'button': - case 'summary': - return element.click(); - default: - if (dom.isContentEditable(element)) { - return element.focus(); - } else if (element.hasAttribute('tabindex')) { - return element.click(); - } - } - } - - onMessage(message, sender) { - switch (message.type) { - case messages.FOLLOW_REQUEST_COUNT_TARGETS: - return this.countHints(sender, message.viewSize, message.framePosition); - case messages.FOLLOW_CREATE_HINTS: - return this.createHints( - message.keysArray, message.newTab, message.background); - case messages.FOLLOW_SHOW_HINTS: - return this.showHints(message.keys); - case messages.FOLLOW_ACTIVATE: - return this.activateHints(message.keys); - case messages.FOLLOW_REMOVE_HINTS: - return this.removeHints(message.keys); - } - } - - static getTargetElements(win, viewSize, framePosition) { - let all = win.document.querySelectorAll(TARGET_SELECTOR); - let filtered = Array.prototype.filter.call(all, (element) => { - let style = win.getComputedStyle(element); - - // AREA's 'display' in Browser style is 'none' - return (element.tagName === 'AREA' || style.display !== 'none') && - style.visibility !== 'hidden' && - element.type !== 'hidden' && - element.offsetHeight > 0 && - !isAriaHiddenOrAriaDisabled(win, element) && - inViewport(win, element, viewSize, framePosition); - }); - return filtered; - } -} diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts new file mode 100644 index 0000000..63ce603 --- /dev/null +++ b/src/content/components/common/follow.ts @@ -0,0 +1,200 @@ +import messages from 'shared/messages'; +import Hint from './hint'; +import * as dom from 'shared/utils/dom'; + +const TARGET_SELECTOR = [ + 'a', 'button', 'input', 'textarea', 'area', + '[contenteditable=true]', '[contenteditable=""]', '[tabindex]', + '[role="button"]', 'summary' +].join(','); + + +const inViewport = (win, element, viewSize, framePosition) => { + let { + top, left, bottom, right + } = dom.viewportRect(element); + let doc = win.document; + let frameWidth = doc.documentElement.clientWidth; + let frameHeight = doc.documentElement.clientHeight; + + if (right < 0 || bottom < 0 || top > frameHeight || left > frameWidth) { + // out of frame + return false; + } + if (right + framePosition.x < 0 || bottom + framePosition.y < 0 || + left + framePosition.x > viewSize.width || + top + framePosition.y > viewSize.height) { + // out of viewport + return false; + } + return true; +}; + +const isAriaHiddenOrAriaDisabled = (win, element) => { + if (!element || win.document.documentElement === element) { + return false; + } + for (let attr of ['aria-hidden', 'aria-disabled']) { + if (element.hasAttribute(attr)) { + let hidden = element.getAttribute(attr).toLowerCase(); + if (hidden === '' || hidden === 'true') { + return true; + } + } + } + return isAriaHiddenOrAriaDisabled(win, element.parentNode); +}; + +export default class Follow { + constructor(win, store) { + this.win = win; + this.store = store; + this.newTab = false; + this.background = false; + this.hints = {}; + this.targets = []; + + messages.onMessage(this.onMessage.bind(this)); + } + + key(key) { + if (Object.keys(this.hints).length === 0) { + return false; + } + this.win.parent.postMessage(JSON.stringify({ + type: messages.FOLLOW_KEY_PRESS, + key: key.key, + ctrlKey: key.ctrlKey, + }), '*'); + return true; + } + + openLink(element) { + // Browser prevent new tab by link with target='_blank' + if (!this.newTab && element.getAttribute('target') !== '_blank') { + element.click(); + return; + } + + let href = element.getAttribute('href'); + + // eslint-disable-next-line no-script-url + if (!href || href === '#' || href.toLowerCase().startsWith('javascript:')) { + return; + } + return browser.runtime.sendMessage({ + type: messages.OPEN_URL, + url: element.href, + newTab: true, + background: this.background, + }); + } + + countHints(sender, viewSize, framePosition) { + this.targets = Follow.getTargetElements(this.win, viewSize, framePosition); + sender.postMessage(JSON.stringify({ + type: messages.FOLLOW_RESPONSE_COUNT_TARGETS, + count: this.targets.length, + }), '*'); + } + + createHints(keysArray, newTab, background) { + if (keysArray.length !== this.targets.length) { + throw new Error('illegal hint count'); + } + + this.newTab = newTab; + this.background = background; + this.hints = {}; + for (let i = 0; i < keysArray.length; ++i) { + let keys = keysArray[i]; + let hint = new Hint(this.targets[i], keys); + this.hints[keys] = hint; + } + } + + showHints(keys) { + Object.keys(this.hints).filter(key => key.startsWith(keys)) + .forEach(key => this.hints[key].show()); + Object.keys(this.hints).filter(key => !key.startsWith(keys)) + .forEach(key => this.hints[key].hide()); + } + + removeHints() { + Object.keys(this.hints).forEach((key) => { + this.hints[key].remove(); + }); + this.hints = {}; + this.targets = []; + } + + activateHints(keys) { + let hint = this.hints[keys]; + if (!hint) { + return; + } + let element = hint.target; + switch (element.tagName.toLowerCase()) { + case 'a': + case 'area': + return this.openLink(element); + case 'input': + switch (element.type) { + case 'file': + case 'checkbox': + case 'radio': + case 'submit': + case 'reset': + case 'button': + case 'image': + case 'color': + return element.click(); + default: + return element.focus(); + } + case 'textarea': + return element.focus(); + case 'button': + case 'summary': + return element.click(); + default: + if (dom.isContentEditable(element)) { + return element.focus(); + } else if (element.hasAttribute('tabindex')) { + return element.click(); + } + } + } + + onMessage(message, sender) { + switch (message.type) { + case messages.FOLLOW_REQUEST_COUNT_TARGETS: + return this.countHints(sender, message.viewSize, message.framePosition); + case messages.FOLLOW_CREATE_HINTS: + return this.createHints( + message.keysArray, message.newTab, message.background); + case messages.FOLLOW_SHOW_HINTS: + return this.showHints(message.keys); + case messages.FOLLOW_ACTIVATE: + return this.activateHints(message.keys); + case messages.FOLLOW_REMOVE_HINTS: + return this.removeHints(message.keys); + } + } + + static getTargetElements(win, viewSize, framePosition) { + let all = win.document.querySelectorAll(TARGET_SELECTOR); + let filtered = Array.prototype.filter.call(all, (element) => { + let style = win.getComputedStyle(element); + + // AREA's 'display' in Browser style is 'none' + return (element.tagName === 'AREA' || style.display !== 'none') && + style.visibility !== 'hidden' && + element.type !== 'hidden' && + element.offsetHeight > 0 && + !isAriaHiddenOrAriaDisabled(win, element) && + inViewport(win, element, viewSize, framePosition); + }); + return filtered; + } +} diff --git a/src/content/components/common/hint.js b/src/content/components/common/hint.js deleted file mode 100644 index 1472587..0000000 --- a/src/content/components/common/hint.js +++ /dev/null @@ -1,49 +0,0 @@ -import * as dom from 'shared/utils/dom'; - -const hintPosition = (element) => { - let { left, top, right, bottom } = dom.viewportRect(element); - - if (element.tagName !== 'AREA') { - return { x: left, y: top }; - } - - return { - x: (left + right) / 2, - y: (top + bottom) / 2, - }; -}; - -export default class Hint { - constructor(target, tag) { - if (!(document.body instanceof HTMLElement)) { - throw new TypeError('target is not an HTMLElement'); - } - - this.target = target; - - let doc = target.ownerDocument; - let { x, y } = hintPosition(target); - let { scrollX, scrollY } = window; - - this.element = doc.createElement('span'); - this.element.className = 'vimvixen-hint'; - this.element.textContent = tag; - this.element.style.left = x + scrollX + 'px'; - this.element.style.top = y + scrollY + 'px'; - - this.show(); - doc.body.append(this.element); - } - - show() { - this.element.style.display = 'inline'; - } - - hide() { - this.element.style.display = 'none'; - } - - remove() { - this.element.remove(); - } -} diff --git a/src/content/components/common/hint.ts b/src/content/components/common/hint.ts new file mode 100644 index 0000000..1472587 --- /dev/null +++ b/src/content/components/common/hint.ts @@ -0,0 +1,49 @@ +import * as dom from 'shared/utils/dom'; + +const hintPosition = (element) => { + let { left, top, right, bottom } = dom.viewportRect(element); + + if (element.tagName !== 'AREA') { + return { x: left, y: top }; + } + + return { + x: (left + right) / 2, + y: (top + bottom) / 2, + }; +}; + +export default class Hint { + constructor(target, tag) { + if (!(document.body instanceof HTMLElement)) { + throw new TypeError('target is not an HTMLElement'); + } + + this.target = target; + + let doc = target.ownerDocument; + let { x, y } = hintPosition(target); + let { scrollX, scrollY } = window; + + this.element = doc.createElement('span'); + this.element.className = 'vimvixen-hint'; + this.element.textContent = tag; + this.element.style.left = x + scrollX + 'px'; + this.element.style.top = y + scrollY + 'px'; + + this.show(); + doc.body.append(this.element); + } + + show() { + this.element.style.display = 'inline'; + } + + hide() { + this.element.style.display = 'none'; + } + + remove() { + this.element.remove(); + } +} diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js deleted file mode 100644 index bcab4fa..0000000 --- a/src/content/components/common/index.js +++ /dev/null @@ -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); - } - } -} diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts new file mode 100644 index 0000000..bcab4fa --- /dev/null +++ b/src/content/components/common/index.ts @@ -0,0 +1,55 @@ +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); + } + } +} diff --git a/src/content/components/common/input.js b/src/content/components/common/input.js deleted file mode 100644 index eefaf10..0000000 --- a/src/content/components/common/input.js +++ /dev/null @@ -1,75 +0,0 @@ -import * as dom from 'shared/utils/dom'; -import * as keys from 'shared/utils/keys'; - -const cancelKey = (e) => { - return e.key === 'Escape' || e.key === '[' && e.ctrlKey; -}; - -export default class InputComponent { - constructor(target) { - this.pressed = {}; - this.onKeyListeners = []; - - target.addEventListener('keypress', this.onKeyPress.bind(this)); - target.addEventListener('keydown', this.onKeyDown.bind(this)); - target.addEventListener('keyup', this.onKeyUp.bind(this)); - } - - onKey(cb) { - this.onKeyListeners.push(cb); - } - - onKeyPress(e) { - if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') { - return; - } - this.pressed[e.key] = 'keypress'; - this.capture(e); - } - - onKeyDown(e) { - if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') { - return; - } - this.pressed[e.key] = 'keydown'; - this.capture(e); - } - - onKeyUp(e) { - delete this.pressed[e.key]; - } - - capture(e) { - if (this.fromInput(e)) { - if (cancelKey(e) && e.target.blur) { - e.target.blur(); - } - return; - } - if (['Shift', 'Control', 'Alt', 'OS'].includes(e.key)) { - // pressing only meta key is ignored - return; - } - - let key = keys.fromKeyboardEvent(e); - - for (let listener of this.onKeyListeners) { - let stop = listener(key); - if (stop) { - e.preventDefault(); - e.stopPropagation(); - break; - } - } - } - - fromInput(e) { - if (!e.target) { - return false; - } - return e.target instanceof HTMLInputElement || - e.target instanceof HTMLTextAreaElement || - e.target instanceof HTMLSelectElement || - dom.isContentEditable(e.target); - } -} diff --git a/src/content/components/common/input.ts b/src/content/components/common/input.ts new file mode 100644 index 0000000..eefaf10 --- /dev/null +++ b/src/content/components/common/input.ts @@ -0,0 +1,75 @@ +import * as dom from 'shared/utils/dom'; +import * as keys from 'shared/utils/keys'; + +const cancelKey = (e) => { + return e.key === 'Escape' || e.key === '[' && e.ctrlKey; +}; + +export default class InputComponent { + constructor(target) { + this.pressed = {}; + this.onKeyListeners = []; + + target.addEventListener('keypress', this.onKeyPress.bind(this)); + target.addEventListener('keydown', this.onKeyDown.bind(this)); + target.addEventListener('keyup', this.onKeyUp.bind(this)); + } + + onKey(cb) { + this.onKeyListeners.push(cb); + } + + onKeyPress(e) { + if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') { + return; + } + this.pressed[e.key] = 'keypress'; + this.capture(e); + } + + onKeyDown(e) { + if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') { + return; + } + this.pressed[e.key] = 'keydown'; + this.capture(e); + } + + onKeyUp(e) { + delete this.pressed[e.key]; + } + + capture(e) { + if (this.fromInput(e)) { + if (cancelKey(e) && e.target.blur) { + e.target.blur(); + } + return; + } + if (['Shift', 'Control', 'Alt', 'OS'].includes(e.key)) { + // pressing only meta key is ignored + return; + } + + let key = keys.fromKeyboardEvent(e); + + for (let listener of this.onKeyListeners) { + let stop = listener(key); + if (stop) { + e.preventDefault(); + e.stopPropagation(); + break; + } + } + } + + fromInput(e) { + if (!e.target) { + return false; + } + return e.target instanceof HTMLInputElement || + e.target instanceof HTMLTextAreaElement || + e.target instanceof HTMLSelectElement || + dom.isContentEditable(e.target); + } +} diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js deleted file mode 100644 index ec0d093..0000000 --- a/src/content/components/common/keymapper.js +++ /dev/null @@ -1,58 +0,0 @@ -import * as inputActions from 'content/actions/input'; -import * as operationActions from 'content/actions/operation'; -import operations from 'shared/operations'; -import * as keyUtils from 'shared/utils/keys'; - -const mapStartsWith = (mapping, keys) => { - if (mapping.length < keys.length) { - return false; - } - for (let i = 0; i < keys.length; ++i) { - if (!keyUtils.equals(mapping[i], keys[i])) { - return false; - } - } - return true; -}; - -export default class KeymapperComponent { - constructor(store) { - this.store = store; - } - - // eslint-disable-next-line max-statements - key(key) { - this.store.dispatch(inputActions.keyPress(key)); - - let state = this.store.getState(); - let input = state.input; - let keymaps = new Map(state.setting.keymaps); - - let matched = Array.from(keymaps.keys()).filter((mapping) => { - return mapStartsWith(mapping, input.keys); - }); - if (!state.addon.enabled) { - // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if - // the addon disabled - matched = matched.filter((keys) => { - let type = keymaps.get(keys).type; - return type === operations.ADDON_ENABLE || - type === operations.ADDON_TOGGLE_ENABLED; - }); - } - if (matched.length === 0) { - this.store.dispatch(inputActions.clearKeys()); - return false; - } else if (matched.length > 1 || - matched.length === 1 && input.keys.length < matched[0].length) { - return true; - } - let operation = keymaps.get(matched[0]); - let act = operationActions.exec( - operation, state.setting, state.addon.enabled - ); - this.store.dispatch(act); - this.store.dispatch(inputActions.clearKeys()); - return true; - } -} diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts new file mode 100644 index 0000000..ec0d093 --- /dev/null +++ b/src/content/components/common/keymapper.ts @@ -0,0 +1,58 @@ +import * as inputActions from 'content/actions/input'; +import * as operationActions from 'content/actions/operation'; +import operations from 'shared/operations'; +import * as keyUtils from 'shared/utils/keys'; + +const mapStartsWith = (mapping, keys) => { + if (mapping.length < keys.length) { + return false; + } + for (let i = 0; i < keys.length; ++i) { + if (!keyUtils.equals(mapping[i], keys[i])) { + return false; + } + } + return true; +}; + +export default class KeymapperComponent { + constructor(store) { + this.store = store; + } + + // eslint-disable-next-line max-statements + key(key) { + this.store.dispatch(inputActions.keyPress(key)); + + let state = this.store.getState(); + let input = state.input; + let keymaps = new Map(state.setting.keymaps); + + let matched = Array.from(keymaps.keys()).filter((mapping) => { + return mapStartsWith(mapping, input.keys); + }); + if (!state.addon.enabled) { + // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if + // the addon disabled + matched = matched.filter((keys) => { + let type = keymaps.get(keys).type; + return type === operations.ADDON_ENABLE || + type === operations.ADDON_TOGGLE_ENABLED; + }); + } + if (matched.length === 0) { + this.store.dispatch(inputActions.clearKeys()); + return false; + } else if (matched.length > 1 || + matched.length === 1 && input.keys.length < matched[0].length) { + return true; + } + let operation = keymaps.get(matched[0]); + let act = operationActions.exec( + operation, state.setting, state.addon.enabled + ); + this.store.dispatch(act); + this.store.dispatch(inputActions.clearKeys()); + return true; + } +} diff --git a/src/content/components/common/mark.js b/src/content/components/common/mark.js deleted file mode 100644 index 0f838a9..0000000 --- a/src/content/components/common/mark.js +++ /dev/null @@ -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)); - } -} diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts new file mode 100644 index 0000000..0f838a9 --- /dev/null +++ b/src/content/components/common/mark.ts @@ -0,0 +1,74 @@ +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)); + } +} diff --git a/src/content/components/frame-content.js b/src/content/components/frame-content.js deleted file mode 100644 index ca999ba..0000000 --- a/src/content/components/frame-content.js +++ /dev/null @@ -1,3 +0,0 @@ -import CommonComponent from './common'; - -export default CommonComponent; diff --git a/src/content/components/frame-content.ts b/src/content/components/frame-content.ts new file mode 100644 index 0000000..ca999ba --- /dev/null +++ b/src/content/components/frame-content.ts @@ -0,0 +1,3 @@ +import CommonComponent from './common'; + +export default CommonComponent; diff --git a/src/content/components/top-content/find.js b/src/content/components/top-content/find.js deleted file mode 100644 index 4d46d79..0000000 --- a/src/content/components/top-content/find.js +++ /dev/null @@ -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)); - } -} diff --git a/src/content/components/top-content/find.ts b/src/content/components/top-content/find.ts new file mode 100644 index 0000000..4d46d79 --- /dev/null +++ b/src/content/components/top-content/find.ts @@ -0,0 +1,41 @@ +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)); + } +} diff --git a/src/content/components/top-content/follow-controller.js b/src/content/components/top-content/follow-controller.js deleted file mode 100644 index 7f36604..0000000 --- a/src/content/components/top-content/follow-controller.js +++ /dev/null @@ -1,147 +0,0 @@ -import * as followControllerActions from 'content/actions/follow-controller'; -import messages from 'shared/messages'; -import HintKeyProducer from 'content/hint-key-producer'; -import * as properties from 'shared/settings/properties'; - -const broadcastMessage = (win, message) => { - let json = JSON.stringify(message); - let frames = [window.self].concat(Array.from(window.frames)); - frames.forEach(frame => frame.postMessage(json, '*')); -}; - -export default class FollowController { - constructor(win, store) { - this.win = win; - this.store = store; - this.state = {}; - this.keys = []; - this.producer = null; - - messages.onMessage(this.onMessage.bind(this)); - - store.subscribe(() => { - this.update(); - }); - } - - onMessage(message, sender) { - switch (message.type) { - case messages.FOLLOW_START: - return this.store.dispatch( - followControllerActions.enable(message.newTab, message.background)); - case messages.FOLLOW_RESPONSE_COUNT_TARGETS: - return this.create(message.count, sender); - case messages.FOLLOW_KEY_PRESS: - return this.keyPress(message.key, message.ctrlKey); - } - } - - update() { - let prevState = this.state; - this.state = this.store.getState().followController; - - if (!prevState.enabled && this.state.enabled) { - this.count(); - } else if (prevState.enabled && !this.state.enabled) { - this.remove(); - } else if (prevState.keys !== this.state.keys) { - this.updateHints(); - } - } - - updateHints() { - let shown = this.keys.filter(key => key.startsWith(this.state.keys)); - if (shown.length === 1) { - this.activate(); - this.store.dispatch(followControllerActions.disable()); - } - - broadcastMessage(this.win, { - type: messages.FOLLOW_SHOW_HINTS, - keys: this.state.keys, - }); - } - - activate() { - broadcastMessage(this.win, { - type: messages.FOLLOW_ACTIVATE, - keys: this.state.keys, - }); - } - - keyPress(key, ctrlKey) { - if (key === '[' && ctrlKey) { - this.store.dispatch(followControllerActions.disable()); - return true; - } - switch (key) { - case 'Enter': - this.activate(); - this.store.dispatch(followControllerActions.disable()); - break; - case 'Esc': - this.store.dispatch(followControllerActions.disable()); - break; - case 'Backspace': - case 'Delete': - this.store.dispatch(followControllerActions.backspace()); - break; - default: - if (this.hintchars().includes(key)) { - this.store.dispatch(followControllerActions.keyPress(key)); - } - break; - } - return true; - } - - count() { - this.producer = new HintKeyProducer(this.hintchars()); - let doc = this.win.document; - let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth; - let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight; - let frameElements = this.win.document.querySelectorAll('frame,iframe'); - - this.win.postMessage(JSON.stringify({ - type: messages.FOLLOW_REQUEST_COUNT_TARGETS, - viewSize: { width: viewWidth, height: viewHeight }, - framePosition: { x: 0, y: 0 }, - }), '*'); - frameElements.forEach((element) => { - let { left: frameX, top: frameY } = element.getBoundingClientRect(); - let message = JSON.stringify({ - type: messages.FOLLOW_REQUEST_COUNT_TARGETS, - viewSize: { width: viewWidth, height: viewHeight }, - framePosition: { x: frameX, y: frameY }, - }); - element.contentWindow.postMessage(message, '*'); - }); - } - - create(count, sender) { - let produced = []; - for (let i = 0; i < count; ++i) { - produced.push(this.producer.produce()); - } - this.keys = this.keys.concat(produced); - - sender.postMessage(JSON.stringify({ - type: messages.FOLLOW_CREATE_HINTS, - keysArray: produced, - newTab: this.state.newTab, - background: this.state.background, - }), '*'); - } - - remove() { - this.keys = []; - broadcastMessage(this.win, { - type: messages.FOLLOW_REMOVE_HINTS, - }); - } - - hintchars() { - return this.store.getState().setting.properties.hintchars || - properties.defaults.hintchars; - } -} diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts new file mode 100644 index 0000000..7f36604 --- /dev/null +++ b/src/content/components/top-content/follow-controller.ts @@ -0,0 +1,147 @@ +import * as followControllerActions from 'content/actions/follow-controller'; +import messages from 'shared/messages'; +import HintKeyProducer from 'content/hint-key-producer'; +import * as properties from 'shared/settings/properties'; + +const broadcastMessage = (win, message) => { + let json = JSON.stringify(message); + let frames = [window.self].concat(Array.from(window.frames)); + frames.forEach(frame => frame.postMessage(json, '*')); +}; + +export default class FollowController { + constructor(win, store) { + this.win = win; + this.store = store; + this.state = {}; + this.keys = []; + this.producer = null; + + messages.onMessage(this.onMessage.bind(this)); + + store.subscribe(() => { + this.update(); + }); + } + + onMessage(message, sender) { + switch (message.type) { + case messages.FOLLOW_START: + return this.store.dispatch( + followControllerActions.enable(message.newTab, message.background)); + case messages.FOLLOW_RESPONSE_COUNT_TARGETS: + return this.create(message.count, sender); + case messages.FOLLOW_KEY_PRESS: + return this.keyPress(message.key, message.ctrlKey); + } + } + + update() { + let prevState = this.state; + this.state = this.store.getState().followController; + + if (!prevState.enabled && this.state.enabled) { + this.count(); + } else if (prevState.enabled && !this.state.enabled) { + this.remove(); + } else if (prevState.keys !== this.state.keys) { + this.updateHints(); + } + } + + updateHints() { + let shown = this.keys.filter(key => key.startsWith(this.state.keys)); + if (shown.length === 1) { + this.activate(); + this.store.dispatch(followControllerActions.disable()); + } + + broadcastMessage(this.win, { + type: messages.FOLLOW_SHOW_HINTS, + keys: this.state.keys, + }); + } + + activate() { + broadcastMessage(this.win, { + type: messages.FOLLOW_ACTIVATE, + keys: this.state.keys, + }); + } + + keyPress(key, ctrlKey) { + if (key === '[' && ctrlKey) { + this.store.dispatch(followControllerActions.disable()); + return true; + } + switch (key) { + case 'Enter': + this.activate(); + this.store.dispatch(followControllerActions.disable()); + break; + case 'Esc': + this.store.dispatch(followControllerActions.disable()); + break; + case 'Backspace': + case 'Delete': + this.store.dispatch(followControllerActions.backspace()); + break; + default: + if (this.hintchars().includes(key)) { + this.store.dispatch(followControllerActions.keyPress(key)); + } + break; + } + return true; + } + + count() { + this.producer = new HintKeyProducer(this.hintchars()); + let doc = this.win.document; + let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth; + let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight; + let frameElements = this.win.document.querySelectorAll('frame,iframe'); + + this.win.postMessage(JSON.stringify({ + type: messages.FOLLOW_REQUEST_COUNT_TARGETS, + viewSize: { width: viewWidth, height: viewHeight }, + framePosition: { x: 0, y: 0 }, + }), '*'); + frameElements.forEach((element) => { + let { left: frameX, top: frameY } = element.getBoundingClientRect(); + let message = JSON.stringify({ + type: messages.FOLLOW_REQUEST_COUNT_TARGETS, + viewSize: { width: viewWidth, height: viewHeight }, + framePosition: { x: frameX, y: frameY }, + }); + element.contentWindow.postMessage(message, '*'); + }); + } + + create(count, sender) { + let produced = []; + for (let i = 0; i < count; ++i) { + produced.push(this.producer.produce()); + } + this.keys = this.keys.concat(produced); + + sender.postMessage(JSON.stringify({ + type: messages.FOLLOW_CREATE_HINTS, + keysArray: produced, + newTab: this.state.newTab, + background: this.state.background, + }), '*'); + } + + remove() { + this.keys = []; + broadcastMessage(this.win, { + type: messages.FOLLOW_REMOVE_HINTS, + }); + } + + hintchars() { + return this.store.getState().setting.properties.hintchars || + properties.defaults.hintchars; + } +} diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js deleted file mode 100644 index 1aaef1b..0000000 --- a/src/content/components/top-content/index.js +++ /dev/null @@ -1,41 +0,0 @@ -import CommonComponent from '../common'; -import FollowController from './follow-controller'; -import FindComponent from './find'; -import * as consoleFrames from '../../console-frames'; -import messages from 'shared/messages'; -import * as scrolls from 'content/scrolls'; - -export default class TopContent { - - constructor(win, store) { - this.win = win; - this.store = store; - - new CommonComponent(win, store); // eslint-disable-line no-new - new FollowController(win, store); // eslint-disable-line no-new - new FindComponent(win, store); // eslint-disable-line no-new - - // TODO make component - consoleFrames.initialize(this.win.document); - - messages.onMessage(this.onMessage.bind(this)); - } - - onMessage(message) { - let addonState = this.store.getState().addon; - - switch (message.type) { - case messages.CONSOLE_UNFOCUS: - this.win.focus(); - consoleFrames.blur(window.document); - return Promise.resolve(); - case messages.ADDON_ENABLED_QUERY: - return Promise.resolve({ - type: messages.ADDON_ENABLED_RESPONSE, - enabled: addonState.enabled, - }); - case messages.TAB_SCROLL_TO: - return scrolls.scrollTo(message.x, message.y, false); - } - } -} diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts new file mode 100644 index 0000000..1aaef1b --- /dev/null +++ b/src/content/components/top-content/index.ts @@ -0,0 +1,41 @@ +import CommonComponent from '../common'; +import FollowController from './follow-controller'; +import FindComponent from './find'; +import * as consoleFrames from '../../console-frames'; +import messages from 'shared/messages'; +import * as scrolls from 'content/scrolls'; + +export default class TopContent { + + constructor(win, store) { + this.win = win; + this.store = store; + + new CommonComponent(win, store); // eslint-disable-line no-new + new FollowController(win, store); // eslint-disable-line no-new + new FindComponent(win, store); // eslint-disable-line no-new + + // TODO make component + consoleFrames.initialize(this.win.document); + + messages.onMessage(this.onMessage.bind(this)); + } + + onMessage(message) { + let addonState = this.store.getState().addon; + + switch (message.type) { + case messages.CONSOLE_UNFOCUS: + this.win.focus(); + consoleFrames.blur(window.document); + return Promise.resolve(); + case messages.ADDON_ENABLED_QUERY: + return Promise.resolve({ + type: messages.ADDON_ENABLED_RESPONSE, + enabled: addonState.enabled, + }); + case messages.TAB_SCROLL_TO: + return scrolls.scrollTo(message.x, message.y, false); + } + } +} diff --git a/src/content/console-frames.js b/src/content/console-frames.js deleted file mode 100644 index ecb5a87..0000000 --- a/src/content/console-frames.js +++ /dev/null @@ -1,38 +0,0 @@ -import messages from 'shared/messages'; - -const initialize = (doc) => { - let iframe = doc.createElement('iframe'); - iframe.src = browser.runtime.getURL('build/console.html'); - iframe.id = 'vimvixen-console-frame'; - iframe.className = 'vimvixen-console-frame'; - doc.body.append(iframe); - - return iframe; -}; - -const blur = (doc) => { - let iframe = doc.getElementById('vimvixen-console-frame'); - iframe.blur(); -}; - -const postError = (text) => { - browser.runtime.sendMessage({ - type: messages.CONSOLE_FRAME_MESSAGE, - message: { - type: messages.CONSOLE_SHOW_ERROR, - text, - }, - }); -}; - -const postInfo = (text) => { - browser.runtime.sendMessage({ - type: messages.CONSOLE_FRAME_MESSAGE, - message: { - type: messages.CONSOLE_SHOW_INFO, - text, - }, - }); -}; - -export { initialize, blur, postError, postInfo }; diff --git a/src/content/console-frames.ts b/src/content/console-frames.ts new file mode 100644 index 0000000..ecb5a87 --- /dev/null +++ b/src/content/console-frames.ts @@ -0,0 +1,38 @@ +import messages from 'shared/messages'; + +const initialize = (doc) => { + let iframe = doc.createElement('iframe'); + iframe.src = browser.runtime.getURL('build/console.html'); + iframe.id = 'vimvixen-console-frame'; + iframe.className = 'vimvixen-console-frame'; + doc.body.append(iframe); + + return iframe; +}; + +const blur = (doc) => { + let iframe = doc.getElementById('vimvixen-console-frame'); + iframe.blur(); +}; + +const postError = (text) => { + browser.runtime.sendMessage({ + type: messages.CONSOLE_FRAME_MESSAGE, + message: { + type: messages.CONSOLE_SHOW_ERROR, + text, + }, + }); +}; + +const postInfo = (text) => { + browser.runtime.sendMessage({ + type: messages.CONSOLE_FRAME_MESSAGE, + message: { + type: messages.CONSOLE_SHOW_INFO, + text, + }, + }); +}; + +export { initialize, blur, postError, postInfo }; diff --git a/src/content/focuses.js b/src/content/focuses.js deleted file mode 100644 index a6f6cc8..0000000 --- a/src/content/focuses.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as doms from 'shared/utils/dom'; - -const focusInput = () => { - let inputTypes = ['email', 'number', 'search', 'tel', 'text', 'url']; - let inputSelector = inputTypes.map(type => `input[type=${type}]`).join(','); - let targets = window.document.querySelectorAll(inputSelector + ',textarea'); - let target = Array.from(targets).find(doms.isVisible); - if (target) { - target.focus(); - } -}; - -export { focusInput }; diff --git a/src/content/focuses.ts b/src/content/focuses.ts new file mode 100644 index 0000000..a6f6cc8 --- /dev/null +++ b/src/content/focuses.ts @@ -0,0 +1,13 @@ +import * as doms from 'shared/utils/dom'; + +const focusInput = () => { + let inputTypes = ['email', 'number', 'search', 'tel', 'text', 'url']; + let inputSelector = inputTypes.map(type => `input[type=${type}]`).join(','); + let targets = window.document.querySelectorAll(inputSelector + ',textarea'); + let target = Array.from(targets).find(doms.isVisible); + if (target) { + target.focus(); + } +}; + +export { focusInput }; diff --git a/src/content/hint-key-producer.js b/src/content/hint-key-producer.js deleted file mode 100644 index 14b23b6..0000000 --- a/src/content/hint-key-producer.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class HintKeyProducer { - constructor(charset) { - if (charset.length === 0) { - throw new TypeError('charset is empty'); - } - - this.charset = charset; - this.counter = []; - } - - produce() { - this.increment(); - - return this.counter.map(x => this.charset[x]).join(''); - } - - increment() { - let max = this.charset.length - 1; - if (this.counter.every(x => x === max)) { - this.counter = new Array(this.counter.length + 1).fill(0); - return; - } - - this.counter.reverse(); - let len = this.charset.length; - let num = this.counter.reduce((x, y, index) => x + y * len ** index) + 1; - for (let i = 0; i < this.counter.length; ++i) { - this.counter[i] = num % len; - num = ~~(num / len); - } - this.counter.reverse(); - } -} diff --git a/src/content/hint-key-producer.ts b/src/content/hint-key-producer.ts new file mode 100644 index 0000000..14b23b6 --- /dev/null +++ b/src/content/hint-key-producer.ts @@ -0,0 +1,33 @@ +export default class HintKeyProducer { + constructor(charset) { + if (charset.length === 0) { + throw new TypeError('charset is empty'); + } + + this.charset = charset; + this.counter = []; + } + + produce() { + this.increment(); + + return this.counter.map(x => this.charset[x]).join(''); + } + + increment() { + let max = this.charset.length - 1; + if (this.counter.every(x => x === max)) { + this.counter = new Array(this.counter.length + 1).fill(0); + return; + } + + this.counter.reverse(); + let len = this.charset.length; + let num = this.counter.reduce((x, y, index) => x + y * len ** index) + 1; + for (let i = 0; i < this.counter.length; ++i) { + this.counter[i] = num % len; + num = ~~(num / len); + } + this.counter.reverse(); + } +} diff --git a/src/content/index.js b/src/content/index.js deleted file mode 100644 index 9edb712..0000000 --- a/src/content/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import { createStore, applyMiddleware } from 'redux'; -import promise from 'redux-promise'; -import reducers from 'content/reducers'; -import TopContentComponent from './components/top-content'; -import FrameContentComponent from './components/frame-content'; -import consoleFrameStyle from './site-style'; - -const store = createStore( - reducers, - applyMiddleware(promise), -); - -if (window.self === window.top) { - new TopContentComponent(window, store); // eslint-disable-line no-new -} else { - new FrameContentComponent(window, store); // eslint-disable-line no-new -} - -let style = window.document.createElement('style'); -style.textContent = consoleFrameStyle.default; -window.document.head.appendChild(style); diff --git a/src/content/index.ts b/src/content/index.ts new file mode 100644 index 0000000..9edb712 --- /dev/null +++ b/src/content/index.ts @@ -0,0 +1,21 @@ +import { createStore, applyMiddleware } from 'redux'; +import promise from 'redux-promise'; +import reducers from 'content/reducers'; +import TopContentComponent from './components/top-content'; +import FrameContentComponent from './components/frame-content'; +import consoleFrameStyle from './site-style'; + +const store = createStore( + reducers, + applyMiddleware(promise), +); + +if (window.self === window.top) { + new TopContentComponent(window, store); // eslint-disable-line no-new +} else { + new FrameContentComponent(window, store); // eslint-disable-line no-new +} + +let style = window.document.createElement('style'); +style.textContent = consoleFrameStyle.default; +window.document.head.appendChild(style); diff --git a/src/content/navigates.js b/src/content/navigates.js deleted file mode 100644 index c9baa30..0000000 --- a/src/content/navigates.js +++ /dev/null @@ -1,78 +0,0 @@ -const REL_PATTERN = { - prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<>/i, -}; - -// Return the last element in the document matching the supplied selector -// and the optional filter, or null if there are no matches. -const selectLast = (win, selector, filter) => { - let nodes = win.document.querySelectorAll(selector); - - if (filter) { - nodes = Array.from(nodes).filter(filter); - } - - return nodes.length ? nodes[nodes.length - 1] : null; -}; - -const historyPrev = (win) => { - win.history.back(); -}; - -const historyNext = (win) => { - win.history.forward(); -}; - -// Code common to linkPrev and linkNext which navigates to the specified page. -const linkRel = (win, rel) => { - let link = selectLast(win, `link[rel~=${rel}][href]`); - - if (link) { - win.location = link.href; - return; - } - - const pattern = REL_PATTERN[rel]; - - link = selectLast(win, `a[rel~=${rel}][href]`) || - // `innerText` is much slower than `textContent`, but produces much better - // (i.e. less unexpected) results - selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText)); - - if (link) { - link.click(); - } -}; - -const linkPrev = (win) => { - linkRel(win, 'prev'); -}; - -const linkNext = (win) => { - linkRel(win, 'next'); -}; - -const parent = (win) => { - const loc = win.location; - if (loc.hash !== '') { - loc.hash = ''; - return; - } else if (loc.search !== '') { - loc.search = ''; - return; - } - - const basenamePattern = /\/[^/]+$/; - const lastDirPattern = /\/[^/]+\/$/; - if (basenamePattern.test(loc.pathname)) { - loc.pathname = loc.pathname.replace(basenamePattern, '/'); - } else if (lastDirPattern.test(loc.pathname)) { - loc.pathname = loc.pathname.replace(lastDirPattern, '/'); - } -}; - -const root = (win) => { - win.location = win.location.origin; -}; - -export { historyPrev, historyNext, linkPrev, linkNext, parent, root }; diff --git a/src/content/navigates.ts b/src/content/navigates.ts new file mode 100644 index 0000000..c9baa30 --- /dev/null +++ b/src/content/navigates.ts @@ -0,0 +1,78 @@ +const REL_PATTERN = { + prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<>/i, +}; + +// Return the last element in the document matching the supplied selector +// and the optional filter, or null if there are no matches. +const selectLast = (win, selector, filter) => { + let nodes = win.document.querySelectorAll(selector); + + if (filter) { + nodes = Array.from(nodes).filter(filter); + } + + return nodes.length ? nodes[nodes.length - 1] : null; +}; + +const historyPrev = (win) => { + win.history.back(); +}; + +const historyNext = (win) => { + win.history.forward(); +}; + +// Code common to linkPrev and linkNext which navigates to the specified page. +const linkRel = (win, rel) => { + let link = selectLast(win, `link[rel~=${rel}][href]`); + + if (link) { + win.location = link.href; + return; + } + + const pattern = REL_PATTERN[rel]; + + link = selectLast(win, `a[rel~=${rel}][href]`) || + // `innerText` is much slower than `textContent`, but produces much better + // (i.e. less unexpected) results + selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText)); + + if (link) { + link.click(); + } +}; + +const linkPrev = (win) => { + linkRel(win, 'prev'); +}; + +const linkNext = (win) => { + linkRel(win, 'next'); +}; + +const parent = (win) => { + const loc = win.location; + if (loc.hash !== '') { + loc.hash = ''; + return; + } else if (loc.search !== '') { + loc.search = ''; + return; + } + + const basenamePattern = /\/[^/]+$/; + const lastDirPattern = /\/[^/]+\/$/; + if (basenamePattern.test(loc.pathname)) { + loc.pathname = loc.pathname.replace(basenamePattern, '/'); + } else if (lastDirPattern.test(loc.pathname)) { + loc.pathname = loc.pathname.replace(lastDirPattern, '/'); + } +}; + +const root = (win) => { + win.location = win.location.origin; +}; + +export { historyPrev, historyNext, linkPrev, linkNext, parent, root }; diff --git a/src/content/reducers/addon.js b/src/content/reducers/addon.js deleted file mode 100644 index 0def55a..0000000 --- a/src/content/reducers/addon.js +++ /dev/null @@ -1,15 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - enabled: true, -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.ADDON_SET_ENABLED: - return { ...state, - enabled: action.enabled, }; - default: - return state; - } -} diff --git a/src/content/reducers/addon.ts b/src/content/reducers/addon.ts new file mode 100644 index 0000000..0def55a --- /dev/null +++ b/src/content/reducers/addon.ts @@ -0,0 +1,15 @@ +import actions from 'content/actions'; + +const defaultState = { + enabled: true, +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.ADDON_SET_ENABLED: + return { ...state, + enabled: action.enabled, }; + default: + return state; + } +} diff --git a/src/content/reducers/find.js b/src/content/reducers/find.js deleted file mode 100644 index 4560e2c..0000000 --- a/src/content/reducers/find.js +++ /dev/null @@ -1,17 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - keyword: null, - found: false, -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.FIND_SET_KEYWORD: - return { ...state, - keyword: action.keyword, - found: action.found, }; - default: - return state; - } -} diff --git a/src/content/reducers/find.ts b/src/content/reducers/find.ts new file mode 100644 index 0000000..4560e2c --- /dev/null +++ b/src/content/reducers/find.ts @@ -0,0 +1,17 @@ +import actions from 'content/actions'; + +const defaultState = { + keyword: null, + found: false, +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.FIND_SET_KEYWORD: + return { ...state, + keyword: action.keyword, + found: action.found, }; + default: + return state; + } +} diff --git a/src/content/reducers/follow-controller.js b/src/content/reducers/follow-controller.js deleted file mode 100644 index 5869c47..0000000 --- a/src/content/reducers/follow-controller.js +++ /dev/null @@ -1,30 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - enabled: false, - newTab: false, - background: false, - keys: '', -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.FOLLOW_CONTROLLER_ENABLE: - return { ...state, - enabled: true, - newTab: action.newTab, - background: action.background, - keys: '', }; - case actions.FOLLOW_CONTROLLER_DISABLE: - return { ...state, - enabled: false, }; - case actions.FOLLOW_CONTROLLER_KEY_PRESS: - return { ...state, - keys: state.keys + action.key, }; - case actions.FOLLOW_CONTROLLER_BACKSPACE: - return { ...state, - keys: state.keys.slice(0, -1), }; - default: - return state; - } -} diff --git a/src/content/reducers/follow-controller.ts b/src/content/reducers/follow-controller.ts new file mode 100644 index 0000000..5869c47 --- /dev/null +++ b/src/content/reducers/follow-controller.ts @@ -0,0 +1,30 @@ +import actions from 'content/actions'; + +const defaultState = { + enabled: false, + newTab: false, + background: false, + keys: '', +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.FOLLOW_CONTROLLER_ENABLE: + return { ...state, + enabled: true, + newTab: action.newTab, + background: action.background, + keys: '', }; + case actions.FOLLOW_CONTROLLER_DISABLE: + return { ...state, + enabled: false, }; + case actions.FOLLOW_CONTROLLER_KEY_PRESS: + return { ...state, + keys: state.keys + action.key, }; + case actions.FOLLOW_CONTROLLER_BACKSPACE: + return { ...state, + keys: state.keys.slice(0, -1), }; + default: + return state; + } +} diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js deleted file mode 100644 index bf612a3..0000000 --- a/src/content/reducers/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import { combineReducers } from 'redux'; -import addon from './addon'; -import find from './find'; -import setting from './setting'; -import input from './input'; -import followController from './follow-controller'; -import mark from './mark'; - -export default combineReducers({ - addon, find, setting, input, followController, mark, -}); diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts new file mode 100644 index 0000000..bf612a3 --- /dev/null +++ b/src/content/reducers/index.ts @@ -0,0 +1,11 @@ +import { combineReducers } from 'redux'; +import addon from './addon'; +import find from './find'; +import setting from './setting'; +import input from './input'; +import followController from './follow-controller'; +import mark from './mark'; + +export default combineReducers({ + addon, find, setting, input, followController, mark, +}); diff --git a/src/content/reducers/input.js b/src/content/reducers/input.js deleted file mode 100644 index 23e7dd2..0000000 --- a/src/content/reducers/input.js +++ /dev/null @@ -1,18 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - keys: [] -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.INPUT_KEY_PRESS: - return { ...state, - keys: state.keys.concat([action.key]), }; - case actions.INPUT_CLEAR_KEYS: - return { ...state, - keys: [], }; - default: - return state; - } -} diff --git a/src/content/reducers/input.ts b/src/content/reducers/input.ts new file mode 100644 index 0000000..23e7dd2 --- /dev/null +++ b/src/content/reducers/input.ts @@ -0,0 +1,18 @@ +import actions from 'content/actions'; + +const defaultState = { + keys: [] +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.INPUT_KEY_PRESS: + return { ...state, + keys: state.keys.concat([action.key]), }; + case actions.INPUT_CLEAR_KEYS: + return { ...state, + keys: [], }; + default: + return state; + } +} diff --git a/src/content/reducers/mark.js b/src/content/reducers/mark.js deleted file mode 100644 index 2c96cc5..0000000 --- a/src/content/reducers/mark.js +++ /dev/null @@ -1,25 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - setMode: false, - jumpMode: false, - marks: {}, -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.MARK_START_SET: - return { ...state, setMode: true }; - case actions.MARK_START_JUMP: - return { ...state, jumpMode: true }; - case actions.MARK_CANCEL: - return { ...state, setMode: false, jumpMode: false }; - case actions.MARK_SET_LOCAL: { - let marks = { ...state.marks }; - marks[action.key] = { x: action.x, y: action.y }; - return { ...state, setMode: false, marks }; - } - default: - return state; - } -} diff --git a/src/content/reducers/mark.ts b/src/content/reducers/mark.ts new file mode 100644 index 0000000..2c96cc5 --- /dev/null +++ b/src/content/reducers/mark.ts @@ -0,0 +1,25 @@ +import actions from 'content/actions'; + +const defaultState = { + setMode: false, + jumpMode: false, + marks: {}, +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.MARK_START_SET: + return { ...state, setMode: true }; + case actions.MARK_START_JUMP: + return { ...state, jumpMode: true }; + case actions.MARK_CANCEL: + return { ...state, setMode: false, jumpMode: false }; + case actions.MARK_SET_LOCAL: { + let marks = { ...state.marks }; + marks[action.key] = { x: action.x, y: action.y }; + return { ...state, setMode: false, marks }; + } + default: + return state; + } +} diff --git a/src/content/reducers/setting.js b/src/content/reducers/setting.js deleted file mode 100644 index a49db6d..0000000 --- a/src/content/reducers/setting.js +++ /dev/null @@ -1,16 +0,0 @@ -import actions from 'content/actions'; - -const defaultState = { - // keymaps is and arrays of key-binding pairs, which is entries of Map - keymaps: [], -}; - -export default function reducer(state = defaultState, action = {}) { - switch (action.type) { - case actions.SETTING_SET: - return { ...action.value }; - default: - return state; - } -} - diff --git a/src/content/reducers/setting.ts b/src/content/reducers/setting.ts new file mode 100644 index 0000000..a49db6d --- /dev/null +++ b/src/content/reducers/setting.ts @@ -0,0 +1,16 @@ +import actions from 'content/actions'; + +const defaultState = { + // keymaps is and arrays of key-binding pairs, which is entries of Map + keymaps: [], +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.SETTING_SET: + return { ...action.value }; + default: + return state; + } +} + diff --git a/src/content/scrolls.js b/src/content/scrolls.js deleted file mode 100644 index f3124a1..0000000 --- a/src/content/scrolls.js +++ /dev/null @@ -1,174 +0,0 @@ -import * as doms from 'shared/utils/dom'; - -const SCROLL_DELTA_X = 64; -const SCROLL_DELTA_Y = 64; - -// dirty way to store scrolling state on globally -let scrolling = false; -let lastTimeoutId = null; - -const isScrollableStyle = (element) => { - let { overflowX, overflowY } = window.getComputedStyle(element); - return !(overflowX !== 'scroll' && overflowX !== 'auto' && - overflowY !== 'scroll' && overflowY !== 'auto'); -}; - -const isOverflowed = (element) => { - return element.scrollWidth > element.clientWidth || - element.scrollHeight > element.clientHeight; -}; - -// Find a visiable and scrollable element by depth-first search. Currently -// this method is called by each scrolling, and the returned value of this -// method is not cached. That does not cause performance issue because in the -// most pages, the window is root element i,e, documentElement. -const findScrollable = (element) => { - if (isScrollableStyle(element) && isOverflowed(element)) { - return element; - } - - let children = Array.from(element.children).filter(doms.isVisible); - for (let child of children) { - let scrollable = findScrollable(child); - if (scrollable) { - return scrollable; - } - } - return null; -}; - -const scrollTarget = () => { - if (isOverflowed(window.document.documentElement)) { - return window.document.documentElement; - } - if (isOverflowed(window.document.body)) { - return window.document.body; - } - let target = findScrollable(window.document.documentElement); - if (target) { - return target; - } - return window.document.documentElement; -}; - -const resetScrolling = () => { - scrolling = false; -}; - -class Scroller { - constructor(element, smooth) { - this.element = element; - this.smooth = smooth; - } - - scrollTo(x, y) { - if (!this.smooth) { - this.element.scrollTo(x, y); - return; - } - this.element.scrollTo({ - left: x, - top: y, - behavior: 'smooth', - }); - this.prepareReset(); - } - - scrollBy(x, y) { - let left = this.element.scrollLeft + x; - let top = this.element.scrollTop + y; - this.scrollTo(left, top); - } - - prepareReset() { - scrolling = true; - if (lastTimeoutId) { - clearTimeout(lastTimeoutId); - lastTimeoutId = null; - } - lastTimeoutId = setTimeout(resetScrolling, 100); - } -} - -class RoughtScroller { - constructor(element) { - this.element = element; - } - - scroll(x, y) { - this.element.scrollTo(x, y); - } -} - -const getScroll = () => { - let target = scrollTarget(); - return { x: target.scrollLeft, y: target.scrollTop }; -}; - -const scrollVertically = (count, smooth) => { - let target = scrollTarget(); - let delta = SCROLL_DELTA_Y * count; - if (scrolling) { - delta = SCROLL_DELTA_Y * count * 4; - } - new Scroller(target, smooth).scrollBy(0, delta); -}; - -const scrollHorizonally = (count, smooth) => { - let target = scrollTarget(); - let delta = SCROLL_DELTA_X * count; - if (scrolling) { - delta = SCROLL_DELTA_X * count * 4; - } - new Scroller(target, smooth).scrollBy(delta, 0); -}; - -const scrollPages = (count, smooth) => { - let target = scrollTarget(); - let height = target.clientHeight; - let delta = height * count; - if (scrolling) { - delta = height * count; - } - new Scroller(target, smooth).scrollBy(0, delta); -}; - -const scrollTo = (x, y, smooth) => { - let target = scrollTarget(); - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToTop = (smooth) => { - let target = scrollTarget(); - let x = target.scrollLeft; - let y = 0; - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToBottom = (smooth) => { - let target = scrollTarget(); - let x = target.scrollLeft; - let y = target.scrollHeight; - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToHome = (smooth) => { - let target = scrollTarget(); - let x = 0; - let y = target.scrollTop; - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToEnd = (smooth) => { - let target = scrollTarget(); - let x = target.scrollWidth; - let y = target.scrollTop; - new Scroller(target, smooth).scrollTo(x, y); -}; - -export { - getScroll, - scrollVertically, scrollHorizonally, scrollPages, - scrollTo, - scrollToTop, scrollToBottom, scrollToHome, scrollToEnd -}; diff --git a/src/content/scrolls.ts b/src/content/scrolls.ts new file mode 100644 index 0000000..f3124a1 --- /dev/null +++ b/src/content/scrolls.ts @@ -0,0 +1,174 @@ +import * as doms from 'shared/utils/dom'; + +const SCROLL_DELTA_X = 64; +const SCROLL_DELTA_Y = 64; + +// dirty way to store scrolling state on globally +let scrolling = false; +let lastTimeoutId = null; + +const isScrollableStyle = (element) => { + let { overflowX, overflowY } = window.getComputedStyle(element); + return !(overflowX !== 'scroll' && overflowX !== 'auto' && + overflowY !== 'scroll' && overflowY !== 'auto'); +}; + +const isOverflowed = (element) => { + return element.scrollWidth > element.clientWidth || + element.scrollHeight > element.clientHeight; +}; + +// Find a visiable and scrollable element by depth-first search. Currently +// this method is called by each scrolling, and the returned value of this +// method is not cached. That does not cause performance issue because in the +// most pages, the window is root element i,e, documentElement. +const findScrollable = (element) => { + if (isScrollableStyle(element) && isOverflowed(element)) { + return element; + } + + let children = Array.from(element.children).filter(doms.isVisible); + for (let child of children) { + let scrollable = findScrollable(child); + if (scrollable) { + return scrollable; + } + } + return null; +}; + +const scrollTarget = () => { + if (isOverflowed(window.document.documentElement)) { + return window.document.documentElement; + } + if (isOverflowed(window.document.body)) { + return window.document.body; + } + let target = findScrollable(window.document.documentElement); + if (target) { + return target; + } + return window.document.documentElement; +}; + +const resetScrolling = () => { + scrolling = false; +}; + +class Scroller { + constructor(element, smooth) { + this.element = element; + this.smooth = smooth; + } + + scrollTo(x, y) { + if (!this.smooth) { + this.element.scrollTo(x, y); + return; + } + this.element.scrollTo({ + left: x, + top: y, + behavior: 'smooth', + }); + this.prepareReset(); + } + + scrollBy(x, y) { + let left = this.element.scrollLeft + x; + let top = this.element.scrollTop + y; + this.scrollTo(left, top); + } + + prepareReset() { + scrolling = true; + if (lastTimeoutId) { + clearTimeout(lastTimeoutId); + lastTimeoutId = null; + } + lastTimeoutId = setTimeout(resetScrolling, 100); + } +} + +class RoughtScroller { + constructor(element) { + this.element = element; + } + + scroll(x, y) { + this.element.scrollTo(x, y); + } +} + +const getScroll = () => { + let target = scrollTarget(); + return { x: target.scrollLeft, y: target.scrollTop }; +}; + +const scrollVertically = (count, smooth) => { + let target = scrollTarget(); + let delta = SCROLL_DELTA_Y * count; + if (scrolling) { + delta = SCROLL_DELTA_Y * count * 4; + } + new Scroller(target, smooth).scrollBy(0, delta); +}; + +const scrollHorizonally = (count, smooth) => { + let target = scrollTarget(); + let delta = SCROLL_DELTA_X * count; + if (scrolling) { + delta = SCROLL_DELTA_X * count * 4; + } + new Scroller(target, smooth).scrollBy(delta, 0); +}; + +const scrollPages = (count, smooth) => { + let target = scrollTarget(); + let height = target.clientHeight; + let delta = height * count; + if (scrolling) { + delta = height * count; + } + new Scroller(target, smooth).scrollBy(0, delta); +}; + +const scrollTo = (x, y, smooth) => { + let target = scrollTarget(); + new Scroller(target, smooth).scrollTo(x, y); +}; + +const scrollToTop = (smooth) => { + let target = scrollTarget(); + let x = target.scrollLeft; + let y = 0; + new Scroller(target, smooth).scrollTo(x, y); +}; + +const scrollToBottom = (smooth) => { + let target = scrollTarget(); + let x = target.scrollLeft; + let y = target.scrollHeight; + new Scroller(target, smooth).scrollTo(x, y); +}; + +const scrollToHome = (smooth) => { + let target = scrollTarget(); + let x = 0; + let y = target.scrollTop; + new Scroller(target, smooth).scrollTo(x, y); +}; + +const scrollToEnd = (smooth) => { + let target = scrollTarget(); + let x = target.scrollWidth; + let y = target.scrollTop; + new Scroller(target, smooth).scrollTo(x, y); +}; + +export { + getScroll, + scrollVertically, scrollHorizonally, scrollPages, + scrollTo, + scrollToTop, scrollToBottom, scrollToHome, scrollToEnd +}; diff --git a/src/content/site-style.js b/src/content/site-style.js deleted file mode 100644 index e7a82a5..0000000 --- a/src/content/site-style.js +++ /dev/null @@ -1,26 +0,0 @@ -exports.default = ` -.vimvixen-console-frame { - margin: 0; - padding: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - position: fixed; - z-index: 2147483647; - border: none; - background-color: unset; - pointer-events:none; -} - -.vimvixen-hint { - background-color: yellow; - border: 1px solid gold; - font-weight: bold; - position: absolute; - text-transform: uppercase; - z-index: 2147483647; - font-size: 12px; - color: black; -} -`; diff --git a/src/content/site-style.ts b/src/content/site-style.ts new file mode 100644 index 0000000..e7a82a5 --- /dev/null +++ b/src/content/site-style.ts @@ -0,0 +1,26 @@ +exports.default = ` +.vimvixen-console-frame { + margin: 0; + padding: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + position: fixed; + z-index: 2147483647; + border: none; + background-color: unset; + pointer-events:none; +} + +.vimvixen-hint { + background-color: yellow; + border: 1px solid gold; + font-weight: bold; + position: absolute; + text-transform: uppercase; + z-index: 2147483647; + font-size: 12px; + color: black; +} +`; diff --git a/src/content/urls.js b/src/content/urls.js deleted file mode 100644 index 6e7ea31..0000000 --- a/src/content/urls.js +++ /dev/null @@ -1,40 +0,0 @@ -import messages from 'shared/messages'; -import * as urls from '../shared/urls'; - -const yank = (win) => { - let input = win.document.createElement('input'); - win.document.body.append(input); - - input.style.position = 'fixed'; - input.style.top = '-100px'; - input.value = win.location.href; - input.select(); - - win.document.execCommand('copy'); - - input.remove(); -}; - -const paste = (win, newTab, searchSettings) => { - let textarea = win.document.createElement('textarea'); - win.document.body.append(textarea); - - textarea.style.position = 'fixed'; - textarea.style.top = '-100px'; - textarea.contentEditable = 'true'; - textarea.focus(); - - if (win.document.execCommand('paste')) { - let value = textarea.textContent; - let url = urls.searchUrl(value, searchSettings); - browser.runtime.sendMessage({ - type: messages.OPEN_URL, - url, - newTab, - }); - } - - textarea.remove(); -}; - -export { yank, paste }; diff --git a/src/content/urls.ts b/src/content/urls.ts new file mode 100644 index 0000000..6e7ea31 --- /dev/null +++ b/src/content/urls.ts @@ -0,0 +1,40 @@ +import messages from 'shared/messages'; +import * as urls from '../shared/urls'; + +const yank = (win) => { + let input = win.document.createElement('input'); + win.document.body.append(input); + + input.style.position = 'fixed'; + input.style.top = '-100px'; + input.value = win.location.href; + input.select(); + + win.document.execCommand('copy'); + + input.remove(); +}; + +const paste = (win, newTab, searchSettings) => { + let textarea = win.document.createElement('textarea'); + win.document.body.append(textarea); + + textarea.style.position = 'fixed'; + textarea.style.top = '-100px'; + textarea.contentEditable = 'true'; + textarea.focus(); + + if (win.document.execCommand('paste')) { + let value = textarea.textContent; + let url = urls.searchUrl(value, searchSettings); + browser.runtime.sendMessage({ + type: messages.OPEN_URL, + url, + newTab, + }); + } + + textarea.remove(); +}; + +export { yank, paste }; diff --git a/src/settings/actions/index.js b/src/settings/actions/index.js deleted file mode 100644 index 016f2a5..0000000 --- a/src/settings/actions/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - // Settings - SETTING_SET_SETTINGS: 'setting.set.settings', - SETTING_SHOW_ERROR: 'setting.show.error', - SETTING_SWITCH_TO_FORM: 'setting.switch.to.form', - SETTING_SWITCH_TO_JSON: 'setting.switch.to.json', -}; diff --git a/src/settings/actions/index.ts b/src/settings/actions/index.ts new file mode 100644 index 0000000..016f2a5 --- /dev/null +++ b/src/settings/actions/index.ts @@ -0,0 +1,7 @@ +export default { + // Settings + SETTING_SET_SETTINGS: 'setting.set.settings', + SETTING_SHOW_ERROR: 'setting.show.error', + SETTING_SWITCH_TO_FORM: 'setting.switch.to.form', + SETTING_SWITCH_TO_JSON: 'setting.switch.to.json', +}; diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js deleted file mode 100644 index db63a45..0000000 --- a/src/settings/actions/setting.js +++ /dev/null @@ -1,63 +0,0 @@ -import actions from 'settings/actions'; -import * as validator from 'shared/settings/validator'; -import * as settingsValues from 'shared/settings/values'; -import * as settingsStorage from 'shared/settings/storage'; -import keymaps from '../keymaps'; - -const load = async() => { - let settings = await settingsStorage.loadRaw(); - return set(settings); -}; - -const save = async(settings) => { - try { - if (settings.source === 'json') { - let value = JSON.parse(settings.json); - validator.validate(value); - } - } catch (e) { - return { - type: actions.SETTING_SHOW_ERROR, - error: e.toString(), - json: settings.json, - }; - } - await settingsStorage.save(settings); - return set(settings); -}; - -const switchToForm = (json) => { - try { - validator.validate(JSON.parse(json)); - let form = settingsValues.formFromJson(json, keymaps.allowedOps); - return { - type: actions.SETTING_SWITCH_TO_FORM, - form, - }; - } catch (e) { - return { - type: actions.SETTING_SHOW_ERROR, - error: e.toString(), - json, - }; - } -}; - -const switchToJson = (form) => { - let json = settingsValues.jsonFromForm(form); - return { - type: actions.SETTING_SWITCH_TO_JSON, - json, - }; -}; - -const set = (settings) => { - return { - type: actions.SETTING_SET_SETTINGS, - source: settings.source, - json: settings.json, - form: settings.form, - }; -}; - -export { load, save, set, switchToForm, switchToJson }; diff --git a/src/settings/actions/setting.ts b/src/settings/actions/setting.ts new file mode 100644 index 0000000..db63a45 --- /dev/null +++ b/src/settings/actions/setting.ts @@ -0,0 +1,63 @@ +import actions from 'settings/actions'; +import * as validator from 'shared/settings/validator'; +import * as settingsValues from 'shared/settings/values'; +import * as settingsStorage from 'shared/settings/storage'; +import keymaps from '../keymaps'; + +const load = async() => { + let settings = await settingsStorage.loadRaw(); + return set(settings); +}; + +const save = async(settings) => { + try { + if (settings.source === 'json') { + let value = JSON.parse(settings.json); + validator.validate(value); + } + } catch (e) { + return { + type: actions.SETTING_SHOW_ERROR, + error: e.toString(), + json: settings.json, + }; + } + await settingsStorage.save(settings); + return set(settings); +}; + +const switchToForm = (json) => { + try { + validator.validate(JSON.parse(json)); + let form = settingsValues.formFromJson(json, keymaps.allowedOps); + return { + type: actions.SETTING_SWITCH_TO_FORM, + form, + }; + } catch (e) { + return { + type: actions.SETTING_SHOW_ERROR, + error: e.toString(), + json, + }; + } +}; + +const switchToJson = (form) => { + let json = settingsValues.jsonFromForm(form); + return { + type: actions.SETTING_SWITCH_TO_JSON, + json, + }; +}; + +const set = (settings) => { + return { + type: actions.SETTING_SET_SETTINGS, + source: settings.source, + json: settings.json, + form: settings.form, + }; +}; + +export { load, save, set, switchToForm, switchToJson }; diff --git a/src/settings/components/form/BlacklistForm.jsx b/src/settings/components/form/BlacklistForm.jsx deleted file mode 100644 index c470758..0000000 --- a/src/settings/components/form/BlacklistForm.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import './BlacklistForm.scss'; -import AddButton from '../ui/AddButton'; -import DeleteButton from '../ui/DeleteButton'; -import React from 'react'; -import PropTypes from 'prop-types'; - -class BlacklistForm extends React.Component { - - render() { - return
    - { - this.props.value.map((url, index) => { - return
    - - -
    ; - }) - } - -
    ; - } - - bindValue(e) { - let name = e.target.name; - let index = e.target.getAttribute('data-index'); - let next = this.props.value ? this.props.value.slice() : []; - - if (name === 'url') { - next[index] = e.target.value; - } else if (name === 'add') { - next.push(''); - } else if (name === 'delete') { - next.splice(index, 1); - } - - this.props.onChange(next); - if (name === 'delete') { - this.props.onBlur(); - } - } -} - -BlacklistForm.propTypes = { - value: PropTypes.arrayOf(PropTypes.string), - onChange: PropTypes.func, - onBlur: PropTypes.func, -}; - -BlacklistForm.defaultProps = { - value: [], - onChange: () => {}, - onBlur: () => {}, -}; - -export default BlacklistForm; diff --git a/src/settings/components/form/BlacklistForm.tsx b/src/settings/components/form/BlacklistForm.tsx new file mode 100644 index 0000000..c470758 --- /dev/null +++ b/src/settings/components/form/BlacklistForm.tsx @@ -0,0 +1,63 @@ +import './BlacklistForm.scss'; +import AddButton from '../ui/AddButton'; +import DeleteButton from '../ui/DeleteButton'; +import React from 'react'; +import PropTypes from 'prop-types'; + +class BlacklistForm extends React.Component { + + render() { + return
    + { + this.props.value.map((url, index) => { + return
    + + +
    ; + }) + } + +
    ; + } + + bindValue(e) { + let name = e.target.name; + let index = e.target.getAttribute('data-index'); + let next = this.props.value ? this.props.value.slice() : []; + + if (name === 'url') { + next[index] = e.target.value; + } else if (name === 'add') { + next.push(''); + } else if (name === 'delete') { + next.splice(index, 1); + } + + this.props.onChange(next); + if (name === 'delete') { + this.props.onBlur(); + } + } +} + +BlacklistForm.propTypes = { + value: PropTypes.arrayOf(PropTypes.string), + onChange: PropTypes.func, + onBlur: PropTypes.func, +}; + +BlacklistForm.defaultProps = { + value: [], + onChange: () => {}, + onBlur: () => {}, +}; + +export default BlacklistForm; diff --git a/src/settings/components/form/KeymapsForm.jsx b/src/settings/components/form/KeymapsForm.jsx deleted file mode 100644 index 01acf61..0000000 --- a/src/settings/components/form/KeymapsForm.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import './KeymapsForm.scss'; -import React from 'react'; -import PropTypes from 'prop-types'; -import Input from '../ui/Input'; -import keymaps from '../../keymaps'; - -class KeymapsForm extends React.Component { - - render() { - return
    - { - keymaps.fields.map((group, index) => { - return
    - { - group.map((field) => { - let name = field[0]; - let label = field[1]; - let value = this.props.value[name] || ''; - return ; - }) - } -
    ; - }) - } -
    ; - } - - bindValue(e) { - let next = { ...this.props.value }; - next[e.target.name] = e.target.value; - - this.props.onChange(next); - } -} - -KeymapsForm.propTypes = { - value: PropTypes.objectOf(PropTypes.string), - onChange: PropTypes.func, -}; - -KeymapsForm.defaultProps = { - value: {}, - onChange: () => {}, -}; - -export default KeymapsForm; diff --git a/src/settings/components/form/KeymapsForm.tsx b/src/settings/components/form/KeymapsForm.tsx new file mode 100644 index 0000000..01acf61 --- /dev/null +++ b/src/settings/components/form/KeymapsForm.tsx @@ -0,0 +1,51 @@ +import './KeymapsForm.scss'; +import React from 'react'; +import PropTypes from 'prop-types'; +import Input from '../ui/Input'; +import keymaps from '../../keymaps'; + +class KeymapsForm extends React.Component { + + render() { + return
    + { + keymaps.fields.map((group, index) => { + return
    + { + group.map((field) => { + let name = field[0]; + let label = field[1]; + let value = this.props.value[name] || ''; + return ; + }) + } +
    ; + }) + } +
    ; + } + + bindValue(e) { + let next = { ...this.props.value }; + next[e.target.name] = e.target.value; + + this.props.onChange(next); + } +} + +KeymapsForm.propTypes = { + value: PropTypes.objectOf(PropTypes.string), + onChange: PropTypes.func, +}; + +KeymapsForm.defaultProps = { + value: {}, + onChange: () => {}, +}; + +export default KeymapsForm; diff --git a/src/settings/components/form/PropertiesForm.jsx b/src/settings/components/form/PropertiesForm.jsx deleted file mode 100644 index 979fdd8..0000000 --- a/src/settings/components/form/PropertiesForm.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import './PropertiesForm.scss'; -import React from 'react'; -import PropTypes from 'prop-types'; - -class PropertiesForm extends React.Component { - - render() { - let types = this.props.types; - let value = this.props.value; - - return
    - { - Object.keys(types).map((name) => { - let type = types[name]; - let inputType = null; - if (type === 'string') { - inputType = 'text'; - } else if (type === 'number') { - inputType = 'number'; - } else if (type === 'boolean') { - inputType = 'checkbox'; - } - return
    - -
    ; - }) - } -
    ; - } - - bindValue(e) { - let name = e.target.name; - let next = { ...this.props.value }; - if (e.target.type.toLowerCase() === 'checkbox') { - next[name] = e.target.checked; - } else if (e.target.type.toLowerCase() === 'number') { - next[name] = Number(e.target.value); - } else { - next[name] = e.target.value; - } - - this.props.onChange(next); - } -} - -PropertiesForm.propTypes = { - value: PropTypes.objectOf(PropTypes.any), - onChange: PropTypes.func, -}; - -PropertiesForm.defaultProps = { - value: {}, - onChange: () => {}, -}; - -export default PropertiesForm; diff --git a/src/settings/components/form/PropertiesForm.tsx b/src/settings/components/form/PropertiesForm.tsx new file mode 100644 index 0000000..979fdd8 --- /dev/null +++ b/src/settings/components/form/PropertiesForm.tsx @@ -0,0 +1,65 @@ +import './PropertiesForm.scss'; +import React from 'react'; +import PropTypes from 'prop-types'; + +class PropertiesForm extends React.Component { + + render() { + let types = this.props.types; + let value = this.props.value; + + return
    + { + Object.keys(types).map((name) => { + let type = types[name]; + let inputType = null; + if (type === 'string') { + inputType = 'text'; + } else if (type === 'number') { + inputType = 'number'; + } else if (type === 'boolean') { + inputType = 'checkbox'; + } + return
    + +
    ; + }) + } +
    ; + } + + bindValue(e) { + let name = e.target.name; + let next = { ...this.props.value }; + if (e.target.type.toLowerCase() === 'checkbox') { + next[name] = e.target.checked; + } else if (e.target.type.toLowerCase() === 'number') { + next[name] = Number(e.target.value); + } else { + next[name] = e.target.value; + } + + this.props.onChange(next); + } +} + +PropertiesForm.propTypes = { + value: PropTypes.objectOf(PropTypes.any), + onChange: PropTypes.func, +}; + +PropertiesForm.defaultProps = { + value: {}, + onChange: () => {}, +}; + +export default PropertiesForm; diff --git a/src/settings/components/form/SearchForm.jsx b/src/settings/components/form/SearchForm.jsx deleted file mode 100644 index 6b0bd01..0000000 --- a/src/settings/components/form/SearchForm.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import './SearchForm.scss'; -import React from 'react'; -import PropTypes from 'prop-types'; -import AddButton from '../ui/AddButton'; -import DeleteButton from '../ui/DeleteButton'; - -class SearchForm extends React.Component { - - render() { - let value = this.props.value; - if (!value.engines) { - value.engines = []; - } - - return
    -
    -
    Name
    -
    URL
    -
    Default
    -
    - { - value.engines.map((engine, index) => { - return
    - - -
    - - -
    -
    ; - }) - } - -
    ; - } - - bindValue(e) { - let value = this.props.value; - let name = e.target.name; - let index = e.target.getAttribute('data-index'); - let next = { - default: value.default, - engines: value.engines ? value.engines.slice() : [], - }; - - if (name === 'name') { - next.engines[index][0] = e.target.value; - next.default = this.props.value.engines[index][0]; - } else if (name === 'url') { - next.engines[index][1] = e.target.value; - } else if (name === 'default') { - next.default = this.props.value.engines[index][0]; - } else if (name === 'add') { - next.engines.push(['', '']); - } else if (name === 'delete') { - next.engines.splice(index, 1); - } - - this.props.onChange(next); - if (name === 'delete' || name === 'default') { - this.props.onBlur(); - } - } -} - -SearchForm.propTypes = { - value: PropTypes.shape({ - default: PropTypes.string, - engines: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)), - }), - onChange: PropTypes.func, -}; - -SearchForm.defaultProps = { - value: { default: '', engines: []}, - onChange: () => {}, -}; - -export default SearchForm; diff --git a/src/settings/components/form/SearchForm.tsx b/src/settings/components/form/SearchForm.tsx new file mode 100644 index 0000000..6b0bd01 --- /dev/null +++ b/src/settings/components/form/SearchForm.tsx @@ -0,0 +1,92 @@ +import './SearchForm.scss'; +import React from 'react'; +import PropTypes from 'prop-types'; +import AddButton from '../ui/AddButton'; +import DeleteButton from '../ui/DeleteButton'; + +class SearchForm extends React.Component { + + render() { + let value = this.props.value; + if (!value.engines) { + value.engines = []; + } + + return
    +
    +
    Name
    +
    URL
    +
    Default
    +
    + { + value.engines.map((engine, index) => { + return
    + + +
    + + +
    +
    ; + }) + } + +
    ; + } + + bindValue(e) { + let value = this.props.value; + let name = e.target.name; + let index = e.target.getAttribute('data-index'); + let next = { + default: value.default, + engines: value.engines ? value.engines.slice() : [], + }; + + if (name === 'name') { + next.engines[index][0] = e.target.value; + next.default = this.props.value.engines[index][0]; + } else if (name === 'url') { + next.engines[index][1] = e.target.value; + } else if (name === 'default') { + next.default = this.props.value.engines[index][0]; + } else if (name === 'add') { + next.engines.push(['', '']); + } else if (name === 'delete') { + next.engines.splice(index, 1); + } + + this.props.onChange(next); + if (name === 'delete' || name === 'default') { + this.props.onBlur(); + } + } +} + +SearchForm.propTypes = { + value: PropTypes.shape({ + default: PropTypes.string, + engines: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)), + }), + onChange: PropTypes.func, +}; + +SearchForm.defaultProps = { + value: { default: '', engines: []}, + onChange: () => {}, +}; + +export default SearchForm; diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx deleted file mode 100644 index 4ef59d7..0000000 --- a/src/settings/components/index.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import './site.scss'; -import React from 'react'; -import { connect } from 'react-redux'; -import Input from './ui/Input'; -import SearchForm from './form/SearchForm'; -import KeymapsForm from './form/KeymapsForm'; -import BlacklistForm from './form/BlacklistForm'; -import PropertiesForm from './form/PropertiesForm'; -import * as properties from 'shared/settings/properties'; -import * as settingActions from 'settings/actions/setting'; - -const DO_YOU_WANT_TO_CONTINUE = - 'Some settings in JSON can be lost when migrating. ' + - 'Do you want to continue?'; - -class SettingsComponent extends React.Component { - componentDidMount() { - this.props.dispatch(settingActions.load()); - } - - renderFormFields(form) { - return
    -
    - Keybindings - this.bindForm('keymaps', value)} - onBlur={this.save.bind(this)} - /> -
    -
    - Search Engines - this.bindForm('search', value)} - onBlur={this.save.bind(this)} - /> -
    -
    - Blacklist - this.bindForm('blacklist', value)} - onBlur={this.save.bind(this)} - /> -
    -
    - Properties - this.bindForm('properties', value)} - onBlur={this.save.bind(this)} - /> -
    -
    ; - } - - renderJsonFields(json, error) { - return
    - -
    ; - } - - render() { - let fields = null; - let disabled = this.props.error.length > 0; - if (this.props.source === 'form') { - fields = this.renderFormFields(this.props.form); - } else if (this.props.source === 'json') { - fields = this.renderJsonFields(this.props.json, this.props.error); - } - return ( -
    -

    Configure Vim-Vixen

    -
    - - - - { fields } -
    -
    - ); - } - - bindForm(name, value) { - let settings = { - source: this.props.source, - json: this.props.json, - form: { ...this.props.form }, - }; - settings.form[name] = value; - this.props.dispatch(settingActions.set(settings)); - } - - bindJson(e) { - let settings = { - source: this.props.source, - json: e.target.value, - form: this.props.form, - }; - this.props.dispatch(settingActions.set(settings)); - } - - bindSource(e) { - let from = this.props.source; - let to = e.target.value; - - if (from === 'form' && to === 'json') { - this.props.dispatch(settingActions.switchToJson(this.props.form)); - } else if (from === 'json' && to === 'form') { - let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); - if (!b) { - this.forceUpdate(); - return; - } - this.props.dispatch(settingActions.switchToForm(this.props.json)); - } - } - - save() { - let settings = this.props.store.getState(); - this.props.dispatch(settingActions.save(settings)); - } -} - -const mapStateToProps = state => state; - -export default connect(mapStateToProps)(SettingsComponent); diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx new file mode 100644 index 0000000..4ef59d7 --- /dev/null +++ b/src/settings/components/index.tsx @@ -0,0 +1,153 @@ +import './site.scss'; +import React from 'react'; +import { connect } from 'react-redux'; +import Input from './ui/Input'; +import SearchForm from './form/SearchForm'; +import KeymapsForm from './form/KeymapsForm'; +import BlacklistForm from './form/BlacklistForm'; +import PropertiesForm from './form/PropertiesForm'; +import * as properties from 'shared/settings/properties'; +import * as settingActions from 'settings/actions/setting'; + +const DO_YOU_WANT_TO_CONTINUE = + 'Some settings in JSON can be lost when migrating. ' + + 'Do you want to continue?'; + +class SettingsComponent extends React.Component { + componentDidMount() { + this.props.dispatch(settingActions.load()); + } + + renderFormFields(form) { + return
    +
    + Keybindings + this.bindForm('keymaps', value)} + onBlur={this.save.bind(this)} + /> +
    +
    + Search Engines + this.bindForm('search', value)} + onBlur={this.save.bind(this)} + /> +
    +
    + Blacklist + this.bindForm('blacklist', value)} + onBlur={this.save.bind(this)} + /> +
    +
    + Properties + this.bindForm('properties', value)} + onBlur={this.save.bind(this)} + /> +
    +
    ; + } + + renderJsonFields(json, error) { + return
    + +
    ; + } + + render() { + let fields = null; + let disabled = this.props.error.length > 0; + if (this.props.source === 'form') { + fields = this.renderFormFields(this.props.form); + } else if (this.props.source === 'json') { + fields = this.renderJsonFields(this.props.json, this.props.error); + } + return ( +
    +

    Configure Vim-Vixen

    +
    + + + + { fields } +
    +
    + ); + } + + bindForm(name, value) { + let settings = { + source: this.props.source, + json: this.props.json, + form: { ...this.props.form }, + }; + settings.form[name] = value; + this.props.dispatch(settingActions.set(settings)); + } + + bindJson(e) { + let settings = { + source: this.props.source, + json: e.target.value, + form: this.props.form, + }; + this.props.dispatch(settingActions.set(settings)); + } + + bindSource(e) { + let from = this.props.source; + let to = e.target.value; + + if (from === 'form' && to === 'json') { + this.props.dispatch(settingActions.switchToJson(this.props.form)); + } else if (from === 'json' && to === 'form') { + let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); + if (!b) { + this.forceUpdate(); + return; + } + this.props.dispatch(settingActions.switchToForm(this.props.json)); + } + } + + save() { + let settings = this.props.store.getState(); + this.props.dispatch(settingActions.save(settings)); + } +} + +const mapStateToProps = state => state; + +export default connect(mapStateToProps)(SettingsComponent); diff --git a/src/settings/components/ui/AddButton.jsx b/src/settings/components/ui/AddButton.jsx deleted file mode 100644 index 185a03b..0000000 --- a/src/settings/components/ui/AddButton.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import './AddButton.scss'; -import React from 'react'; - -class AddButton extends React.Component { - render() { - return ; - } -} - -export default AddButton; diff --git a/src/settings/components/ui/AddButton.tsx b/src/settings/components/ui/AddButton.tsx new file mode 100644 index 0000000..185a03b --- /dev/null +++ b/src/settings/components/ui/AddButton.tsx @@ -0,0 +1,12 @@ +import './AddButton.scss'; +import React from 'react'; + +class AddButton extends React.Component { + render() { + return ; + } +} + +export default AddButton; diff --git a/src/settings/components/ui/DeleteButton.jsx b/src/settings/components/ui/DeleteButton.jsx deleted file mode 100644 index 75811cd..0000000 --- a/src/settings/components/ui/DeleteButton.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import './DeleteButton.scss'; -import React from 'react'; - -class DeleteButton extends React.Component { - render() { - return ; - } -} - -export default DeleteButton; diff --git a/src/settings/components/ui/DeleteButton.tsx b/src/settings/components/ui/DeleteButton.tsx new file mode 100644 index 0000000..75811cd --- /dev/null +++ b/src/settings/components/ui/DeleteButton.tsx @@ -0,0 +1,12 @@ +import './DeleteButton.scss'; +import React from 'react'; + +class DeleteButton extends React.Component { + render() { + return ; + } +} + +export default DeleteButton; diff --git a/src/settings/components/ui/Input.jsx b/src/settings/components/ui/Input.jsx deleted file mode 100644 index 13a246b..0000000 --- a/src/settings/components/ui/Input.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Input.scss'; - -class Input extends React.Component { - - renderText(props) { - let inputClassName = props.error ? 'input-error' : ''; - return
    - - -
    ; - } - - renderRadio(props) { - let inputClassName = props.error ? 'input-error' : ''; - return
    - -
    ; - } - - renderTextArea(props) { - let inputClassName = props.error ? 'input-error' : ''; - return
    - -