From 85aab699e9faaf4ba93573a74d58cbce64395306 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 20 Feb 2020 20:34:23 -0500 Subject: Add jsdom dependency --- package-lock.json | 672 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 674 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 505c71db..88ba43f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,18 +24,48 @@ "js-tokens": "^4.0.0" } }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "dev": true + }, "acorn": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", + "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "dev": true + } + } + }, "acorn-jsx": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true + }, "ajv": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", @@ -81,12 +111,45 @@ "sprintf-js": "~1.0.2" } }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -99,6 +162,15 @@ "integrity": "sha512-UCIPaDJrNNj5jG2ZL+nzJ7czvZV/ZYX6LaIRgfVU1k1edJOQg7dkbiSKzwHkNp6aHEHER/PhlFBrMYnlvJJQEw==", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -109,12 +181,24 @@ "concat-map": "0.0.1" } }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -162,6 +246,15 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -174,6 +267,12 @@ "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", "dev": true }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -195,6 +294,77 @@ } } }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz", + "integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "dependencies": { + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + }, + "whatwg-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.0.0.tgz", + "integrity": "sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.0", + "webidl-conversions": "^5.0.0" + } + } + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -204,12 +374,24 @@ "ms": "^2.1.1" } }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -228,6 +410,16 @@ "webidl-conversions": "^4.0.2" } }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -240,6 +432,19 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "escodegen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, "eslint": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", @@ -363,6 +568,12 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -374,6 +585,12 @@ "tmp": "^0.0.33" } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, "fake-indexeddb": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-3.0.0.tgz", @@ -437,6 +654,23 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -449,6 +683,15 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -481,12 +724,48 @@ "type-fest": "^0.8.1" } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "html-encoding-sniffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.0.tgz", + "integrity": "sha512-Y9prnPKkM7FXxQevZ5UH8Z6aVTY0ede1tHquck5UxGmKWDshxXh95gSa2xXYjS8AsGO5iOvrCI5+GttRKnLdNA==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -555,6 +834,12 @@ "through": "^2.3.6" } }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -576,18 +861,36 @@ "is-extglob": "^2.1.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -604,6 +907,89 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.0.tgz", + "integrity": "sha512-6VaW3UWyKbm9DFVIAgTfhuwnvqiqlRYNg5Rk6dINTVoZT0eKz+N86vQZr+nqt1ny1lSB1TWZJWSEWQAfu8oTpA==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.0", + "acorn-globals": "^4.3.4", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.13.0", + "html-encoding-sniffer": "^2.0.0", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.0", + "request-promise-native": "^1.0.8", + "saxes": "^4.0.2", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^5.0.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.1", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + }, + "whatwg-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.0.0.tgz", + "integrity": "sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.0", + "webidl-conversions": "^5.0.0" + } + } + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -616,6 +1002,24 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -638,6 +1042,21 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dev": true, + "requires": { + "mime-db": "1.43.0" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -692,6 +1111,18 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -739,6 +1170,12 @@ "callsites": "^3.0.0" } }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -751,6 +1188,12 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -763,12 +1206,24 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, "realistic-structured-clone": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz", @@ -787,6 +1242,78 @@ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "dev": true, + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -830,12 +1357,27 @@ "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "saxes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-4.0.2.tgz", + "integrity": "sha512-EZOTeQ4bgkOaGCDaTKux+LaRNcLNbdbvMH7R3/yjEEULPEmqvkFbFub6DJhJTub2iGMT93CfpZ5LTdKZmAbVeQ==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -888,12 +1430,42 @@ } } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -948,6 +1520,12 @@ "has-flag": "^3.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -1006,6 +1584,17 @@ "os-tmpdir": "~1.0.2" } }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -1021,6 +1610,21 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -1062,18 +1666,68 @@ "punycode": "^2.1.0" } }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, "whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", @@ -1114,6 +1768,24 @@ "requires": { "mkdirp": "^0.5.1" } + }, + "ws": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz", + "integrity": "sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true } } } diff --git a/package.json b/package.json index 2bbc6a79..a6994eb3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "devDependencies": { "eslint": "^6.8.0", "eslint-plugin-no-unsanitized": "^3.0.2", - "fake-indexeddb": "^3.0.0" + "fake-indexeddb": "^3.0.0", + "jsdom": "^16.2.0" } } -- cgit v1.2.3 From 934c3239f25f9063882c82fdaf3cfa88706f908b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 20 Feb 2020 21:18:31 -0500 Subject: Add some basic document tests --- test/data/html/test-document1.html | 106 ++++++++++++++++++++++ test/data/html/test-stylesheet.css | 32 +++++++ test/test-document.js | 180 +++++++++++++++++++++++++++++++++++++ test/test-stylesheet.css | 32 +++++++ 4 files changed, 350 insertions(+) create mode 100644 test/data/html/test-document1.html create mode 100644 test/data/html/test-stylesheet.css create mode 100644 test/test-document.js create mode 100644 test/test-stylesheet.css diff --git a/test/data/html/test-document1.html b/test/data/html/test-document1.html new file mode 100644 index 00000000..00cc8524 --- /dev/null +++ b/test/data/html/test-document1.html @@ -0,0 +1,106 @@ + + + + + + Yomichan Tests + + + + + +

Yomichan Tests

+ +
+ 真白「心配してくださって、ありがとございます」 +
+ +
+ 真白「心配してくださって、ありがとございます」 +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ よみちゃん +
+ + + \ No newline at end of file diff --git a/test/data/html/test-stylesheet.css b/test/data/html/test-stylesheet.css new file mode 100644 index 00000000..ab25732e --- /dev/null +++ b/test/data/html/test-stylesheet.css @@ -0,0 +1,32 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + max-width: 680px; + padding: 0 1em; + box-sizing: border-box; + margin: 0 auto; + background-color: #f8f8f8; + counter-reset: test-id; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.test { + background-color: #ffffff; + margin: 1em 0; + padding: 0.5em; + box-shadow: rgba(64, 64, 64, 0.3) 0px 1px 2px 0px, rgba(64, 64, 64, 0.15) 0px 1px 3px 1px; + border-radius: 4px; +} + +.test:before { + content: "Test " counter(test-id); + display: block; + counter-increment: test-id; + margin-bottom: 0.5em; + border-bottom: 1px solid #d8d8d8; + font-weight: bold; +} diff --git a/test/test-document.js b/test/test-document.js new file mode 100644 index 00000000..0c3c3d8e --- /dev/null +++ b/test/test-document.js @@ -0,0 +1,180 @@ +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {JSDOM} = require('jsdom'); +const yomichanTest = require('./yomichan-test'); + + +// DOMRect class definition +class DOMRect { + constructor(x, y, width, height) { + this._x = x; + this._y = y; + this._width = width; + this._height = height; + } + + get x() { return this._x; } + get y() { return this._y; } + get width() { return this._width; } + get height() { return this._height; } + get left() { return this._x + Math.min(0, this._width); } + get right() { return this._x + Math.max(0, this._width); } + get top() { return this._y + Math.min(0, this._height); } + get bottom() { return this._y + Math.max(0, this._height); } +} + + +function createJSDOM(fileName) { + const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); + const dom = new JSDOM(domSource); + const document = dom.window.document; + const window = dom.window; + + // Define innerText setter as an alias for textContent setter + Object.defineProperty(window.HTMLDivElement.prototype, 'innerText', { + set(value) { this.textContent = value; } + }); + + // Placeholder for feature detection + document.caretRangeFromPoint = () => null; + + return dom; +} + +function querySelectorChildOrSelf(element, selector) { + return selector ? element.querySelector(selector) : element; +} + +function getChildTextNodeOrSelf(dom, node) { + if (node === null) { return null; } + const Node = dom.window.Node; + const childNode = node.firstChild; + return (childNode !== null && childNode.nodeType === Node.TEXT_NODE ? childNode : node); +} + +function getPrototypeOfOrNull(value) { + try { + return Object.getPrototypeOf(value); + } catch (e) { + return null; + } +} + +function findImposterElement(document) { + // Finds the imposter element based on it's z-index style + return document.querySelector('div[style*="2147483646"]>*'); +} + + +async function testDocument1() { + const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-document1.html')); + const window = dom.window; + const document = window.document; + const Node = window.Node; + const Range = window.Range; + + const {DOM} = yomichanTest.requireScript( + 'ext/mixed/js/dom.js', + ['DOM'] + ); + const {TextSourceRange, TextSourceElement} = yomichanTest.requireScript( + 'ext/fg/js/source.js', + ['TextSourceRange', 'TextSourceElement'], + {document, window, Range, Node} + ); + const {docRangeFromPoint, docSentenceExtract} = yomichanTest.requireScript( + 'ext/fg/js/document.js', + ['docRangeFromPoint', 'docSentenceExtract'], + {document, window, Node, TextSourceElement, TextSourceRange, DOM} + ); + + try { + await testDocument1Inner(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}); + } finally { + window.close(); + } +} + +async function testDocument1Inner(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}) { + const document = dom.window.document; + + for (const testElement of document.querySelectorAll('.test')) { + // Get test parameters + let { + elementFromPointSelector, + caretRangeFromPointSelector, + startNodeSelector, + startOffset, + endNodeSelector, + endOffset, + resultType, + sentenceExtent, + sentence, + hasImposter + } = testElement.dataset; + + const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); + const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); + const startNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, startNodeSelector)); + const endNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, endNodeSelector)); + + startOffset = parseInt(startOffset, 10); + endOffset = parseInt(endOffset, 10); + sentenceExtent = parseInt(sentenceExtent, 10); + + assert.notStrictEqual(elementFromPointValue, null); + assert.notStrictEqual(caretRangeFromPointValue, null); + assert.notStrictEqual(startNode, null); + assert.notStrictEqual(endNode, null); + + // Setup functions + document.elementFromPoint = () => elementFromPointValue; + + document.caretRangeFromPoint = (x, y) => { + const imposter = getChildTextNodeOrSelf(dom, findImposterElement(document)); + assert.strictEqual(!!imposter, hasImposter === 'true'); + + const range = document.createRange(); + range.setStart(imposter ? imposter : startNode, startOffset); + range.setEnd(imposter ? imposter : startNode, endOffset); + + // Override getClientRects to return a rect guaranteed to contain (x, y) + range.getClientRects = () => [new DOMRect(x - 1, y - 1, 2, 2)]; + return range; + }; + + // Test docRangeFromPoint + const source = docRangeFromPoint(0, 0, false); + switch (resultType) { + case 'TextSourceRange': + assert.strictEqual(getPrototypeOfOrNull(source), TextSourceRange.prototype); + break; + case 'TextSourceElement': + assert.strictEqual(getPrototypeOfOrNull(source), TextSourceElement.prototype); + break; + case 'null': + assert.strictEqual(source, null); + break; + default: + assert.ok(false); + break; + } + if (source === null) { continue; } + + // Test docSentenceExtract + const sentenceActual = docSentenceExtract(source, sentenceExtent).text; + assert.strictEqual(sentenceActual, sentence); + + // Clean + source.cleanup(); + } +} + + +async function main() { + await testDocument1(); +} + + +if (require.main === module) { main(); } diff --git a/test/test-stylesheet.css b/test/test-stylesheet.css new file mode 100644 index 00000000..ab25732e --- /dev/null +++ b/test/test-stylesheet.css @@ -0,0 +1,32 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + max-width: 680px; + padding: 0 1em; + box-sizing: border-box; + margin: 0 auto; + background-color: #f8f8f8; + counter-reset: test-id; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.test { + background-color: #ffffff; + margin: 1em 0; + padding: 0.5em; + box-shadow: rgba(64, 64, 64, 0.3) 0px 1px 2px 0px, rgba(64, 64, 64, 0.15) 0px 1px 3px 1px; + border-radius: 4px; +} + +.test:before { + content: "Test " counter(test-id); + display: block; + counter-increment: test-id; + margin-bottom: 0.5em; + border-bottom: 1px solid #d8d8d8; + font-weight: bold; +} -- cgit v1.2.3 From c1d6e5dbf177cdf41e15ff65fdf89a847f198ea1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 22 Feb 2020 15:54:21 -0500 Subject: Add command to test-code --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a6994eb3..04bfd4af 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "test": "npm run test-lint && npm run test-code", "test-lint": "eslint .", - "test-code": "node ./test/test-schema.js && node ./test/test-dictionary.js && node ./test/test-database.js" + "test-code": "node ./test/test-schema.js && node ./test/test-dictionary.js && node ./test/test-database.js && node ./test/test-document.js" }, "repository": { "type": "git", -- cgit v1.2.3 From bdb86e1713a196c9539306166d8dc4238adefc0d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 22 Feb 2020 16:32:38 -0500 Subject: Test seeking functions --- test/data/html/test-document1.html | 158 +++++++++++++++++++++++++++++++++++++ test/test-document.js | 50 +++++++++++- 2 files changed, 205 insertions(+), 3 deletions(-) diff --git a/test/data/html/test-document1.html b/test/data/html/test-document1.html index 00cc8524..0754a314 100644 --- a/test/data/html/test-document1.html +++ b/test/data/html/test-document1.html @@ -13,6 +13,7 @@
+
+ + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + trailing content +
+ +
+ + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + trailing content +
+ +
+ + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + trailing content +
+ +
+ + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + trailing content +
+ \ No newline at end of file diff --git a/test/test-document.js b/test/test-document.js index 0c3c3d8e..edf6bbea 100644 --- a/test/test-document.js +++ b/test/test-document.js @@ -90,16 +90,17 @@ async function testDocument1() { ); try { - await testDocument1Inner(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}); + await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}); + await testTextSourceRangeSeekFunctions(dom, {TextSourceRange}); } finally { window.close(); } } -async function testDocument1Inner(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}) { +async function testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}) { const document = dom.window.document; - for (const testElement of document.querySelectorAll('.test')) { + for (const testElement of document.querySelectorAll('.test[data-test-type=scan]')) { // Get test parameters let { elementFromPointSelector, @@ -171,6 +172,49 @@ async function testDocument1Inner(dom, {docRangeFromPoint, docSentenceExtract, T } } +async function testTextSourceRangeSeekFunctions(dom, {TextSourceRange}) { + const document = dom.window.document; + + for (const testElement of document.querySelectorAll('.test[data-test-type=text-source-range-seek]')) { + // Get test parameters + let { + seekNodeSelector, + seekNodeIsText, + seekOffset, + seekLength, + seekDirection, + expectedResultNodeSelector, + expectedResultNodeIsText, + expectedResultOffset, + expectedResultContent + } = testElement.dataset; + + seekOffset = parseInt(seekOffset, 10); + seekLength = parseInt(seekLength, 10); + expectedResultOffset = parseInt(expectedResultOffset, 10); + + let seekNode = testElement.querySelector(seekNodeSelector); + if (seekNodeIsText === 'true') { + seekNode = seekNode.firstChild; + } + + let expectedResultNode = testElement.querySelector(expectedResultNodeSelector); + if (expectedResultNodeIsText === 'true') { + expectedResultNode = expectedResultNode.firstChild; + } + + const {node, offset, content} = ( + seekDirection === 'forward' ? + TextSourceRange.seekForward(seekNode, seekOffset, seekLength) : + TextSourceRange.seekBackward(seekNode, seekOffset, seekLength) + ); + + assert.strictEqual(node, expectedResultNode); + assert.strictEqual(offset, expectedResultOffset); + assert.strictEqual(content, expectedResultContent); + } +} + async function main() { await testDocument1(); -- cgit v1.2.3 From a0d5d9a8219e52a826d5d8616dacbe1f2aee7a65 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 26 Feb 2020 01:54:40 +0200 Subject: fix opening options in new tab --- ext/bg/js/backend.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index e3bf7bda..3051a873 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -693,9 +693,10 @@ class Backend { } _onCommandOptions(params) { - if (!(params && params.newTab)) { + const {mode='existingOrNewTab'} = params || {}; + if (mode === 'existingOrNewTab') { chrome.runtime.openOptionsPage(); - } else { + } else if (mode === 'newTab') { const manifest = chrome.runtime.getManifest(); const url = chrome.runtime.getURL(manifest.options_ui.page); chrome.tabs.create({url}); -- cgit v1.2.3 From 13a94d06eaf850582577e6fb2c130ae03061c990 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 19:38:52 -0500 Subject: Update styles --- ext/mixed/css/display.css | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 6a5383bc..5a0775f0 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -30,8 +30,8 @@ * General */ -html:root[data-yomichan-page=float]:not([data-yomichan-theme]), -html:root[data-yomichan-page=float]:not([data-yomichan-theme]) body { +:root[data-yomichan-page=float]:not([data-yomichan-theme]), +:root[data-yomichan-page=float]:not([data-yomichan-theme]) body { background-color: transparent; } @@ -65,10 +65,6 @@ ol, ul { height: 2.28571428em; /* 14px => 32px */ } -.invisible { - visibility: hidden; -} - /* * Navigation */ @@ -84,15 +80,15 @@ ol, ul { border-bottom-style: solid; } -html:root[data-yomichan-page=search] .navigation-header { +:root[data-yomichan-page=search] .navigation-header { position: sticky; } -html:root[data-yomichan-page=float] .navigation-header { +:root[data-yomichan-page=float] .navigation-header { position: fixed; } -html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation-header-spacer { +:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation-header-spacer { height: 2.1em; } @@ -136,7 +132,7 @@ html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation margin-right: 0.2em; } -html:root[data-yomichan-page=search][data-search-mode=popup] .search-input { +:root[data-yomichan-page=search][data-search-mode=popup] .search-input { display: none; } @@ -150,7 +146,7 @@ html:root[data-yomichan-page=search][data-search-mode=popup] .search-input { padding-bottom: 0.72em; } -html:root[data-yomichan-page=float] .entry { +:root[data-yomichan-page=float] .entry { padding-left: 0.72em; padding-right: 0.72em; } @@ -231,7 +227,7 @@ button.action-button { margin-right: 0.375em; } -html:root:not([data-enable-search-tags=true]) .tag[data-category=search] { +:root:not([data-enable-search-tags=true]) .tag[data-category=search] { display: none; } -- cgit v1.2.3 From 3795af6cb5230fd1b185566eaa354fe4a04c4ec6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 19:54:59 -0500 Subject: Use Object.entries rather than for in --- ext/bg/js/deinflector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js index e2ced965..d548d271 100644 --- a/ext/bg/js/deinflector.js +++ b/ext/bg/js/deinflector.js @@ -57,9 +57,9 @@ class Deinflector { static normalizeReasons(reasons) { const normalizedReasons = []; - for (const reason in reasons) { + for (const [reason, reasonInfo] of Object.entries(reasons)) { const variants = []; - for (const {kanaIn, kanaOut, rulesIn, rulesOut} of reasons[reason]) { + for (const {kanaIn, kanaOut, rulesIn, rulesOut} of reasonInfo) { variants.push([ kanaIn, kanaOut, -- cgit v1.2.3 From 2e0b3a6ccc50eae3b86e8cc82c623d95a9defdaf Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 20:38:18 -0500 Subject: Refactor dictNoteFormat --- ext/bg/js/dictionary.js | 57 ++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index f5c5b21b..7b112381 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -388,40 +388,39 @@ dictFieldFormat.markers = new Set([ ]); async function dictNoteFormat(definition, mode, options, templates) { - const note = {fields: {}, tags: options.anki.tags}; - let fields = []; - - if (mode === 'kanji') { - fields = options.anki.kanji.fields; - note.deckName = options.anki.kanji.deck; - note.modelName = options.anki.kanji.model; - } else { - fields = options.anki.terms.fields; - note.deckName = options.anki.terms.deck; - note.modelName = options.anki.terms.model; - - if (definition.audio) { - const audio = { - url: definition.audio.url, - filename: definition.audio.filename, - skipHash: '7e2c2f954ef6051373ba916f000168dc', - fields: [] - }; + const isKanji = (mode === 'kanji'); + const tags = options.anki.tags; + const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; + const modeOptionsFieldEntries = Object.entries(modeOptions.fields); + + const note = { + fields: {}, + tags, + deckName: modeOptions.deck, + modelName: modeOptions.model + }; - for (const name in fields) { - if (fields[name].includes('{audio}')) { - audio.fields.push(name); - } - } + for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { + note.fields[fieldName] = await dictFieldFormat(fieldValue, definition, mode, options, templates); + } - if (audio.fields.length > 0) { - note.audio = audio; + if (!isKanji && definition.audio) { + const audioFields = []; + + for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { + if (fieldValue.includes('{audio}')) { + audioFields.push(fieldName); } } - } - for (const name in fields) { - note.fields[name] = await dictFieldFormat(fields[name], definition, mode, options, templates); + if (audioFields.length > 0) { + note.audio = { + url: definition.audio.url, + filename: definition.audio.filename, + skipHash: '7e2c2f954ef6051373ba916f000168dc', + fields: audioFields + }; + } } return note; -- cgit v1.2.3 From 798517cdf1e346e43e66b2afb43cdf4bda1106e9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 21:22:54 -0500 Subject: Use for of --- ext/mixed/js/display.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 5d3076ee..12829650 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -520,15 +520,13 @@ class Display { updateAdderButtons(states) { for (let i = 0; i < states.length; ++i) { - const state = states[i]; let noteId = null; - for (const mode in state) { + for (const [mode, info] of Object.entries(states[i])) { const button = this.adderButtonFind(i, mode); if (button === null) { continue; } - const info = state[mode]; if (!info.canAdd && noteId === null && info.noteId) { noteId = info.noteId; } -- cgit v1.2.3 From 6bd714fec0437413d0731e0d346f52f8abe55cd2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 21:23:11 -0500 Subject: Use Map to avoid using for in --- ext/bg/js/settings/conditions-ui.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/bg/js/settings/conditions-ui.js b/ext/bg/js/settings/conditions-ui.js index 5a271321..63e01861 100644 --- a/ext/bg/js/settings/conditions-ui.js +++ b/ext/bg/js/settings/conditions-ui.js @@ -235,10 +235,10 @@ ConditionsUI.Condition = class Condition { updateInput() { const conditionDescriptors = this.parent.parent.conditionDescriptors; const {type, operator} = this.condition; - const props = { - placeholder: '', - type: 'text' - }; + const props = new Map([ + ['placeholder', ''], + ['type', 'text'] + ]); const objects = []; if (hasOwn(conditionDescriptors, type)) { @@ -252,20 +252,20 @@ ConditionsUI.Condition = class Condition { for (const object of objects) { if (hasOwn(object, 'placeholder')) { - props.placeholder = object.placeholder; + props.set('placeholder', object.placeholder); } if (object.type === 'number') { - props.type = 'number'; + props.set('type', 'number'); for (const prop of ['step', 'min', 'max']) { if (hasOwn(object, prop)) { - props[prop] = object[prop]; + props.set(prop, object[prop]); } } } } - for (const prop in props) { - this.input.prop(prop, props[prop]); + for (const [prop, value] of props.entries()) { + this.input.prop(prop, value); } const {valid} = this.validateValue(this.condition.value); -- cgit v1.2.3 From 7b97138ad1242ab51206f5d35247da3a9ccd905d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 21:26:56 -0500 Subject: Changed type returned by apiTextParseMecab to avoid using for in --- ext/bg/js/backend.js | 8 ++++---- ext/bg/js/search-query-parser.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 3051a873..717ffac3 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -357,11 +357,11 @@ class Backend { async _onApiTextParseMecab({text, optionsContext}) { const options = await this.getOptions(optionsContext); - const results = {}; + const results = []; const rawResults = await this.mecab.parseText(text); - for (const mecabName in rawResults) { + for (const [mecabName, parsedLines] of Object.entries(rawResults)) { const result = []; - for (const parsedLine of rawResults[mecabName]) { + for (const parsedLine of parsedLines) { for (const {expression, reading, source} of parsedLine) { const term = []; if (expression !== null && reading !== null) { @@ -381,7 +381,7 @@ class Backend { } result.push([{text: '\n'}]); } - results[mecabName] = result; + results.push([mecabName, result]); } return results; } diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 0d4aaa50..11c7baa2 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -142,11 +142,11 @@ class QueryParser extends TextScanner { } if (this.search.options.parsing.enableMecabParser) { const mecabResults = await apiTextParseMecab(text, this.search.getOptionsContext()); - for (const mecabDictName in mecabResults) { + for (const [mecabDictName, mecabDictResults] of mecabResults) { results.push({ name: `MeCab: ${mecabDictName}`, id: `mecab-${mecabDictName}`, - parsedText: mecabResults[mecabDictName] + parsedText: mecabDictResults }); } } -- cgit v1.2.3 From b391704f3db77f19aad7e1a4e61b515a18475024 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 21:58:12 -0500 Subject: Use for of --- ext/bg/js/audio.js | 4 ++-- ext/bg/js/backend.js | 4 ++-- ext/mixed/js/display.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index d300570b..972e2b8b 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -156,8 +156,8 @@ function audioBuildFilename(definition) { async function audioInject(definition, fields, sources, optionsContext) { let usesAudio = false; - for (const name in fields) { - if (fields[name].includes('{audio}')) { + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{audio}')) { usesAudio = true; break; } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 717ffac3..6736b1ae 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -719,8 +719,8 @@ class Backend { async _injectScreenshot(definition, fields, screenshot) { let usesScreenshot = false; - for (const name in fields) { - if (fields[name].includes('{screenshot}')) { + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{screenshot}')) { usesScreenshot = true; break; } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 12829650..b0bcff7c 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -705,8 +705,8 @@ class Display { noteUsesScreenshot() { const fields = this.options.anki.terms.fields; - for (const name in fields) { - if (fields[name].includes('{screenshot}')) { + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{screenshot}')) { return true; } } -- cgit v1.2.3 From 0b5a26e64a3fecf4ab64e5a1b6a1b068cde695fe Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 22:10:49 -0500 Subject: Update dictEnabledSet and dictConfigured to use for of --- ext/bg/js/dictionary.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 7b112381..ffeac80a 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -20,22 +20,16 @@ function dictEnabledSet(options) { const enabledDictionaryMap = new Map(); - const optionsDictionaries = options.dictionaries; - for (const title in optionsDictionaries) { - if (!hasOwn(optionsDictionaries, title)) { continue; } - const dictionary = optionsDictionaries[title]; - if (!dictionary.enabled) { continue; } - enabledDictionaryMap.set(title, { - priority: dictionary.priority || 0, - allowSecondarySearches: !!dictionary.allowSecondarySearches - }); + for (const [title, {enabled, priority, allowSecondarySearches}] of Object.entries(options.dictionaries)) { + if (!enabled) { continue; } + enabledDictionaryMap.set(title, {priority, allowSecondarySearches}); } return enabledDictionaryMap; } function dictConfigured(options) { - for (const title in options.dictionaries) { - if (options.dictionaries[title].enabled) { + for (const {enabled} of Object.values(options.dictionaries)) { + if (enabled) { return true; } } -- cgit v1.2.3 From 32cb1debb52624eb458b1fa19fc10325b386605a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 22:11:33 -0500 Subject: Add "guard-for-in": "error" --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index fcc6995b..5be4d245 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,6 +22,7 @@ "dot-notation": "error", "eqeqeq": "error", "func-names": ["error", "always"], + "guard-for-in": "error", "no-case-declarations": "error", "no-const-assign": "error", "no-constant-condition": "off", -- cgit v1.2.3 From fa385aafa4cf1b0101b6bc0a08f38b8508666158 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 22:24:22 -0500 Subject: Fix noteUsesScreenshot not checking mode --- ext/mixed/js/display.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b0bcff7c..8a68b43f 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -633,7 +633,7 @@ class Display { this.setSpinnerVisible(true); const context = {}; - if (this.noteUsesScreenshot()) { + if (this.noteUsesScreenshot(mode)) { const screenshot = await this.getScreenshot(); if (screenshot) { context.screenshot = screenshot; @@ -703,8 +703,9 @@ class Display { } } - noteUsesScreenshot() { - const fields = this.options.anki.terms.fields; + noteUsesScreenshot(mode) { + const optionsAnki = this.options.anki; + const fields = (mode === 'kanji' ? optionsAnki.kanji : optionsAnki.terms).fields; for (const fieldValue of Object.values(fields)) { if (fieldValue.includes('{screenshot}')) { return true; -- cgit v1.2.3 From 39f9bb1050d707ba1edd3f4956eb69b577a91ddd Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 22:30:00 -0500 Subject: Remove hasOwn which is no longer necessary --- test/test-database.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test-database.js b/test/test-database.js index c2317881..35f22523 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -90,7 +90,7 @@ class XMLHttpRequest { const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); const {dictFieldSplit, dictTagSanitize} = yomichanTest.requireScript('ext/bg/js/dictionary.js', ['dictFieldSplit', 'dictTagSanitize']); -const {stringReverse, hasOwn} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse', 'hasOwn'], {chrome}); +const {stringReverse} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse'], {chrome}); const {requestJson} = yomichanTest.requireScript('ext/bg/js/request.js', ['requestJson'], {XMLHttpRequest}); const databaseGlobals = { @@ -98,7 +98,6 @@ const databaseGlobals = { JsonSchema, requestJson, stringReverse, - hasOwn, dictFieldSplit, dictTagSanitize, indexedDB: global.indexedDB, -- cgit v1.2.3 From 81fbe60da9c4cd7de27ef68c39805b96680bc24d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 25 Feb 2020 22:30:29 -0500 Subject: Define core globals only for extension scripts --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5be4d245..4ee1f982 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -74,7 +74,7 @@ }, "overrides": [ { - "files": ["*.js"], + "files": ["ext/**/*.js"], "excludedFiles": ["ext/mixed/js/core.js"], "globals": { "yomichan": "readonly", -- cgit v1.2.3 From 0c1b250b4f72a0de6348b119a050d19c6e767113 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:04:21 -0500 Subject: Move event handler function bodies --- ext/bg/js/search.js | 64 ++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 98e167ad..59cb8636 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -69,18 +69,7 @@ class DisplaySearch extends Display { } else { this.wanakanaEnable.checked = false; } - this.wanakanaEnable.addEventListener('change', (e) => { - const {queryParams: {query: query2=''}} = parseUrl(window.location.href); - if (e.target.checked) { - window.wanakana.bind(this.query); - apiOptionsSet({general: {enableWanakana: true}}, this.getOptionsContext()); - } else { - window.wanakana.unbind(this.query); - apiOptionsSet({general: {enableWanakana: false}}, this.getOptionsContext()); - } - this.setQuery(query2); - this.onSearchQueryUpdated(this.query.value, false); - }); + this.wanakanaEnable.addEventListener('change', (e) => this.onWanakanaEnableChange(e)); } this.setQuery(query); @@ -93,24 +82,7 @@ class DisplaySearch extends Display { } else { this.clipboardMonitorEnable.checked = false; } - this.clipboardMonitorEnable.addEventListener('change', (e) => { - if (e.target.checked) { - chrome.permissions.request( - {permissions: ['clipboardRead']}, - (granted) => { - if (granted) { - this.clipboardMonitor.start(); - apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext()); - } else { - e.target.checked = false; - } - } - ); - } else { - this.clipboardMonitor.stop(); - apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext()); - } - }); + this.clipboardMonitorEnable.addEventListener('change', (e) => this.onClipboardMonitorEnableChange(e)); } chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); @@ -253,6 +225,38 @@ class DisplaySearch extends Display { } } + onWanakanaEnableChange(e) { + const {queryParams: {query=''}} = parseUrl(window.location.href); + const enableWanakana = e.target.checked; + if (enableWanakana) { + window.wanakana.bind(this.query); + } else { + window.wanakana.unbind(this.query); + } + this.setQuery(query); + this.onSearchQueryUpdated(this.query.value, false); + apiOptionsSet({general: {enableWanakana}}, this.getOptionsContext()); + } + + onClipboardMonitorEnableChange(e) { + if (e.target.checked) { + chrome.permissions.request( + {permissions: ['clipboardRead']}, + (granted) => { + if (granted) { + this.clipboardMonitor.start(); + apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext()); + } else { + e.target.checked = false; + } + } + ); + } else { + this.clipboardMonitor.stop(); + apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext()); + } + } + async updateOptions(options) { await super.updateOptions(options); this.queryParser.setOptions(this.options); -- cgit v1.2.3 From 007789ffa07149daeeb857ae3587aa4a0da85c40 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:08:35 -0500 Subject: Organize, remove unnecessary null checks --- ext/bg/js/search.js | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 59cb8636..209a5feb 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -55,27 +55,19 @@ class DisplaySearch extends Display { const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); - if (this.search !== null) { - this.search.addEventListener('click', (e) => this.onSearch(e), false); - } - if (this.query !== null) { - document.documentElement.dataset.searchMode = mode; - this.query.addEventListener('input', () => this.onSearchInput(), false); - - if (this.wanakanaEnable !== null) { - if (this.options.general.enableWanakana === true) { - this.wanakanaEnable.checked = true; - window.wanakana.bind(this.query); - } else { - this.wanakanaEnable.checked = false; - } - this.wanakanaEnable.addEventListener('change', (e) => this.onWanakanaEnableChange(e)); - } + document.documentElement.dataset.searchMode = mode; - this.setQuery(query); - this.onSearchQueryUpdated(this.query.value, false); + if (this.options.general.enableWanakana === true) { + this.wanakanaEnable.checked = true; + window.wanakana.bind(this.query); + } else { + this.wanakanaEnable.checked = false; } - if (this.clipboardMonitorEnable !== null && mode !== 'popup') { + + this.setQuery(query); + this.onSearchQueryUpdated(this.query.value, false); + + if (mode !== 'popup') { if (this.options.general.enableClipboardMonitor === true) { this.clipboardMonitorEnable.checked = true; this.clipboardMonitor.start(); @@ -87,6 +79,9 @@ class DisplaySearch extends Display { chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); + this.search.addEventListener('click', (e) => this.onSearch(e), false); + this.query.addEventListener('input', () => this.onSearchInput(), false); + this.wanakanaEnable.addEventListener('change', (e) => this.onWanakanaEnableChange(e)); window.addEventListener('popstate', (e) => this.onPopState(e)); window.addEventListener('copy', (e) => this.onCopy(e)); -- cgit v1.2.3 From 8d5d03451654d54724732c9300d54ab5cfee1e41 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:22:32 -0500 Subject: Move event handler definitions --- ext/bg/js/backend.js | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6736b1ae..25537e55 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -48,6 +48,41 @@ class Backend { this.apiForwarder = new BackendApiForwarder(); this.messageToken = yomichan.generateId(16); + + this._messageHandlers = new Map([ + ['optionsSchemaGet', this._onApiOptionsSchemaGet.bind(this)], + ['optionsGet', this._onApiOptionsGet.bind(this)], + ['optionsGetFull', this._onApiOptionsGetFull.bind(this)], + ['optionsSet', this._onApiOptionsSet.bind(this)], + ['optionsSave', this._onApiOptionsSave.bind(this)], + ['kanjiFind', this._onApiKanjiFind.bind(this)], + ['termsFind', this._onApiTermsFind.bind(this)], + ['textParse', this._onApiTextParse.bind(this)], + ['textParseMecab', this._onApiTextParseMecab.bind(this)], + ['definitionAdd', this._onApiDefinitionAdd.bind(this)], + ['definitionsAddable', this._onApiDefinitionsAddable.bind(this)], + ['noteView', this._onApiNoteView.bind(this)], + ['templateRender', this._onApiTemplateRender.bind(this)], + ['commandExec', this._onApiCommandExec.bind(this)], + ['audioGetUrl', this._onApiAudioGetUrl.bind(this)], + ['screenshotGet', this._onApiScreenshotGet.bind(this)], + ['forward', this._onApiForward.bind(this)], + ['frameInformationGet', this._onApiFrameInformationGet.bind(this)], + ['injectStylesheet', this._onApiInjectStylesheet.bind(this)], + ['getEnvironmentInfo', this._onApiGetEnvironmentInfo.bind(this)], + ['clipboardGet', this._onApiClipboardGet.bind(this)], + ['getDisplayTemplatesHtml', this._onApiGetDisplayTemplatesHtml.bind(this)], + ['getQueryParserTemplatesHtml', this._onApiGetQueryParserTemplatesHtml.bind(this)], + ['getZoom', this._onApiGetZoom.bind(this)], + ['getMessageToken', this._onApiGetMessageToken.bind(this)] + ]); + + this._commandHandlers = new Map([ + ['search', this._onCommandSearch.bind(this)], + ['help', this._onCommandHelp.bind(this)], + ['options', this._onCommandOptions.bind(this)], + ['toggle', this._onCommandToggle.bind(this)] + ]); } async prepare() { @@ -96,11 +131,11 @@ class Backend { } onMessage({action, params}, sender, callback) { - const handler = Backend._messageHandlers.get(action); + const handler = this._messageHandlers.get(action); if (typeof handler !== 'function') { return false; } try { - const promise = handler(this, params, sender); + const promise = handler(params, sender); promise.then( (result) => callback({result}), (error) => callback({error: errorToJson(error)}) @@ -243,10 +278,10 @@ class Backend { } _runCommand(command, params) { - const handler = Backend._commandHandlers.get(command); + const handler = this._commandHandlers.get(command); if (typeof handler !== 'function') { return false; } - handler(this, params); + handler(params); return true; } @@ -868,40 +903,5 @@ class Backend { } } -Backend._messageHandlers = new Map([ - ['optionsSchemaGet', (self, ...args) => self._onApiOptionsSchemaGet(...args)], - ['optionsGet', (self, ...args) => self._onApiOptionsGet(...args)], - ['optionsGetFull', (self, ...args) => self._onApiOptionsGetFull(...args)], - ['optionsSet', (self, ...args) => self._onApiOptionsSet(...args)], - ['optionsSave', (self, ...args) => self._onApiOptionsSave(...args)], - ['kanjiFind', (self, ...args) => self._onApiKanjiFind(...args)], - ['termsFind', (self, ...args) => self._onApiTermsFind(...args)], - ['textParse', (self, ...args) => self._onApiTextParse(...args)], - ['textParseMecab', (self, ...args) => self._onApiTextParseMecab(...args)], - ['definitionAdd', (self, ...args) => self._onApiDefinitionAdd(...args)], - ['definitionsAddable', (self, ...args) => self._onApiDefinitionsAddable(...args)], - ['noteView', (self, ...args) => self._onApiNoteView(...args)], - ['templateRender', (self, ...args) => self._onApiTemplateRender(...args)], - ['commandExec', (self, ...args) => self._onApiCommandExec(...args)], - ['audioGetUrl', (self, ...args) => self._onApiAudioGetUrl(...args)], - ['screenshotGet', (self, ...args) => self._onApiScreenshotGet(...args)], - ['forward', (self, ...args) => self._onApiForward(...args)], - ['frameInformationGet', (self, ...args) => self._onApiFrameInformationGet(...args)], - ['injectStylesheet', (self, ...args) => self._onApiInjectStylesheet(...args)], - ['getEnvironmentInfo', (self, ...args) => self._onApiGetEnvironmentInfo(...args)], - ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)], - ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)], - ['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)], - ['getZoom', (self, ...args) => self._onApiGetZoom(...args)], - ['getMessageToken', (self, ...args) => self._onApiGetMessageToken(...args)] -]); - -Backend._commandHandlers = new Map([ - ['search', (self, ...args) => self._onCommandSearch(...args)], - ['help', (self, ...args) => self._onCommandHelp(...args)], - ['options', (self, ...args) => self._onCommandOptions(...args)], - ['toggle', (self, ...args) => self._onCommandToggle(...args)] -]); - window.yomichanBackend = new Backend(); window.yomichanBackend.prepare(); -- cgit v1.2.3 From 13874e962bbc1964e02517aa098961e14ddc954a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:34:25 -0500 Subject: Refactor DisplaySearch.onKeyDownIgnoreKeys --- ext/bg/js/search.js | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 209a5feb..2dd4e758 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -39,6 +39,21 @@ class DisplaySearch extends Display { this.introAnimationTimer = null; this.clipboardMonitor = new ClipboardMonitor(); + + this._onKeyDownIgnoreKeys = new Map([ + ['ANY_MOD', new Set([ + 'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End', + 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', + 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', + 'F21', 'F22', 'F23', 'F24' + ])], + ['Control', new Set(['C', 'A', 'Z', 'Y', 'X', 'F', 'G'])], + ['Meta', new Set(['C', 'A', 'Z', 'Y', 'X', 'F', 'G'])], + ['OS', []], + ['Alt', []], + ['AltGraph', []], + ['Shift', []] + ]); } static create() { @@ -151,18 +166,18 @@ class DisplaySearch extends Display { onKeyDown(e) { const key = Display.getKeyFromEvent(e); - const ignoreKeys = DisplaySearch.onKeyDownIgnoreKeys; + const ignoreKeys = this._onKeyDownIgnoreKeys; - const activeModifierMap = { - 'Control': e.ctrlKey, - 'Meta': e.metaKey, - 'ANY_MOD': true - }; + const activeModifierMap = new Map([ + ['Control', e.ctrlKey], + ['Meta', e.metaKey], + ['ANY_MOD', true] + ]); let preventFocus = false; - for (const [modifier, keys] of Object.entries(ignoreKeys)) { - const modifierActive = activeModifierMap[modifier]; - if (key === modifier || (modifierActive && keys.includes(key))) { + for (const [modifier, keys] of ignoreKeys.entries()) { + const modifierActive = activeModifierMap.get(modifier); + if (key === modifier || (modifierActive && keys.has(key))) { preventFocus = true; break; } @@ -345,21 +360,6 @@ class DisplaySearch extends Display { } } -DisplaySearch.onKeyDownIgnoreKeys = { - 'ANY_MOD': [ - 'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End', - 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', - 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', - 'F21', 'F22', 'F23', 'F24' - ], - 'Control': ['C', 'A', 'Z', 'Y', 'X', 'F', 'G'], - 'Meta': ['C', 'A', 'Z', 'Y', 'X', 'F', 'G'], - 'OS': [], - 'Alt': [], - 'AltGraph': [], - 'Shift': [] -}; - DisplaySearch._runtimeMessageHandlers = new Map([ ['searchQueryUpdate', (self, {query}) => { self.onExternalSearchUpdate(query); }] ]); -- cgit v1.2.3 From 359eabb26eeb7e21fc866a94cbcc4afa788e93fe Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:48:30 -0500 Subject: Move event handler definitions --- ext/mixed/js/display.js | 225 +++++++++++++++++++++++------------------------- 1 file changed, 106 insertions(+), 119 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 8a68b43f..631f9e34 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -45,6 +45,110 @@ class Display { this.displayGenerator = new DisplayGenerator(); this.windowScroll = new WindowScroll(); + this._onKeyDownHandlers = new Map([ + ['Escape', () => { + this.onSearchClear(); + return true; + }], + ['PageUp', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index - 3, null, true); + return true; + } + return false; + }], + ['PageDown', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index + 3, null, true); + return true; + } + return false; + }], + ['End', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.definitions.length - 1, null, true); + return true; + } + return false; + }], + ['Home', (e) => { + if (e.altKey) { + this.entryScrollIntoView(0, null, true); + return true; + } + return false; + }], + ['ArrowUp', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index - 1, null, true); + return true; + } + return false; + }], + ['ArrowDown', (e) => { + if (e.altKey) { + this.entryScrollIntoView(this.index + 1, null, true); + return true; + } + return false; + }], + ['B', (e) => { + if (e.altKey) { + this.sourceTermView(); + return true; + } + return false; + }], + ['F', (e) => { + if (e.altKey) { + this.nextTermView(); + return true; + } + return false; + }], + ['E', (e) => { + if (e.altKey) { + this.noteTryAdd('term-kanji'); + return true; + } + return false; + }], + ['K', (e) => { + if (e.altKey) { + this.noteTryAdd('kanji'); + return true; + } + return false; + }], + ['R', (e) => { + if (e.altKey) { + this.noteTryAdd('term-kana'); + return true; + } + return false; + }], + ['P', (e) => { + if (e.altKey) { + const index = this.index; + if (index < 0 || index >= this.definitions.length) { return; } + + const entry = this.getEntry(index); + if (entry !== null && entry.dataset.type === 'term') { + this.audioPlay(this.definitions[index], this.firstExpressionIndex, index); + } + return true; + } + return false; + }], + ['V', (e) => { + if (e.altKey) { + this.noteTryView(); + return true; + } + return false; + }] + ]); + this.setInteractive(true); } @@ -215,9 +319,9 @@ class Display { onKeyDown(e) { const key = Display.getKeyFromEvent(e); - const handler = Display._onKeyDownHandlers.get(key); + const handler = this._onKeyDownHandlers.get(key); if (typeof handler === 'function') { - if (handler(this, e)) { + if (handler(e)) { e.preventDefault(); return true; } @@ -814,120 +918,3 @@ class Display { return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); } } - -Display._onKeyDownHandlers = new Map([ - ['Escape', (self) => { - self.onSearchClear(); - return true; - }], - - ['PageUp', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index - 3, null, true); - return true; - } - return false; - }], - - ['PageDown', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index + 3, null, true); - return true; - } - return false; - }], - - ['End', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.definitions.length - 1, null, true); - return true; - } - return false; - }], - - ['Home', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(0, null, true); - return true; - } - return false; - }], - - ['ArrowUp', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index - 1, null, true); - return true; - } - return false; - }], - - ['ArrowDown', (self, e) => { - if (e.altKey) { - self.entryScrollIntoView(self.index + 1, null, true); - return true; - } - return false; - }], - - ['B', (self, e) => { - if (e.altKey) { - self.sourceTermView(); - return true; - } - return false; - }], - - ['F', (self, e) => { - if (e.altKey) { - self.nextTermView(); - return true; - } - return false; - }], - - ['E', (self, e) => { - if (e.altKey) { - self.noteTryAdd('term-kanji'); - return true; - } - return false; - }], - - ['K', (self, e) => { - if (e.altKey) { - self.noteTryAdd('kanji'); - return true; - } - return false; - }], - - ['R', (self, e) => { - if (e.altKey) { - self.noteTryAdd('term-kana'); - return true; - } - return false; - }], - - ['P', (self, e) => { - if (e.altKey) { - const index = self.index; - if (index < 0 || index >= self.definitions.length) { return; } - - const entry = self.getEntry(index); - if (entry !== null && entry.dataset.type === 'term') { - self.audioPlay(self.definitions[index], self.firstExpressionIndex, index); - } - return true; - } - return false; - }], - - ['V', (self, e) => { - if (e.altKey) { - self.noteTryView(); - return true; - } - return false; - }] -]); -- cgit v1.2.3 From d17af2cbabb6d34e2053c016c93a8545fcb9052c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:48:53 -0500 Subject: Move event handler definitions --- ext/bg/js/search.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 2dd4e758..c692a859 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -54,6 +54,10 @@ class DisplaySearch extends Display { ['AltGraph', []], ['Shift', []] ]); + + this._runtimeMessageHandlers = new Map([ + ['searchQueryUpdate', ({query}) => { this.onExternalSearchUpdate(query); }] + ]); } static create() { @@ -156,10 +160,10 @@ class DisplaySearch extends Display { } onRuntimeMessage({action, params}, sender, callback) { - const handler = DisplaySearch._runtimeMessageHandlers.get(action); + const handler = this._runtimeMessageHandlers.get(action); if (typeof handler !== 'function') { return false; } - const result = handler(this, params, sender); + const result = handler(params, sender); callback(result); return false; } @@ -360,8 +364,4 @@ class DisplaySearch extends Display { } } -DisplaySearch._runtimeMessageHandlers = new Map([ - ['searchQueryUpdate', (self, {query}) => { self.onExternalSearchUpdate(query); }] -]); - DisplaySearch.instance = DisplaySearch.create(); -- cgit v1.2.3 From d08ac02c6a36d06bfc525b4839fce8736731cfd5 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 19:52:12 -0500 Subject: Move event handler definitions --- ext/fg/js/frontend.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 67045241..71ca7c9e 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -39,6 +39,15 @@ class Frontend extends TextScanner { this._contentScale = 1.0; this._orphaned = true; this._lastShowPromise = Promise.resolve(); + + this._windowMessageHandlers = new Map([ + ['popupClose', () => this.onSearchClear(true)], + ['selectionCopy', () => document.execCommand('copy')] + ]); + + this._runtimeMessageHandlers = new Map([ + ['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }] + ]); } async prepare() { @@ -72,17 +81,17 @@ class Frontend extends TextScanner { onWindowMessage(e) { const action = e.data; - const handler = Frontend._windowMessageHandlers.get(action); + const handler = this._windowMessageHandlers.get(action); if (typeof handler !== 'function') { return false; } - handler(this); + handler(); } onRuntimeMessage({action, params}, sender, callback) { - const handler = Frontend._runtimeMessageHandlers.get(action); + const handler = this._runtimeMessageHandlers.get(action); if (typeof handler !== 'function') { return false; } - const result = handler(this, params, sender); + const result = handler(params, sender); callback(result); return false; } @@ -237,12 +246,3 @@ class Frontend extends TextScanner { return visualViewport !== null && typeof visualViewport === 'object' ? visualViewport.scale : 1.0; } } - -Frontend._windowMessageHandlers = new Map([ - ['popupClose', (self) => self.onSearchClear(true)], - ['selectionCopy', () => document.execCommand('copy')] -]); - -Frontend._runtimeMessageHandlers = new Map([ - ['popupSetVisibleOverride', (self, {visible}) => { self.popup.setVisibleOverride(visible); }] -]); -- cgit v1.2.3 From 03ba1b633e2230435f6baeeaa4b6009de2932452 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 20:02:50 -0500 Subject: Move event handler definitions --- ext/fg/js/float.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 8f21a9c5..7cc9c367 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -33,6 +33,24 @@ class DisplayFloat extends Display { this._messageToken = null; this._messageTokenPromise = null; + this._onKeyDownHandlers = new Map([ + ['C', (e) => { + if (e.ctrlKey && !window.getSelection().toString()) { + this.onSelectionCopy(); + return true; + } + return false; + }] + ]); + + this._windowMessageHandlers = new Map([ + ['setContent', ({type, details}) => this.setContent(type, details)], + ['clearAutoPlayTimer', () => this.clearAutoPlayTimer()], + ['setCustomCss', ({css}) => this.setCustomCss(css)], + ['prepare', ({options, popupInfo, url, childrenSupported, scale, uniqueId}) => this.prepare(options, popupInfo, url, childrenSupported, scale, uniqueId)], + ['setContentScale', ({scale}) => this.setContentScale(scale)] + ]); + yomichan.on('orphaned', () => this.onOrphaned()); window.addEventListener('message', (e) => this.onMessage(e), false); } @@ -98,9 +116,9 @@ class DisplayFloat extends Display { onKeyDown(e) { const key = Display.getKeyFromEvent(e); - const handler = DisplayFloat._onKeyDownHandlers.get(key); + const handler = this._onKeyDownHandlers.get(key); if (typeof handler === 'function') { - if (handler(this, e)) { + if (handler(e)) { e.preventDefault(); return true; } @@ -126,10 +144,10 @@ class DisplayFloat extends Display { return; } - const handler = DisplayFloat._messageHandlers.get(action); + const handler = this._windowMessageHandlers.get(action); if (typeof handler !== 'function') { return; } - handler(this, params); + handler(params); } getOptionsContext() { @@ -153,22 +171,4 @@ class DisplayFloat extends Display { } } -DisplayFloat._onKeyDownHandlers = new Map([ - ['C', (self, e) => { - if (e.ctrlKey && !window.getSelection().toString()) { - self.onSelectionCopy(); - return true; - } - return false; - }] -]); - -DisplayFloat._messageHandlers = new Map([ - ['setContent', (self, {type, details}) => self.setContent(type, details)], - ['clearAutoPlayTimer', (self) => self.clearAutoPlayTimer()], - ['setCustomCss', (self, {css}) => self.setCustomCss(css)], - ['prepare', (self, {options, popupInfo, url, childrenSupported, scale, uniqueId}) => self.prepare(options, popupInfo, url, childrenSupported, scale, uniqueId)], - ['setContentScale', (self, {scale}) => self.setContentScale(scale)] -]); - DisplayFloat.instance = new DisplayFloat(); -- cgit v1.2.3 From 78dc501d02830244f898d5752c3cf2bc9841524c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 20:07:14 -0500 Subject: Move event handler definitions --- ext/bg/js/settings/popup-preview-frame.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index aa2b6100..d0336b5e 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -28,6 +28,12 @@ class SettingsPopupPreview { this.themeChangeTimeout = null; this.textSource = null; this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); + + this._windowMessageHandlers = new Map([ + ['setText', ({text}) => this.setText(text)], + ['setCustomCss', ({css}) => this.setCustomCss(css)], + ['setCustomOuterCss', ({css}) => this.setCustomOuterCss(css)] + ]); } static create() { @@ -101,10 +107,10 @@ class SettingsPopupPreview { if (e.origin !== this._targetOrigin) { return; } const {action, params} = e.data; - const handler = SettingsPopupPreview._messageHandlers.get(action); + const handler = this._windowMessageHandlers.get(action); if (typeof handler !== 'function') { return; } - handler(this, params); + handler(params); } onThemeDarkCheckboxChanged(node) { @@ -171,12 +177,6 @@ class SettingsPopupPreview { } } -SettingsPopupPreview._messageHandlers = new Map([ - ['setText', (self, {text}) => self.setText(text)], - ['setCustomCss', (self, {css}) => self.setCustomCss(css)], - ['setCustomOuterCss', (self, {css}) => self.setCustomOuterCss(css)] -]); - SettingsPopupPreview.instance = SettingsPopupPreview.create(); -- cgit v1.2.3 From fc08cd74fe030ced6840a51ebe093f924cdd87e0 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 20:35:37 -0500 Subject: Use .bind instead of () => {} --- ext/fg/js/popup-proxy-host.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index e55801ff..bef2cb16 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -34,16 +34,16 @@ class PopupProxyHost { if (typeof frameId !== 'number') { return; } this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, new Map([ - ['getOrCreatePopup', ({id, parentId}) => this._onApiGetOrCreatePopup(id, parentId)], - ['setOptions', ({id, options}) => this._onApiSetOptions(id, options)], - ['hide', ({id, changeFocus}) => this._onApiHide(id, changeFocus)], - ['isVisible', ({id}) => this._onApiIsVisibleAsync(id)], - ['setVisibleOverride', ({id, visible}) => this._onApiSetVisibleOverride(id, visible)], - ['containsPoint', ({id, x, y}) => this._onApiContainsPoint(id, x, y)], - ['showContent', ({id, elementRect, writingMode, type, details}) => this._onApiShowContent(id, elementRect, writingMode, type, details)], - ['setCustomCss', ({id, css}) => this._onApiSetCustomCss(id, css)], - ['clearAutoPlayTimer', ({id}) => this._onApiClearAutoPlayTimer(id)], - ['setContentScale', ({id, scale}) => this._onApiSetContentScale(id, scale)] + ['getOrCreatePopup', this._onApiGetOrCreatePopup.bind(this)], + ['setOptions', this._onApiSetOptions.bind(this)], + ['hide', this._onApiHide.bind(this)], + ['isVisible', this._onApiIsVisibleAsync.bind(this)], + ['setVisibleOverride', this._onApiSetVisibleOverride.bind(this)], + ['containsPoint', this._onApiContainsPoint.bind(this)], + ['showContent', this._onApiShowContent.bind(this)], + ['setCustomCss', this._onApiSetCustomCss.bind(this)], + ['clearAutoPlayTimer', this._onApiClearAutoPlayTimer.bind(this)], + ['setContentScale', this._onApiSetContentScale.bind(this)] ])); } @@ -87,56 +87,56 @@ class PopupProxyHost { // Message handlers - async _onApiGetOrCreatePopup(id, parentId) { + async _onApiGetOrCreatePopup({id, parentId}) { const popup = this.getOrCreatePopup(id, parentId); return { id: popup.id }; } - async _onApiSetOptions(id, options) { + async _onApiSetOptions({id, options}) { const popup = this._getPopup(id); return await popup.setOptions(options); } - async _onApiHide(id, changeFocus) { + async _onApiHide({id, changeFocus}) { const popup = this._getPopup(id); return popup.hide(changeFocus); } - async _onApiIsVisibleAsync(id) { + async _onApiIsVisibleAsync({id}) { const popup = this._getPopup(id); return await popup.isVisible(); } - async _onApiSetVisibleOverride(id, visible) { + async _onApiSetVisibleOverride({id, visible}) { const popup = this._getPopup(id); return await popup.setVisibleOverride(visible); } - async _onApiContainsPoint(id, x, y) { + async _onApiContainsPoint({id, x, y}) { const popup = this._getPopup(id); return await popup.containsPoint(x, y); } - async _onApiShowContent(id, elementRect, writingMode, type, details) { + async _onApiShowContent({id, elementRect, writingMode, type, details}) { const popup = this._getPopup(id); elementRect = PopupProxyHost._convertJsonRectToDOMRect(popup, elementRect); if (!PopupProxyHost._popupCanShow(popup)) { return; } return await popup.showContent(elementRect, writingMode, type, details); } - async _onApiSetCustomCss(id, css) { + async _onApiSetCustomCss({id, css}) { const popup = this._getPopup(id); return popup.setCustomCss(css); } - async _onApiClearAutoPlayTimer(id) { + async _onApiClearAutoPlayTimer({id}) { const popup = this._getPopup(id); return popup.clearAutoPlayTimer(); } - async _onApiSetContentScale(id, scale) { + async _onApiSetContentScale({id, scale}) { const popup = this._getPopup(id); return popup.setContentScale(scale); } -- cgit v1.2.3 From 8bc1a409144898124386ef03e921efb2a6e73a8f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 21:01:40 -0500 Subject: Use .bind instead of () => {} --- ext/bg/js/backend.js | 6 +++--- ext/bg/js/search.js | 14 +++++++------- ext/bg/js/settings/audio-ui.js | 6 +++--- ext/bg/js/settings/conditions-ui.js | 12 ++++++------ ext/bg/js/settings/dictionaries.js | 10 +++++----- ext/bg/js/settings/popup-preview-frame.js | 6 +++--- ext/fg/js/float.js | 4 ++-- ext/fg/js/frontend.js | 6 +++--- ext/fg/js/popup.js | 2 +- ext/mixed/js/scroll.js | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 25537e55..238ac52c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -100,10 +100,10 @@ class Backend { this.onOptionsUpdated('background'); if (isObject(chrome.commands) && isObject(chrome.commands.onCommand)) { - chrome.commands.onCommand.addListener((command) => this._runCommand(command)); + chrome.commands.onCommand.addListener(this._runCommand.bind(this)); } if (isObject(chrome.tabs) && isObject(chrome.tabs.onZoomChange)) { - chrome.tabs.onZoomChange.addListener((info) => this._onZoomChange(info)); + chrome.tabs.onZoomChange.addListener(this._onZoomChange.bind(this)); } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); @@ -116,7 +116,7 @@ class Backend { this.isPreparedResolve = null; this.isPreparedPromise = null; - this.clipboardMonitor.onClipboardText = (text) => this._onClipboardText(text); + this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); } onOptionsUpdated(source) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index c692a859..e9b1918b 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -93,18 +93,18 @@ class DisplaySearch extends Display { } else { this.clipboardMonitorEnable.checked = false; } - this.clipboardMonitorEnable.addEventListener('change', (e) => this.onClipboardMonitorEnableChange(e)); + this.clipboardMonitorEnable.addEventListener('change', this.onClipboardMonitorEnableChange.bind(this)); } chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); - this.search.addEventListener('click', (e) => this.onSearch(e), false); - this.query.addEventListener('input', () => this.onSearchInput(), false); - this.wanakanaEnable.addEventListener('change', (e) => this.onWanakanaEnableChange(e)); - window.addEventListener('popstate', (e) => this.onPopState(e)); - window.addEventListener('copy', (e) => this.onCopy(e)); + this.search.addEventListener('click', this.onSearch.bind(this), false); + this.query.addEventListener('input', this.onSearchInput.bind(this), false); + this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); + window.addEventListener('popstate', this.onPopState.bind(this)); + window.addEventListener('copy', this.onCopy.bind(this)); - this.clipboardMonitor.onClipboardText = (text) => this.onExternalSearchUpdate(text); + this.clipboardMonitor.onClipboardText = this.onExternalSearchUpdate.bind(this); this.updateSearchButton(); } catch (e) { diff --git a/ext/bg/js/settings/audio-ui.js b/ext/bg/js/settings/audio-ui.js index 555380b4..206539a4 100644 --- a/ext/bg/js/settings/audio-ui.js +++ b/ext/bg/js/settings/audio-ui.js @@ -37,7 +37,7 @@ AudioSourceUI.Container = class Container { this.children.push(new AudioSourceUI.AudioSource(this, audioSource, this.children.length)); } - this._clickListener = () => this.onAddAudioSource(); + this._clickListener = this.onAddAudioSource.bind(this); this.addButton.addEventListener('click', this._clickListener, false); } @@ -105,8 +105,8 @@ AudioSourceUI.AudioSource = class AudioSource { this.select.value = audioSource; - this._selectChangeListener = () => this.onSelectChanged(); - this._removeClickListener = () => this.onRemoveClicked(); + this._selectChangeListener = this.onSelectChanged.bind(this); + this._removeClickListener = this.onRemoveClicked.bind(this); this.select.addEventListener('change', this._selectChangeListener, false); this.removeButton.addEventListener('click', this._removeClickListener, false); diff --git a/ext/bg/js/settings/conditions-ui.js b/ext/bg/js/settings/conditions-ui.js index 63e01861..4ca86b07 100644 --- a/ext/bg/js/settings/conditions-ui.js +++ b/ext/bg/js/settings/conditions-ui.js @@ -41,7 +41,7 @@ ConditionsUI.Container = class Container { this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup)); } - this.addButton.on('click', () => this.onAddConditionGroup()); + this.addButton.on('click', this.onAddConditionGroup.bind(this)); } cleanup() { @@ -127,7 +127,7 @@ ConditionsUI.ConditionGroup = class ConditionGroup { this.children.push(new ConditionsUI.Condition(this, condition)); } - this.addButton.on('click', () => this.onAddCondition()); + this.addButton.on('click', this.onAddCondition.bind(this)); } cleanup() { @@ -185,10 +185,10 @@ ConditionsUI.Condition = class Condition { this.updateOperators(); this.updateInput(); - this.input.on('change', () => this.onInputChanged()); - this.typeSelect.on('change', () => this.onConditionTypeChanged()); - this.operatorSelect.on('change', () => this.onConditionOperatorChanged()); - this.removeButton.on('click', () => this.onRemoveClicked()); + this.input.on('change', this.onInputChanged.bind(this)); + this.typeSelect.on('change', this.onConditionTypeChanged.bind(this)); + this.operatorSelect.on('change', this.onConditionOperatorChanged.bind(this)); + this.removeButton.on('click', this.onRemoveClicked.bind(this)); } cleanup() { diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 70a22a16..3ceb12fa 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -36,7 +36,7 @@ class SettingsDictionaryListUI { this.dictionaryEntries = []; this.extra = null; - document.querySelector('#dict-delete-confirm').addEventListener('click', (e) => this.onDictionaryConfirmDelete(e), false); + document.querySelector('#dict-delete-confirm').addEventListener('click', this.onDictionaryConfirmDelete.bind(this), false); } setOptionsDictionaries(optionsDictionaries) { @@ -198,10 +198,10 @@ class SettingsDictionaryEntryUI { this.applyValues(); - this.eventListeners.addEventListener(this.enabledCheckbox, 'change', (e) => this.onEnabledChanged(e), false); - this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', (e) => this.onAllowSecondarySearchesChanged(e), false); - this.eventListeners.addEventListener(this.priorityInput, 'change', (e) => this.onPriorityChanged(e), false); - this.eventListeners.addEventListener(this.deleteButton, 'click', (e) => this.onDeleteButtonClicked(e), false); + this.eventListeners.addEventListener(this.enabledCheckbox, 'change', this.onEnabledChanged.bind(this), false); + this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', this.onAllowSecondarySearchesChanged.bind(this), false); + this.eventListeners.addEventListener(this.priorityInput, 'change', this.onPriorityChanged.bind(this), false); + this.eventListeners.addEventListener(this.deleteButton, 'click', this.onDeleteButtonClicked.bind(this), false); } cleanup() { diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index d0336b5e..4c086bcd 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -44,7 +44,7 @@ class SettingsPopupPreview { async prepare() { // Setup events - window.addEventListener('message', (e) => this.onMessage(e), false); + window.addEventListener('message', this.onMessage.bind(this), false); const themeDarkCheckbox = document.querySelector('#theme-dark-checkbox'); if (themeDarkCheckbox !== null) { @@ -52,7 +52,7 @@ class SettingsPopupPreview { } // Overwrite API functions - window.apiOptionsGet = (...args) => this.apiOptionsGet(...args); + window.apiOptionsGet = this.apiOptionsGet.bind(this); // Overwrite frontend const popupHost = new PopupProxyHost(); @@ -62,7 +62,7 @@ class SettingsPopupPreview { this.popup.setChildrenSupported(false); this.popupSetCustomOuterCssOld = this.popup.setCustomOuterCss; - this.popup.setCustomOuterCss = (...args) => this.popupSetCustomOuterCss(...args); + this.popup.setCustomOuterCss = this.popupSetCustomOuterCss.bind(this); this.frontend = new Frontend(this.popup); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 7cc9c367..aef0367e 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -51,8 +51,8 @@ class DisplayFloat extends Display { ['setContentScale', ({scale}) => this.setContentScale(scale)] ]); - yomichan.on('orphaned', () => this.onOrphaned()); - window.addEventListener('message', (e) => this.onMessage(e), false); + yomichan.on('orphaned', this.onOrphaned.bind(this)); + window.addEventListener('message', this.onMessage.bind(this), false); } async prepare(options, popupInfo, url, childrenSupported, scale, uniqueId) { diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 71ca7c9e..929ab56a 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -64,9 +64,9 @@ class Frontend extends TextScanner { window.visualViewport.addEventListener('resize', this.onVisualViewportResize.bind(this)); } - yomichan.on('orphaned', () => this.onOrphaned()); - yomichan.on('optionsUpdated', () => this.updateOptions()); - yomichan.on('zoomChanged', (e) => this.onZoomChanged(e)); + yomichan.on('orphaned', this.onOrphaned.bind(this)); + yomichan.on('optionsUpdated', this.updateOptions.bind(this)); + yomichan.on('zoomChanged', this.onZoomChanged.bind(this)); chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); this._updateContentScale(); diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 4927f4bd..bc40a8c4 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -260,7 +260,7 @@ class Popup { 'mozfullscreenchange', 'webkitfullscreenchange' ]; - const onFullscreenChanged = () => this._onFullscreenChanged(); + const onFullscreenChanged = this._onFullscreenChanged.bind(this); for (const eventName of fullscreenEvents) { this._fullscreenEventListeners.addEventListener(document, eventName, onFullscreenChanged, false); } diff --git a/ext/mixed/js/scroll.js b/ext/mixed/js/scroll.js index 5829d294..72da8b65 100644 --- a/ext/mixed/js/scroll.js +++ b/ext/mixed/js/scroll.js @@ -26,7 +26,7 @@ class WindowScroll { this.animationEndTime = 0; this.animationEndX = 0; this.animationEndY = 0; - this.requestAnimationFrameCallback = (t) => this.onAnimationFrame(t); + this.requestAnimationFrameCallback = this.onAnimationFrame.bind(this); } toY(y) { -- cgit v1.2.3 From 2d109c3e56231fc84dd225977bc4ef78eac9ed4d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 21:17:01 -0500 Subject: Use functions directly rather than wrapping in () => {} when args are same --- ext/bg/js/settings/anki-templates.js | 8 ++++---- ext/bg/js/settings/anki.js | 6 +++--- ext/bg/js/settings/audio.js | 8 ++++---- ext/bg/js/settings/dictionaries.js | 16 ++++++++-------- ext/bg/js/settings/main.js | 2 +- ext/bg/js/settings/profiles.js | 16 ++++++++-------- ext/bg/js/settings/storage.js | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 2e80e334..770716d4 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -45,10 +45,10 @@ function ankiTemplatesInitialize() { node.addEventListener('click', onAnkiTemplateMarkerClicked, false); } - $('#field-templates').on('change', (e) => onAnkiFieldTemplatesChanged(e)); - $('#field-template-render').on('click', (e) => onAnkiTemplateRender(e)); - $('#field-templates-reset').on('click', (e) => onAnkiFieldTemplatesReset(e)); - $('#field-templates-reset-confirm').on('click', (e) => onAnkiFieldTemplatesResetConfirm(e)); + $('#field-templates').on('change', onAnkiFieldTemplatesChanged); + $('#field-template-render').on('click', onAnkiTemplateRender); + $('#field-templates-reset').on('click', onAnkiFieldTemplatesReset); + $('#field-templates-reset-confirm').on('click', onAnkiFieldTemplatesResetConfirm); ankiTemplatesUpdateValue(); } diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index 4263fc51..782691ab 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -154,10 +154,10 @@ async function _ankiFieldsPopulate(tabId, options) { container.appendChild(fragment); for (const node of container.querySelectorAll('.anki-field-value')) { - node.addEventListener('change', (e) => onFormOptionsChanged(e), false); + node.addEventListener('change', onFormOptionsChanged, false); } for (const node of container.querySelectorAll('.marker-link')) { - node.addEventListener('click', (e) => _onAnkiMarkerClicked(e), false); + node.addEventListener('click', _onAnkiMarkerClicked, false); } } @@ -267,7 +267,7 @@ function ankiGetFieldMarkers(type) { function ankiInitialize() { for (const node of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) { - node.addEventListener('change', (e) => _onAnkiModelChanged(e), false); + node.addEventListener('change', _onAnkiModelChanged, false); } } diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 588d9a11..6d183a43 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -29,7 +29,7 @@ async function audioSettingsInitialize() { document.querySelector('.audio-source-list'), document.querySelector('.audio-source-add') ); - audioSourceUI.save = () => settingsSaveOptions(); + audioSourceUI.save = settingsSaveOptions; textToSpeechInitialize(); } @@ -37,11 +37,11 @@ async function audioSettingsInitialize() { function textToSpeechInitialize() { if (typeof speechSynthesis === 'undefined') { return; } - speechSynthesis.addEventListener('voiceschanged', () => updateTextToSpeechVoices(), false); + speechSynthesis.addEventListener('voiceschanged', updateTextToSpeechVoices, false); updateTextToSpeechVoices(); - document.querySelector('#text-to-speech-voice').addEventListener('change', (e) => onTextToSpeechVoiceChange(e), false); - document.querySelector('#text-to-speech-voice-test').addEventListener('click', () => textToSpeechTest(), false); + document.querySelector('#text-to-speech-voice').addEventListener('change', onTextToSpeechVoiceChange, false); + document.querySelector('#text-to-speech-voice-test').addEventListener('click', textToSpeechTest, false); } function updateTextToSpeechVoices() { diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 3ceb12fa..b9551073 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -341,14 +341,14 @@ async function dictSettingsInitialize() { document.querySelector('#dict-groups-extra'), document.querySelector('#dict-extra-template') ); - dictionaryUI.save = () => settingsSaveOptions(); - - document.querySelector('#dict-purge-button').addEventListener('click', (e) => onDictionaryPurgeButtonClick(e), false); - document.querySelector('#dict-purge-confirm').addEventListener('click', (e) => onDictionaryPurge(e), false); - document.querySelector('#dict-file-button').addEventListener('click', (e) => onDictionaryImportButtonClick(e), false); - document.querySelector('#dict-file').addEventListener('change', (e) => onDictionaryImport(e), false); - document.querySelector('#dict-main').addEventListener('change', (e) => onDictionaryMainChanged(e), false); - document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', (e) => onDatabaseEnablePrefixWildcardSearchesChanged(e), false); + dictionaryUI.save = settingsSaveOptions; + + document.querySelector('#dict-purge-button').addEventListener('click', onDictionaryPurgeButtonClick, false); + document.querySelector('#dict-purge-confirm').addEventListener('click', onDictionaryPurge, false); + document.querySelector('#dict-file-button').addEventListener('click', onDictionaryImportButtonClick, false); + document.querySelector('#dict-file').addEventListener('change', onDictionaryImport, false); + document.querySelector('#dict-main').addEventListener('change', onDictionaryMainChanged, false); + document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', onDatabaseEnablePrefixWildcardSearchesChanged, false); await onDictionaryOptionsChanged(); await onDatabaseUpdated(); diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index d1ad2c6b..127a6d2b 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -200,7 +200,7 @@ async function formWrite(options) { } function formSetupEventListeners() { - $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change((e) => onFormOptionsChanged(e)); + $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(onFormOptionsChanged); } function formUpdateVisibility(options) { diff --git a/ext/bg/js/settings/profiles.js b/ext/bg/js/settings/profiles.js index 3e589809..f946a33a 100644 --- a/ext/bg/js/settings/profiles.js +++ b/ext/bg/js/settings/profiles.js @@ -39,16 +39,16 @@ async function profileOptionsSetup() { } function profileOptionsSetupEventListeners() { - $('#profile-target').change((e) => onTargetProfileChanged(e)); - $('#profile-name').change((e) => onProfileNameChanged(e)); - $('#profile-add').click((e) => onProfileAdd(e)); - $('#profile-remove').click((e) => onProfileRemove(e)); - $('#profile-remove-confirm').click((e) => onProfileRemoveConfirm(e)); - $('#profile-copy').click((e) => onProfileCopy(e)); - $('#profile-copy-confirm').click((e) => onProfileCopyConfirm(e)); + $('#profile-target').change(onTargetProfileChanged); + $('#profile-name').change(onProfileNameChanged); + $('#profile-add').click(onProfileAdd); + $('#profile-remove').click(onProfileRemove); + $('#profile-remove-confirm').click(onProfileRemoveConfirm); + $('#profile-copy').click(onProfileCopy); + $('#profile-copy-confirm').click(onProfileCopyConfirm); $('#profile-move-up').click(() => onProfileMove(-1)); $('#profile-move-down').click(() => onProfileMove(1)); - $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change((e) => onProfileOptionsChanged(e)); + $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change(onProfileOptionsChanged); } function tryGetIntegerValue(selector, min, max) { diff --git a/ext/bg/js/settings/storage.js b/ext/bg/js/settings/storage.js index cbe1bb4d..8978414e 100644 --- a/ext/bg/js/settings/storage.js +++ b/ext/bg/js/settings/storage.js @@ -57,7 +57,7 @@ async function storageInfoInitialize() { await storageShowInfo(); - document.querySelector('#storage-refresh').addEventListener('click', () => storageShowInfo(), false); + document.querySelector('#storage-refresh').addEventListener('click', storageShowInfo, false); } async function storageUpdateStats() { -- cgit v1.2.3 From fdfc2d33bbcd32f2984e74bd58a518fcc50705d2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 26 Feb 2020 21:19:22 -0500 Subject: Simplify event to use bind --- ext/bg/js/settings/popup-preview-frame.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 4c086bcd..1ceac177 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -46,10 +46,7 @@ class SettingsPopupPreview { // Setup events window.addEventListener('message', this.onMessage.bind(this), false); - const themeDarkCheckbox = document.querySelector('#theme-dark-checkbox'); - if (themeDarkCheckbox !== null) { - themeDarkCheckbox.addEventListener('change', () => this.onThemeDarkCheckboxChanged(themeDarkCheckbox), false); - } + document.querySelector('#theme-dark-checkbox').addEventListener('change', this.onThemeDarkCheckboxChanged.bind(this), false); // Overwrite API functions window.apiOptionsGet = this.apiOptionsGet.bind(this); @@ -113,8 +110,8 @@ class SettingsPopupPreview { handler(params); } - onThemeDarkCheckboxChanged(node) { - document.documentElement.classList.toggle('dark', node.checked); + onThemeDarkCheckboxChanged(e) { + document.documentElement.classList.toggle('dark', e.target.checked); if (this.themeChangeTimeout !== null) { clearTimeout(this.themeChangeTimeout); } -- cgit v1.2.3 From 8e29da0c6bd0b80dc6c9e37a525a37258518c293 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 27 Feb 2020 20:33:13 -0500 Subject: Load default Anki field templates from a file --- .../data/default-anki-field-templates.handlebars | 161 +++++++++++++++++++ ext/bg/js/backend.js | 20 +-- ext/bg/js/options.js | 177 +-------------------- ext/bg/js/settings/anki-templates.js | 16 +- ext/bg/js/settings/backup.js | 8 +- ext/mixed/js/api.js | 4 + 6 files changed, 191 insertions(+), 195 deletions(-) create mode 100644 ext/bg/data/default-anki-field-templates.handlebars diff --git a/ext/bg/data/default-anki-field-templates.handlebars b/ext/bg/data/default-anki-field-templates.handlebars new file mode 100644 index 00000000..0442f7c5 --- /dev/null +++ b/ext/bg/data/default-anki-field-templates.handlebars @@ -0,0 +1,161 @@ +{{#*inline "glossary-single"}} + {{~#unless brief~}} + {{~#if definitionTags~}}({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}}) {{/if~}} + {{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}} + {{~/unless~}} + {{~#if glossary.[1]~}} + {{~#if compactGlossaries~}} + {{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}} + {{~else~}} +
    {{#each glossary}}
  • {{#multiLine}}{{.}}{{/multiLine}}
  • {{/each}}
+ {{~/if~}} + {{~else~}} + {{~#multiLine}}{{glossary.[0]}}{{/multiLine~}} + {{~/if~}} +{{/inline}} + +{{#*inline "audio"}}{{/inline}} + +{{#*inline "character"}} + {{~definition.character~}} +{{/inline}} + +{{#*inline "dictionary"}} + {{~definition.dictionary~}} +{{/inline}} + +{{#*inline "expression"}} + {{~#if merge~}} + {{~#if modeTermKana~}} + {{~#each definition.reading~}} + {{{.}}} + {{~#unless @last}}、{{/unless~}} + {{~else~}} + {{~#each definition.expression~}} + {{{.}}} + {{~#unless @last}}、{{/unless~}} + {{~/each~}} + {{~/each~}} + {{~else~}} + {{~#each definition.expression~}} + {{{.}}} + {{~#unless @last}}、{{/unless~}} + {{~/each~}} + {{~/if~}} + {{~else~}} + {{~#if modeTermKana~}} + {{~#if definition.reading~}} + {{definition.reading}} + {{~else~}} + {{definition.expression}} + {{~/if~}} + {{~else~}} + {{definition.expression}} + {{~/if~}} + {{~/if~}} +{{/inline}} + +{{#*inline "furigana"}} + {{~#if merge~}} + {{~#each definition.expressions~}} + {{~#furigana}}{{{.}}}{{/furigana~}} + {{~#unless @last}}、{{/unless~}} + {{~/each~}} + {{~else~}} + {{#furigana}}{{{definition}}}{{/furigana}} + {{~/if~}} +{{/inline}} + +{{#*inline "furigana-plain"}} + {{~#if merge~}} + {{~#each definition.expressions~}} + {{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}} + {{~#unless @last}}、{{/unless~}} + {{~/each~}} + {{~else~}} + {{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} + {{~/if~}} +{{/inline}} + +{{#*inline "glossary"}} +
+ {{~#if modeKanji~}} + {{~#if definition.glossary.[1]~}} +
    {{#each definition.glossary}}
  1. {{.}}
  2. {{/each}}
+ {{~else~}} + {{definition.glossary.[0]}} + {{~/if~}} + {{~else~}} + {{~#if group~}} + {{~#if definition.definitions.[1]~}} +
    {{#each definition.definitions}}
  1. {{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}
  2. {{/each}}
+ {{~else~}} + {{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries~}} + {{~/if~}} + {{~else if merge~}} + {{~#if definition.definitions.[1]~}} +
    {{#each definition.definitions}}
  1. {{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}
  2. {{/each}}
+ {{~else~}} + {{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries~}} + {{~/if~}} + {{~else~}} + {{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries~}} + {{~/if~}} + {{~/if~}} +
+{{/inline}} + +{{#*inline "glossary-brief"}} + {{~> glossary brief=true ~}} +{{/inline}} + +{{#*inline "kunyomi"}} + {{~#each definition.kunyomi}}{{.}}{{#unless @last}}, {{/unless}}{{/each~}} +{{/inline}} + +{{#*inline "onyomi"}} + {{~#each definition.onyomi}}{{.}}{{#unless @last}}, {{/unless}}{{/each~}} +{{/inline}} + +{{#*inline "reading"}} + {{~#unless modeTermKana~}} + {{~#if merge~}} + {{~#each definition.reading~}} + {{{.}}} + {{~#unless @last}}、{{/unless~}} + {{~/each~}} + {{~else~}} + {{~definition.reading~}} + {{~/if~}} + {{~/unless~}} +{{/inline}} + +{{#*inline "sentence"}} + {{~#if definition.cloze}}{{definition.cloze.sentence}}{{/if~}} +{{/inline}} + +{{#*inline "cloze-prefix"}} + {{~#if definition.cloze}}{{definition.cloze.prefix}}{{/if~}} +{{/inline}} + +{{#*inline "cloze-body"}} + {{~#if definition.cloze}}{{definition.cloze.body}}{{/if~}} +{{/inline}} + +{{#*inline "cloze-suffix"}} + {{~#if definition.cloze}}{{definition.cloze.suffix}}{{/if~}} +{{/inline}} + +{{#*inline "tags"}} + {{~#each definition.definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each~}} +{{/inline}} + +{{#*inline "url"}} + {{definition.url}} +{{/inline}} + +{{#*inline "screenshot"}} + +{{/inline}} + +{{~> (lookup . "marker") ~}} \ No newline at end of file diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 238ac52c..b99d1ca4 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -17,7 +17,7 @@ */ /*global optionsSave, utilIsolate -conditionsTestValue, profileConditionsDescriptor, profileOptionsGetDefaultFieldTemplates +conditionsTestValue, profileConditionsDescriptor handlebarsRenderDynamic requestText, requestJson, optionsLoad dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat @@ -33,6 +33,7 @@ class Backend { this.clipboardMonitor = new ClipboardMonitor(); this.options = null; this.optionsSchema = null; + this.defaultAnkiFieldTemplates = null; this.optionsContext = { depth: 0, url: window.location.href @@ -74,7 +75,8 @@ class Backend { ['getDisplayTemplatesHtml', this._onApiGetDisplayTemplatesHtml.bind(this)], ['getQueryParserTemplatesHtml', this._onApiGetQueryParserTemplatesHtml.bind(this)], ['getZoom', this._onApiGetZoom.bind(this)], - ['getMessageToken', this._onApiGetMessageToken.bind(this)] + ['getMessageToken', this._onApiGetMessageToken.bind(this)], + ['getDefaultAnkiFieldTemplates', this._onApiGetDefaultAnkiFieldTemplates.bind(this)] ]); this._commandHandlers = new Map([ @@ -89,6 +91,7 @@ class Backend { await this.translator.prepare(); this.optionsSchema = await requestJson(chrome.runtime.getURL('/bg/data/options-schema.json'), 'GET'); + this.defaultAnkiFieldTemplates = await requestText(chrome.runtime.getURL('/bg/data/default-anki-field-templates.handlebars'), 'GET'); this.options = await optionsLoad(); try { this.options = JsonSchema.getValidValueOrDefault(this.optionsSchema, this.options); @@ -423,7 +426,7 @@ class Backend { async _onApiDefinitionAdd({definition, mode, context, optionsContext}) { const options = await this.getOptions(optionsContext); - const templates = Backend._getTemplates(options); + const templates = this.defaultAnkiFieldTemplates; if (mode !== 'kanji') { await audioInject( @@ -448,7 +451,7 @@ class Backend { async _onApiDefinitionsAddable({definitions, modes, optionsContext}) { const options = await this.getOptions(optionsContext); - const templates = Backend._getTemplates(options); + const templates = this.defaultAnkiFieldTemplates; const states = []; try { @@ -656,6 +659,10 @@ class Backend { return this.messageToken; } + async _onApiGetDefaultAnkiFieldTemplates() { + return this.defaultAnkiFieldTemplates; + } + // Command handlers async _onCommandSearch(params) { @@ -896,11 +903,6 @@ class Backend { return 'chrome'; } } - - static _getTemplates(options) { - const templates = options.anki.fieldTemplates; - return typeof templates === 'string' ? templates : profileOptionsGetDefaultFieldTemplates(); - } } window.yomichanBackend = new Backend(); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index f9db99a2..879b4a59 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -58,22 +58,17 @@ const profileOptionsVersionUpdates = [ options.scanning.modifier = options.scanning.requireShift ? 'shift' : 'none'; }, (options) => { - const fieldTemplatesDefault = profileOptionsGetDefaultFieldTemplates(); options.general.resultOutputMode = options.general.groupResults ? 'group' : 'split'; - options.anki.fieldTemplates = ( - (utilStringHashCode(options.anki.fieldTemplates) !== -805327496) ? - `{{#if merge}}${fieldTemplatesDefault}{{else}}${options.anki.fieldTemplates}{{/if}}` : - fieldTemplatesDefault - ); + options.anki.fieldTemplates = null; }, (options) => { if (utilStringHashCode(options.anki.fieldTemplates) === 1285806040) { - options.anki.fieldTemplates = profileOptionsGetDefaultFieldTemplates(); + options.anki.fieldTemplates = null; } }, (options) => { if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { - options.anki.fieldTemplates = profileOptionsGetDefaultFieldTemplates(); + options.anki.fieldTemplates = null; } }, (options) => { @@ -97,172 +92,6 @@ const profileOptionsVersionUpdates = [ } ]; -function profileOptionsGetDefaultFieldTemplates() { - return ` -{{#*inline "glossary-single"}} - {{~#unless brief~}} - {{~#if definitionTags~}}({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}}) {{/if~}} - {{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}} - {{~/unless~}} - {{~#if glossary.[1]~}} - {{~#if compactGlossaries~}} - {{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}} - {{~else~}} -
    {{#each glossary}}
  • {{#multiLine}}{{.}}{{/multiLine}}
  • {{/each}}
- {{~/if~}} - {{~else~}} - {{~#multiLine}}{{glossary.[0]}}{{/multiLine~}} - {{~/if~}} -{{/inline}} - -{{#*inline "audio"}}{{/inline}} - -{{#*inline "character"}} - {{~definition.character~}} -{{/inline}} - -{{#*inline "dictionary"}} - {{~definition.dictionary~}} -{{/inline}} - -{{#*inline "expression"}} - {{~#if merge~}} - {{~#if modeTermKana~}} - {{~#each definition.reading~}} - {{{.}}} - {{~#unless @last}}、{{/unless~}} - {{~else~}} - {{~#each definition.expression~}} - {{{.}}} - {{~#unless @last}}、{{/unless~}} - {{~/each~}} - {{~/each~}} - {{~else~}} - {{~#each definition.expression~}} - {{{.}}} - {{~#unless @last}}、{{/unless~}} - {{~/each~}} - {{~/if~}} - {{~else~}} - {{~#if modeTermKana~}} - {{~#if definition.reading~}} - {{definition.reading}} - {{~else~}} - {{definition.expression}} - {{~/if~}} - {{~else~}} - {{definition.expression}} - {{~/if~}} - {{~/if~}} -{{/inline}} - -{{#*inline "furigana"}} - {{~#if merge~}} - {{~#each definition.expressions~}} - {{~#furigana}}{{{.}}}{{/furigana~}} - {{~#unless @last}}、{{/unless~}} - {{~/each~}} - {{~else~}} - {{#furigana}}{{{definition}}}{{/furigana}} - {{~/if~}} -{{/inline}} - -{{#*inline "furigana-plain"}} - {{~#if merge~}} - {{~#each definition.expressions~}} - {{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}} - {{~#unless @last}}、{{/unless~}} - {{~/each~}} - {{~else~}} - {{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} - {{~/if~}} -{{/inline}} - -{{#*inline "glossary"}} -
- {{~#if modeKanji~}} - {{~#if definition.glossary.[1]~}} -
    {{#each definition.glossary}}
  1. {{.}}
  2. {{/each}}
- {{~else~}} - {{definition.glossary.[0]}} - {{~/if~}} - {{~else~}} - {{~#if group~}} - {{~#if definition.definitions.[1]~}} -
    {{#each definition.definitions}}
  1. {{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}
  2. {{/each}}
- {{~else~}} - {{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries~}} - {{~/if~}} - {{~else if merge~}} - {{~#if definition.definitions.[1]~}} -
    {{#each definition.definitions}}
  1. {{> glossary-single brief=../brief compactGlossaries=../compactGlossaries}}
  2. {{/each}}
- {{~else~}} - {{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries~}} - {{~/if~}} - {{~else~}} - {{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries~}} - {{~/if~}} - {{~/if~}} -
-{{/inline}} - -{{#*inline "glossary-brief"}} - {{~> glossary brief=true ~}} -{{/inline}} - -{{#*inline "kunyomi"}} - {{~#each definition.kunyomi}}{{.}}{{#unless @last}}, {{/unless}}{{/each~}} -{{/inline}} - -{{#*inline "onyomi"}} - {{~#each definition.onyomi}}{{.}}{{#unless @last}}, {{/unless}}{{/each~}} -{{/inline}} - -{{#*inline "reading"}} - {{~#unless modeTermKana~}} - {{~#if merge~}} - {{~#each definition.reading~}} - {{{.}}} - {{~#unless @last}}、{{/unless~}} - {{~/each~}} - {{~else~}} - {{~definition.reading~}} - {{~/if~}} - {{~/unless~}} -{{/inline}} - -{{#*inline "sentence"}} - {{~#if definition.cloze}}{{definition.cloze.sentence}}{{/if~}} -{{/inline}} - -{{#*inline "cloze-prefix"}} - {{~#if definition.cloze}}{{definition.cloze.prefix}}{{/if~}} -{{/inline}} - -{{#*inline "cloze-body"}} - {{~#if definition.cloze}}{{definition.cloze.body}}{{/if~}} -{{/inline}} - -{{#*inline "cloze-suffix"}} - {{~#if definition.cloze}}{{definition.cloze.suffix}}{{/if~}} -{{/inline}} - -{{#*inline "tags"}} - {{~#each definition.definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each~}} -{{/inline}} - -{{#*inline "url"}} - {{definition.url}} -{{/inline}} - -{{#*inline "screenshot"}} - -{{/inline}} - -{{~> (lookup . "marker") ~}} -`.trim(); -} - function profileOptionsCreateDefaults() { return { general: { diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 770716d4..244ec42e 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -17,21 +17,23 @@ */ /*global getOptionsContext, getOptionsMutable, settingsSaveOptions -profileOptionsGetDefaultFieldTemplates, ankiGetFieldMarkers, ankiGetFieldMarkersHtml, dictFieldFormat -apiOptionsGet, apiTermsFind*/ +ankiGetFieldMarkers, ankiGetFieldMarkersHtml, dictFieldFormat +apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates*/ function onAnkiFieldTemplatesReset(e) { e.preventDefault(); $('#field-template-reset-modal').modal('show'); } -function onAnkiFieldTemplatesResetConfirm(e) { +async function onAnkiFieldTemplatesResetConfirm(e) { e.preventDefault(); $('#field-template-reset-modal').modal('hide'); + const value = await apiGetDefaultAnkiFieldTemplates(); + const element = document.querySelector('#field-templates'); - element.value = profileOptionsGetDefaultFieldTemplates(); + element.value = value; element.dispatchEvent(new Event('change')); } @@ -57,7 +59,7 @@ async function ankiTemplatesUpdateValue() { const optionsContext = getOptionsContext(); const options = await apiOptionsGet(optionsContext); let templates = options.anki.fieldTemplates; - if (typeof templates !== 'string') { templates = profileOptionsGetDefaultFieldTemplates(); } + if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); } $('#field-templates').val(templates); onAnkiTemplatesValidateCompile(); @@ -89,7 +91,7 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i if (definition !== null) { const options = await apiOptionsGet(optionsContext); let templates = options.anki.fieldTemplates; - if (typeof templates !== 'string') { templates = profileOptionsGetDefaultFieldTemplates(); } + if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); } result = await dictFieldFormat(field, definition, mode, options, templates, exceptions); } } catch (e) { @@ -109,7 +111,7 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i async function onAnkiFieldTemplatesChanged(e) { // Get value let templates = e.currentTarget.value; - if (templates === profileOptionsGetDefaultFieldTemplates()) { + if (templates === await apiGetDefaultAnkiFieldTemplates()) { // Default templates = null; } diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index f4d622a4..e945d186 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -/*global apiOptionsGetFull, apiGetEnvironmentInfo +/*global apiOptionsGetFull, apiGetEnvironmentInfo, apiGetDefaultAnkiFieldTemplates utilBackend, utilIsolate, utilBackgroundIsolate, utilReadFileArrayBuffer -optionsGetDefault, optionsUpdateVersion -profileOptionsGetDefaultFieldTemplates*/ +optionsGetDefault, optionsUpdateVersion*/ // Exporting @@ -47,8 +46,7 @@ function _getSettingsExportDateString(date, dateSeparator, dateTimeSeparator, ti async function _getSettingsExportData(date) { const optionsFull = await apiOptionsGetFull(); const environment = await apiGetEnvironmentInfo(); - - const fieldTemplatesDefault = profileOptionsGetDefaultFieldTemplates(); + const fieldTemplatesDefault = await apiGetDefaultAnkiFieldTemplates(); // Format options for (const {options} of optionsFull.profiles) { diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 7ea68d59..26f4389d 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -117,6 +117,10 @@ function apiGetMessageToken() { return _apiInvoke('getMessageToken'); } +function apiGetDefaultAnkiFieldTemplates() { + return _apiInvoke('getDefaultAnkiFieldTemplates'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { -- cgit v1.2.3 From 52d8d658a2bbf0dea8fecebe2db7f80934353c91 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 29 Feb 2020 10:00:28 -0500 Subject: Fix empty keys definitions and add Shift/Alt checks --- ext/bg/js/search.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index e9b1918b..0a7a5fe1 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -49,10 +49,10 @@ class DisplaySearch extends Display { ])], ['Control', new Set(['C', 'A', 'Z', 'Y', 'X', 'F', 'G'])], ['Meta', new Set(['C', 'A', 'Z', 'Y', 'X', 'F', 'G'])], - ['OS', []], - ['Alt', []], - ['AltGraph', []], - ['Shift', []] + ['OS', new Set()], + ['Alt', new Set()], + ['AltGraph', new Set()], + ['Shift', new Set()] ]); this._runtimeMessageHandlers = new Map([ @@ -175,6 +175,8 @@ class DisplaySearch extends Display { const activeModifierMap = new Map([ ['Control', e.ctrlKey], ['Meta', e.metaKey], + ['Shift', e.shiftKey], + ['Alt', e.altKey], ['ANY_MOD', true] ]); -- cgit v1.2.3 From a46a84ee151fd218407125cc43a49632427feca2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 1 Mar 2020 11:31:19 -0500 Subject: Increase z-index for navigation-header --- ext/mixed/css/display.css | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 5a0775f0..c4758235 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -78,6 +78,7 @@ ol, ul { padding: 0.25em 0.5em; border-bottom-width: 0.07142857em; /* 14px => 1px */ border-bottom-style: solid; + z-index: 10; } :root[data-yomichan-page=search] .navigation-header { -- cgit v1.2.3 From 314c567a47cd07dee7d6cda785257b2109afcad5 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 1 Mar 2020 19:02:43 +0200 Subject: fix hotkeys in popup --- ext/fg/js/float.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index aef0367e..bc459d23 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -40,7 +40,8 @@ class DisplayFloat extends Display { return true; } return false; - }] + }], + ...this._onKeyDownHandlers ]); this._windowMessageHandlers = new Map([ @@ -114,18 +115,6 @@ class DisplayFloat extends Display { } } - onKeyDown(e) { - const key = Display.getKeyFromEvent(e); - const handler = this._onKeyDownHandlers.get(key); - if (typeof handler === 'function') { - if (handler(e)) { - e.preventDefault(); - return true; - } - } - return super.onKeyDown(e); - } - async getMessageToken() { // this._messageTokenPromise is used to ensure that only one call to apiGetMessageToken is made. if (this._messageTokenPromise === null) { -- cgit v1.2.3 From 9af8f999f4b18f76464e59f3466734eec8078d98 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 1 Mar 2020 21:26:45 +0200 Subject: compatibility transform non-primary definitions --- ext/bg/js/translator.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index a675a9f7..781eb968 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -199,8 +199,19 @@ class Translator { const strayDefinitions = defaultDefinitions.filter((definition, index) => !mergedByTermIndices.has(index)); for (const groupedDefinition of dictTermsGroup(strayDefinitions, dictionaries)) { - groupedDefinition.expressions = [Translator.createExpression(groupedDefinition.expression, groupedDefinition.reading)]; - definitionsMerged.push(groupedDefinition); + // from dictTermsMergeBySequence + const {reasons, score, expression, reading, source, dictionary} = groupedDefinition; + const compatibilityDefinition = { + reasons, + score, + expression: [expression], + reading: [reading], + expressions: [Translator.createExpression(groupedDefinition.expression, groupedDefinition.reading)], + source, + dictionary, + definitions: groupedDefinition.definitions + }; + definitionsMerged.push(compatibilityDefinition); } await this.buildTermMeta(definitionsMerged, dictionaries); -- cgit v1.2.3 From 46fee07d36b3966af9bacca8c6253b044bde07ee Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 1 Mar 2020 14:45:57 -0500 Subject: Fix audio buttons not being hidden properly --- ext/mixed/css/display.css | 8 ++------ ext/mixed/js/display.js | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index c4758235..688a357c 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -277,10 +277,6 @@ button.action-button { display: inline; } -.term-expression-details>.action-play-audio { - display: none; -} - .term-expression-details>.tags { display: inline; } @@ -318,8 +314,8 @@ button.action-button { bottom: 0.5em; } -.term-expression-list[data-multi=true] .term-expression-details>.action-play-audio { - display: block; +.term-expression-list:not([data-multi=true]) .term-expression-details>.action-play-audio { + display: none; } .term-expression-list[data-multi=true] .term-expression-details>.tags { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 631f9e34..e3e5e7df 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -369,7 +369,7 @@ class Display { updateDocumentOptions(options) { const data = document.documentElement.dataset; data.ankiEnabled = `${options.anki.enable}`; - data.audioEnabled = `${options.audio.enable}`; + data.audioEnabled = `${options.audio.enabled}`; data.compactGlossaries = `${options.general.compactGlossaries}`; data.enableSearchTags = `${options.scanning.enableSearchTags}`; data.debug = `${options.general.debugInfo}`; -- cgit v1.2.3 From 2abf46b6fa9d6c03be0d98045e111f2d8e1e41d5 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 1 Mar 2020 23:06:37 +0200 Subject: simplify backend prepare --- ext/bg/js/backend.js | 69 +++++++++++++++----------------------------- ext/bg/js/settings/backup.js | 2 +- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index b99d1ca4..07dca370 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -39,9 +39,6 @@ class Backend { url: window.location.href }; - this.isPreparedResolve = null; - this.isPreparedPromise = new Promise((resolve) => (this.isPreparedResolve = resolve)); - this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); this.popupWindow = null; @@ -110,15 +107,11 @@ class Backend { } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); - const options = this.getOptionsSync(this.optionsContext); + const options = this.getOptions(this.optionsContext); if (options.general.showGuide) { chrome.tabs.create({url: chrome.runtime.getURL('/bg/guide.html')}); } - this.isPreparedResolve(); - this.isPreparedResolve = null; - this.isPreparedPromise = null; - this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); } @@ -160,7 +153,7 @@ class Backend { } applyOptions() { - const options = this.getOptionsSync(this.optionsContext); + const options = this.getOptions(this.optionsContext); if (!options.general.enable) { this.setExtensionBadgeBackgroundColor('#555555'); this.setExtensionBadgeText('off'); @@ -186,24 +179,15 @@ class Backend { } } - async getOptionsSchema() { - if (this.isPreparedPromise !== null) { - await this.isPreparedPromise; - } + getOptionsSchema() { return this.optionsSchema; } - async getFullOptions() { - if (this.isPreparedPromise !== null) { - await this.isPreparedPromise; - } + getFullOptions() { return this.options; } - async setFullOptions(options) { - if (this.isPreparedPromise !== null) { - await this.isPreparedPromise; - } + setFullOptions(options) { try { this.options = JsonSchema.getValidValueOrDefault(this.optionsSchema, utilIsolate(options)); } catch (e) { @@ -212,18 +196,11 @@ class Backend { } } - async getOptions(optionsContext) { - if (this.isPreparedPromise !== null) { - await this.isPreparedPromise; - } - return this.getOptionsSync(optionsContext); - } - - getOptionsSync(optionsContext) { - return this.getProfileSync(optionsContext).options; + getOptions(optionsContext) { + return this.getProfile(optionsContext).options; } - getProfileSync(optionsContext) { + getProfile(optionsContext) { const profiles = this.options.profiles; if (typeof optionsContext.index === 'number') { return profiles[optionsContext.index]; @@ -290,20 +267,20 @@ class Backend { // Message handlers - _onApiOptionsSchemaGet() { + async _onApiOptionsSchemaGet() { return this.getOptionsSchema(); } - _onApiOptionsGet({optionsContext}) { + async _onApiOptionsGet({optionsContext}) { return this.getOptions(optionsContext); } - _onApiOptionsGetFull() { + async _onApiOptionsGetFull() { return this.getFullOptions(); } async _onApiOptionsSet({changedOptions, optionsContext, source}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); function getValuePaths(obj) { const valuePaths = []; @@ -343,20 +320,20 @@ class Backend { } async _onApiOptionsSave({source}) { - const options = await this.getFullOptions(); + const options = this.getFullOptions(); await optionsSave(options); this.onOptionsUpdated(source); } async _onApiKanjiFind({text, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); const definitions = await this.translator.findKanji(text, options); definitions.splice(options.general.maxResults); return definitions; } async _onApiTermsFind({text, details, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); const mode = options.general.resultOutputMode; const [definitions, length] = await this.translator.findTerms(mode, text, details, options); definitions.splice(options.general.maxResults); @@ -364,7 +341,7 @@ class Backend { } async _onApiTextParse({text, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); const results = []; while (text.length > 0) { const term = []; @@ -394,7 +371,7 @@ class Backend { } async _onApiTextParseMecab({text, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); const results = []; const rawResults = await this.mecab.parseText(text); for (const [mecabName, parsedLines] of Object.entries(rawResults)) { @@ -425,7 +402,7 @@ class Backend { } async _onApiDefinitionAdd({definition, mode, context, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); const templates = this.defaultAnkiFieldTemplates; if (mode !== 'kanji') { @@ -450,7 +427,7 @@ class Backend { } async _onApiDefinitionsAddable({definitions, modes, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); const templates = this.defaultAnkiFieldTemplates; const states = []; @@ -497,7 +474,7 @@ class Backend { } async _onApiNoteView({noteId}) { - return this.anki.guiBrowse(`nid:${noteId}`); + return await this.anki.guiBrowse(`nid:${noteId}`); } async _onApiTemplateRender({template, data}) { @@ -509,7 +486,7 @@ class Backend { } async _onApiAudioGetUrl({definition, source, optionsContext}) { - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); return await audioGetUrl(definition, source, options); } @@ -668,7 +645,7 @@ class Backend { async _onCommandSearch(params) { const {mode='existingOrNewTab', query} = params || {}; - const options = await this.getOptions(this.optionsContext); + const options = this.getOptions(this.optionsContext); const {popupWidth, popupHeight} = options.general; const baseUrl = chrome.runtime.getURL('/bg/search.html'); @@ -752,7 +729,7 @@ class Backend { }; const source = 'popup'; - const options = await this.getOptions(optionsContext); + const options = this.getOptions(optionsContext); options.general.enable = !options.general.enable; await this._onApiOptionsSave({source}); } diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index e945d186..acd21920 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -120,7 +120,7 @@ async function _onSettingsExportClick() { // Importing async function _settingsImportSetOptionsFull(optionsFull) { - return utilIsolate(await utilBackend().setFullOptions( + return utilIsolate(utilBackend().setFullOptions( utilBackgroundIsolate(optionsFull) )); } -- cgit v1.2.3 From e6e5f23cf8481db31b94c18244f404dd3374ad90 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 00:39:15 +0200 Subject: fix API calls when Backend isn't ready yet --- ext/bg/js/backend.js | 5 +++++ ext/mixed/js/api.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 07dca370..e849bc69 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -48,6 +48,7 @@ class Backend { this.messageToken = yomichan.generateId(16); this._messageHandlers = new Map([ + ['isBackendReady', this._onApiIsBackendReady.bind(this)], ['optionsSchemaGet', this._onApiOptionsSchemaGet.bind(this)], ['optionsGet', this._onApiOptionsGet.bind(this)], ['optionsGetFull', this._onApiOptionsGetFull.bind(this)], @@ -267,6 +268,10 @@ class Backend { // Message handlers + async _onApiIsBackendReady() { + return true; + } + async _onApiOptionsSchemaGet() { return this.getOptionsSchema(); } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 26f4389d..fa61a1e2 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -122,6 +122,30 @@ function apiGetDefaultAnkiFieldTemplates() { } function _apiInvoke(action, params={}) { + if (!_isBackendReady) { + if (_isBackendReadyPromise === null) { + _isBackendReadyPromise = new Promise((resolve) => (_isBackendReadyResolve = resolve)); + const checkBackendReady = async () => { + try { + if (await _apiInvokeRaw('isBackendReady')) { + _isBackendReady = true; + _isBackendReadyResolve(); + } + } catch (e) { + // NOP + } + setTimeout(checkBackendReady, 100); // poll Backend until it responds + }; + checkBackendReady(); + } + return _isBackendReadyPromise.then( + () => _apiInvokeRaw(action, params) + ); + } + return _apiInvokeRaw(action, params); +} + +function _apiInvokeRaw(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { try { @@ -148,3 +172,7 @@ function _apiInvoke(action, params={}) { function _apiCheckLastError() { // NOP } + +let _isBackendReady = false; +let _isBackendReadyResolve = null; +let _isBackendReadyPromise = null; -- cgit v1.2.3 From 967e99b7f69d24fc76999675cef44b919602dd31 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 04:51:45 +0200 Subject: ensure Backend prepare in other places --- ext/bg/js/backend.js | 22 +++++++++++++++------- ext/bg/js/search-frontend.js | 2 ++ ext/bg/js/search.js | 5 ++--- ext/fg/js/frontend.js | 1 + ext/mixed/js/api.js | 28 ---------------------------- ext/mixed/js/core.js | 13 +++++++++++++ ext/mixed/js/display.js | 1 + 7 files changed, 34 insertions(+), 38 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index e849bc69..81578462 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -48,7 +48,7 @@ class Backend { this.messageToken = yomichan.generateId(16); this._messageHandlers = new Map([ - ['isBackendReady', this._onApiIsBackendReady.bind(this)], + ['yomichanOnline', this._onApiYomichanOnline.bind(this)], ['optionsSchemaGet', this._onApiOptionsSchemaGet.bind(this)], ['optionsGet', this._onApiOptionsGet.bind(this)], ['optionsGetFull', this._onApiOptionsGetFull.bind(this)], @@ -114,19 +114,24 @@ class Backend { } this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); - } - onOptionsUpdated(source) { - this.applyOptions(); + this._sendMessageAllTabs('backendPrepared'); + } + _sendMessageAllTabs(action, params={}) { const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { - chrome.tabs.sendMessage(tab.id, {action: 'optionsUpdated', params: {source}}, callback); + chrome.tabs.sendMessage(tab.id, {action, params}, callback); } }); } + onOptionsUpdated(source) { + this.applyOptions(); + this._sendMessageAllTabs('optionsUpdated', {source}); + } + onMessage({action, params}, sender, callback) { const handler = this._messageHandlers.get(action); if (typeof handler !== 'function') { return false; } @@ -268,8 +273,11 @@ class Backend { // Message handlers - async _onApiIsBackendReady() { - return true; + async _onApiYomichanOnline(_params, sender) { + const tabId = sender.tab.id; + return new Promise((resolve) => { + chrome.tabs.sendMessage(tabId, {action: 'backendPrepared'}, resolve); + }); } async _onApiOptionsSchemaGet() { diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js index 509c4009..453a0b79 100644 --- a/ext/bg/js/search-frontend.js +++ b/ext/bg/js/search-frontend.js @@ -19,6 +19,8 @@ /*global apiOptionsGet*/ async function searchFrontendSetup() { + await yomichan.prepare(); + const optionsContext = { depth: 0, url: window.location.href diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 0a7a5fe1..f3cba7ae 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -68,9 +68,8 @@ class DisplaySearch extends Display { async prepare() { try { - const superPromise = super.prepare(); - const queryParserPromise = this.queryParser.prepare(); - await Promise.all([superPromise, queryParserPromise]); + await super.prepare(); + await this.queryParser.prepare(); const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 929ab56a..203366c3 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -52,6 +52,7 @@ class Frontend extends TextScanner { async prepare() { try { + await yomichan.prepare(); await this.updateOptions(); const {zoomFactor} = await apiGetZoom(); this._pageZoomFactor = zoomFactor; diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index fa61a1e2..26f4389d 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -122,30 +122,6 @@ function apiGetDefaultAnkiFieldTemplates() { } function _apiInvoke(action, params={}) { - if (!_isBackendReady) { - if (_isBackendReadyPromise === null) { - _isBackendReadyPromise = new Promise((resolve) => (_isBackendReadyResolve = resolve)); - const checkBackendReady = async () => { - try { - if (await _apiInvokeRaw('isBackendReady')) { - _isBackendReady = true; - _isBackendReadyResolve(); - } - } catch (e) { - // NOP - } - setTimeout(checkBackendReady, 100); // poll Backend until it responds - }; - checkBackendReady(); - } - return _isBackendReadyPromise.then( - () => _apiInvokeRaw(action, params) - ); - } - return _apiInvokeRaw(action, params); -} - -function _apiInvokeRaw(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { try { @@ -172,7 +148,3 @@ function _apiInvokeRaw(action, params={}) { function _apiCheckLastError() { // NOP } - -let _isBackendReady = false; -let _isBackendReadyResolve = null; -let _isBackendReadyPromise = null; diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 83813796..a3cb2459 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -269,17 +269,26 @@ const yomichan = (() => { constructor() { super(); + this._isBackendPreparedResolve = null; + this._isBackendPreparedPromise = new Promise((resolve) => (this._isBackendPreparedResolve = resolve)); + this._messageHandlers = new Map([ + ['backendPrepared', this._onBackendPrepared.bind(this)], ['getUrl', this._onMessageGetUrl.bind(this)], ['optionsUpdated', this._onMessageOptionsUpdated.bind(this)], ['zoomChanged', this._onMessageZoomChanged.bind(this)] ]); chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); + chrome.runtime.sendMessage({action: 'yomichanOnline'}); } // Public + prepare() { + return this._isBackendPreparedPromise; + } + generateId(length) { const array = new Uint8Array(length); window.crypto.getRandomValues(array); @@ -305,6 +314,10 @@ const yomichan = (() => { return false; } + _onBackendPrepared() { + this._isBackendPreparedResolve(); + } + _onMessageGetUrl() { return {url: window.location.href}; } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index e3e5e7df..6a762a65 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -153,6 +153,7 @@ class Display { } async prepare(options=null) { + await yomichan.prepare(); const displayGeneratorPromise = this.displayGenerator.prepare(); const updateOptionsPromise = this.updateOptions(options); await Promise.all([displayGeneratorPromise, updateOptionsPromise]); -- cgit v1.2.3 From bd48d2f919e1387063c66ef91c40ec86a1131118 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 10:35:46 +0200 Subject: fix Yomichan core message issues --- ext/bg/js/backend.js | 4 ++-- ext/mixed/js/core.js | 2 +- test/test-database.js | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 81578462..cdcfb7ad 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -48,7 +48,7 @@ class Backend { this.messageToken = yomichan.generateId(16); this._messageHandlers = new Map([ - ['yomichanOnline', this._onApiYomichanOnline.bind(this)], + ['yomichanCoreReady', this._onApiYomichanCoreReady.bind(this)], ['optionsSchemaGet', this._onApiOptionsSchemaGet.bind(this)], ['optionsGet', this._onApiOptionsGet.bind(this)], ['optionsGetFull', this._onApiOptionsGetFull.bind(this)], @@ -273,7 +273,7 @@ class Backend { // Message handlers - async _onApiYomichanOnline(_params, sender) { + async _onApiYomichanCoreReady(_params, sender) { const tabId = sender.tab.id; return new Promise((resolve) => { chrome.tabs.sendMessage(tabId, {action: 'backendPrepared'}, resolve); diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index a3cb2459..b7ac8743 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -280,7 +280,7 @@ const yomichan = (() => { ]); chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); - chrome.runtime.sendMessage({action: 'yomichanOnline'}); + chrome.runtime.sendMessage({action: 'yomichanCoreReady'}); } // Public diff --git a/test/test-database.js b/test/test-database.js index 35f22523..9a24a393 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -30,6 +30,9 @@ const chrome = { }, getURL(path2) { return url.pathToFileURL(path.join(__dirname, '..', 'ext', path2.replace(/^\//, ''))); + }, + sendMessage() { + // NOP } } }; -- cgit v1.2.3 From e0edb30efd51ac18167880d77c2dea11c73a1bfc Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 11:18:09 +0200 Subject: fix Backend prepare issues in settings --- ext/bg/js/backend.js | 4 ++++ ext/bg/js/settings/backup.js | 4 ++-- ext/bg/js/settings/main.js | 5 ++++- ext/bg/js/util.js | 6 +++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index cdcfb7ad..be43ecf6 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -39,6 +39,8 @@ class Backend { url: window.location.href }; + this.isPrepared = false; + this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); this.popupWindow = null; @@ -108,6 +110,8 @@ class Backend { } chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); + this.isPrepared = true; + const options = this.getOptions(this.optionsContext); if (options.general.showGuide) { chrome.tabs.create({url: chrome.runtime.getURL('/bg/guide.html')}); diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index acd21920..daa08c61 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -362,10 +362,10 @@ async function _onSettingsResetConfirmClick() { // Setup -window.addEventListener('DOMContentLoaded', () => { +function backupInitialize() { document.querySelector('#settings-export').addEventListener('click', _onSettingsExportClick, false); document.querySelector('#settings-import').addEventListener('click', _onSettingsImportClick, false); document.querySelector('#settings-import-file').addEventListener('change', _onSettingsImportFileChange, false); document.querySelector('#settings-reset').addEventListener('click', _onSettingsResetClick, false); document.querySelector('#settings-reset-modal-confirm').addEventListener('click', _onSettingsResetConfirmClick, false); -}, false); +} diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 127a6d2b..1bf1444c 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -21,7 +21,7 @@ utilBackend, utilIsolate, utilBackgroundIsolate ankiErrorShown, ankiFieldsToDict ankiTemplatesUpdateValue, onAnkiOptionsChanged, onDictionaryOptionsChanged appearanceInitialize, audioSettingsInitialize, profileOptionsSetup, dictSettingsInitialize -ankiInitialize, ankiTemplatesInitialize, storageInfoInitialize +ankiInitialize, ankiTemplatesInitialize, storageInfoInitialize, backupInitialize */ function getOptionsMutable(optionsContext) { @@ -262,6 +262,8 @@ function showExtensionInformation() { async function onReady() { + await yomichan.prepare(); + showExtensionInformation(); formSetupEventListeners(); @@ -271,6 +273,7 @@ async function onReady() { await dictSettingsInitialize(); ankiInitialize(); ankiTemplatesInitialize(); + backupInitialize(); storageInfoInitialize(); diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 5ce4b08c..79c6af06 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -73,7 +73,11 @@ function utilStringHashCode(string) { } function utilBackend() { - return chrome.extension.getBackgroundPage().yomichanBackend; + const backend = chrome.extension.getBackgroundPage().yomichanBackend; + if (!backend.isPrepared) { + throw new Error('Backend not ready yet'); + } + return backend; } async function utilAnkiGetModelNames() { -- cgit v1.2.3 From 56b2f2c853494b228aa4467a64ed91486432bffd Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 11:31:09 +0200 Subject: trigger yomichanCoreReady only when preparing --- ext/mixed/js/core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index b7ac8743..0e22b9ac 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -280,12 +280,12 @@ const yomichan = (() => { ]); chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); - chrome.runtime.sendMessage({action: 'yomichanCoreReady'}); } // Public prepare() { + chrome.runtime.sendMessage({action: 'yomichanCoreReady'}); return this._isBackendPreparedPromise; } -- cgit v1.2.3 From a05a05c4f49d97f44dc3e1599906047c4a346512 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 12:01:53 +0200 Subject: fix content script entry point Yomichan prepare Covers Popup stuff in addition to Frontend --- ext/fg/js/frontend-initialize.js | 2 ++ ext/fg/js/frontend.js | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js index 54b874f2..bbb789cc 100644 --- a/ext/fg/js/frontend-initialize.js +++ b/ext/fg/js/frontend-initialize.js @@ -19,6 +19,8 @@ /*global PopupProxyHost, PopupProxy, Frontend*/ async function main() { + await yomichan.prepare(); + const data = window.frontendInitializationData || {}; const {id, depth=0, parentFrameId, ignoreNodes, url, proxy=false} = data; diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 203366c3..929ab56a 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -52,7 +52,6 @@ class Frontend extends TextScanner { async prepare() { try { - await yomichan.prepare(); await this.updateOptions(); const {zoomFactor} = await apiGetZoom(); this._pageZoomFactor = zoomFactor; -- cgit v1.2.3 From e6347a94e7ff0b7a5c1db7e6f83f50d93c2d4545 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 2 Mar 2020 23:26:55 +0200 Subject: prepare Backend for browser_action --- ext/bg/js/backend.js | 5 +++++ ext/bg/js/context.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index be43ecf6..14b0aff1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -278,6 +278,11 @@ class Backend { // Message handlers async _onApiYomichanCoreReady(_params, sender) { + // tab ID isn't set in background (e.g. browser_action) + if (typeof sender.tab === 'undefined') { + return chrome.runtime.sendMessage({action: 'backendPrepared'}); + } + const tabId = sender.tab.id; return new Promise((resolve) => { chrome.tabs.sendMessage(tabId, {action: 'backendPrepared'}, resolve); diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index bec964fb..1095c7e0 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -48,7 +48,9 @@ function setupButtonEvents(selector, command, url) { } } -window.addEventListener('DOMContentLoaded', () => { +window.addEventListener('DOMContentLoaded', async () => { + await yomichan.prepare(); + showExtensionInfo(); apiGetEnvironmentInfo().then(({browser}) => { -- cgit v1.2.3 From 9ceb663f290f3167bd427171a4f5cc2116e1a765 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Tue, 3 Mar 2020 00:05:01 +0200 Subject: add missing runtime message for backendPrepared --- ext/bg/js/backend.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 14b0aff1..0677b4b1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -120,6 +120,7 @@ class Backend { this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); this._sendMessageAllTabs('backendPrepared'); + chrome.runtime.sendMessage({action: 'backendPrepared'}); } _sendMessageAllTabs(action, params={}) { -- cgit v1.2.3 From 7a51a0fbde445e346a83982dd6a16ce164521e26 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 2 Mar 2020 22:20:47 -0500 Subject: Abstract Yomichan extension script execution --- test/dictionary-validate.js | 8 +- test/schema-validate.js | 6 +- test/test-database.js | 42 ++++++----- test/test-document.js | 28 ++++--- test/test-schema.js | 12 +-- test/yomichan-test.js | 13 ---- test/yomichan-vm.js | 174 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 225 insertions(+), 58 deletions(-) create mode 100644 test/yomichan-vm.js diff --git a/test/dictionary-validate.js b/test/dictionary-validate.js index 14eee2ed..6496f2ac 100644 --- a/test/dictionary-validate.js +++ b/test/dictionary-validate.js @@ -18,10 +18,12 @@ const fs = require('fs'); const path = require('path'); -const yomichanTest = require('./yomichan-test'); +const {JSZip} = require('./yomichan-test'); +const {VM} = require('./yomichan-vm'); -const JSZip = yomichanTest.JSZip; -const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); +const vm = new VM(); +vm.execute('bg/js/json-schema.js'); +const JsonSchema = vm.get('JsonSchema'); function readSchema(relativeFileName) { diff --git a/test/schema-validate.js b/test/schema-validate.js index a4f2d94c..eb31aa8d 100644 --- a/test/schema-validate.js +++ b/test/schema-validate.js @@ -17,9 +17,11 @@ */ const fs = require('fs'); -const yomichanTest = require('./yomichan-test'); +const {VM} = require('./yomichan-vm'); -const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); +const vm = new VM(); +vm.execute('bg/js/json-schema.js'); +const JsonSchema = vm.get('JsonSchema'); function main() { diff --git a/test/test-database.js b/test/test-database.js index 35f22523..fcac9a4d 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -21,6 +21,7 @@ const url = require('url'); const path = require('path'); const assert = require('assert'); const yomichanTest = require('./yomichan-test'); +const {VM} = require('./yomichan-vm'); require('fake-indexeddb/auto'); const chrome = { @@ -88,23 +89,24 @@ class XMLHttpRequest { } } -const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); -const {dictFieldSplit, dictTagSanitize} = yomichanTest.requireScript('ext/bg/js/dictionary.js', ['dictFieldSplit', 'dictTagSanitize']); -const {stringReverse} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse'], {chrome}); -const {requestJson} = yomichanTest.requireScript('ext/bg/js/request.js', ['requestJson'], {XMLHttpRequest}); -const databaseGlobals = { +const vm = new VM({ chrome, - JsonSchema, - requestJson, - stringReverse, - dictFieldSplit, - dictTagSanitize, + XMLHttpRequest, indexedDB: global.indexedDB, + IDBKeyRange: global.IDBKeyRange, JSZip: yomichanTest.JSZip -}; -databaseGlobals.window = databaseGlobals; -const {Database} = yomichanTest.requireScript('ext/bg/js/database.js', ['Database'], databaseGlobals); +}); +vm.context.window = vm.context; + +vm.execute([ + 'bg/js/json-schema.js', + 'bg/js/dictionary.js', + 'mixed/js/core.js', + 'bg/js/request.js', + 'bg/js/database.js' +]); +const Database = vm.get('Database'); function countTermsWithExpression(terms, expression) { @@ -212,20 +214,20 @@ async function testDatabase1() { }, {prefixWildcardsSupported: true} ); - assert.deepStrictEqual(errors, []); - assert.deepStrictEqual(result, expectedSummary); + vm.assert.deepStrictEqual(errors, []); + vm.assert.deepStrictEqual(result, expectedSummary); assert.ok(progressEvent); // Get info summary const info = await database.getDictionaryInfo(); - assert.deepStrictEqual(info, [expectedSummary]); + vm.assert.deepStrictEqual(info, [expectedSummary]); // Get counts const counts = await database.getDictionaryCounts( info.map((v) => v.title), true ); - assert.deepStrictEqual(counts, { + vm.assert.deepStrictEqual(counts, { counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}], total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12} }); @@ -248,10 +250,10 @@ async function testDatabase1() { async function testDatabaseEmpty1(database) { const info = await database.getDictionaryInfo(); - assert.deepStrictEqual(info, []); + vm.assert.deepStrictEqual(info, []); const counts = await database.getDictionaryCounts([], true); - assert.deepStrictEqual(counts, { + vm.assert.deepStrictEqual(counts, { counts: [], total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0} }); @@ -824,7 +826,7 @@ async function testFindTagForTitle1(database, title) { for (const {inputs, expectedResults} of data) { for (const {name} of inputs) { const result = await database.findTagForTitle(name, title); - assert.deepStrictEqual(result, expectedResults.value); + vm.assert.deepStrictEqual(result, expectedResults.value); } } } diff --git a/test/test-document.js b/test/test-document.js index edf6bbea..ab5f5716 100644 --- a/test/test-document.js +++ b/test/test-document.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const assert = require('assert'); const {JSDOM} = require('jsdom'); -const yomichanTest = require('./yomichan-test'); +const {VM} = require('./yomichan-vm'); // DOMRect class definition @@ -74,20 +74,18 @@ async function testDocument1() { const Node = window.Node; const Range = window.Range; - const {DOM} = yomichanTest.requireScript( - 'ext/mixed/js/dom.js', - ['DOM'] - ); - const {TextSourceRange, TextSourceElement} = yomichanTest.requireScript( - 'ext/fg/js/source.js', - ['TextSourceRange', 'TextSourceElement'], - {document, window, Range, Node} - ); - const {docRangeFromPoint, docSentenceExtract} = yomichanTest.requireScript( - 'ext/fg/js/document.js', - ['docRangeFromPoint', 'docSentenceExtract'], - {document, window, Node, TextSourceElement, TextSourceRange, DOM} - ); + const vm = new VM({document, window, Range, Node}); + vm.execute([ + 'mixed/js/dom.js', + 'fg/js/source.js', + 'fg/js/document.js' + ]); + const [TextSourceRange, TextSourceElement, docRangeFromPoint, docSentenceExtract] = vm.get([ + 'TextSourceRange', + 'TextSourceElement', + 'docRangeFromPoint', + 'docSentenceExtract' + ]); try { await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}); diff --git a/test/test-schema.js b/test/test-schema.js index f4612f86..5f9915fd 100644 --- a/test/test-schema.js +++ b/test/test-schema.js @@ -17,9 +17,11 @@ */ const assert = require('assert'); -const yomichanTest = require('./yomichan-test'); +const {VM} = require('./yomichan-vm'); -const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); +const vm = new VM(); +vm.execute('bg/js/json-schema.js'); +const JsonSchema = vm.get('JsonSchema'); function testValidate1() { @@ -138,7 +140,7 @@ function testGetValidValueOrDefault1() { for (const [value, expected] of testData) { const actual = JsonSchema.getValidValueOrDefault(schema, value); - assert.deepStrictEqual(actual, expected); + vm.assert.deepStrictEqual(actual, expected); } } @@ -177,7 +179,7 @@ function testGetValidValueOrDefault2() { for (const [value, expected] of testData) { const actual = JsonSchema.getValidValueOrDefault(schema, value); - assert.deepStrictEqual(actual, expected); + vm.assert.deepStrictEqual(actual, expected); } } @@ -235,7 +237,7 @@ function testGetValidValueOrDefault3() { for (const [value, expected] of testData) { const actual = JsonSchema.getValidValueOrDefault(schema, value); - assert.deepStrictEqual(actual, expected); + vm.assert.deepStrictEqual(actual, expected); } } diff --git a/test/yomichan-test.js b/test/yomichan-test.js index 78bfb9c6..0fc97b4b 100644 --- a/test/yomichan-test.js +++ b/test/yomichan-test.js @@ -22,18 +22,6 @@ const path = require('path'); let JSZip = null; -function requireScript(fileName, exportNames, variables) { - const absoluteFileName = path.join(__dirname, '..', fileName); - const source = fs.readFileSync(absoluteFileName, {encoding: 'utf8'}); - const exportNamesString = Array.isArray(exportNames) ? exportNames.join(',') : ''; - const variablesArgumentName = '__variables__'; - let variableString = ''; - if (typeof variables === 'object' && variables !== null) { - variableString = Object.keys(variables).join(','); - variableString = `const {${variableString}} = ${variablesArgumentName};`; - } - return Function(variablesArgumentName, `'use strict';${variableString}${source}\n;return {${exportNamesString}};`)(variables); -} function getJSZip() { if (JSZip === null) { @@ -64,7 +52,6 @@ function createTestDictionaryArchive(dictionary, dictionaryName) { module.exports = { - requireScript, createTestDictionaryArchive, get JSZip() { return getJSZip(); } }; diff --git a/test/yomichan-vm.js b/test/yomichan-vm.js new file mode 100644 index 00000000..ff478844 --- /dev/null +++ b/test/yomichan-vm.js @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +const fs = require('fs'); +const vm = require('vm'); +const path = require('path'); +const assert = require('assert'); + + +function getContextEnvironmentRecords(context, names) { + // Enables export of values from the declarative environment record + if (!Array.isArray(names) || names.length === 0) { + return []; + } + + let scriptSource = '(() => {\n "use strict";\n const results = [];'; + for (const name of names) { + scriptSource += `\n try { results.push(${name}); } catch (e) { results.push(void 0); }`; + } + scriptSource += '\n return results;\n})();'; + + const script = new vm.Script(scriptSource, {filename: 'getContextEnvironmentRecords'}); + + const contextHasNames = Object.prototype.hasOwnProperty.call(context, 'names'); + const contextNames = context.names; + context.names = names; + + const results = script.runInContext(context, {}); + + if (contextHasNames) { + context.names = contextNames; + } else { + delete context.names; + } + + return Array.from(results); +} + +function isDeepStrictEqual(val1, val2) { + if (val1 === val2) { return true; } + + if (Array.isArray(val1)) { + if (Array.isArray(val2)) { + return isArrayDeepStrictEqual(val1, val2); + } + } else if (typeof val1 === 'object' && val1 !== null) { + if (typeof val2 === 'object' && val2 !== null) { + return isObjectDeepStrictEqual(val1, val2); + } + } + + return false; +} + +function isArrayDeepStrictEqual(val1, val2) { + const ii = val1.length; + if (ii !== val2.length) { return false; } + + for (let i = 0; i < ii; ++i) { + if (!isDeepStrictEqual(val1[i], val2[i])) { + return false; + } + } + + return true; +} + +function isObjectDeepStrictEqual(val1, val2) { + const keys1 = Object.keys(val1); + const keys2 = Object.keys(val2); + + if (keys1.length !== keys2.length) { return false; } + + const keySet = new Set(keys1); + for (const key of keys2) { + if (!keySet.delete(key)) { return false; } + } + + for (const key of keys1) { + if (!isDeepStrictEqual(val1[key], val2[key])) { + return false; + } + } + + const tag1 = Object.prototype.toString.call(val1); + const tag2 = Object.prototype.toString.call(val2); + if (tag1 !== tag2) { return false; } + + return true; +} + +function deepStrictEqual(actual, expected) { + try { + // This will fail on prototype === comparison on cross context objects + assert.deepStrictEqual(actual, expected); + } catch (e) { + if (!isDeepStrictEqual(actual, expected)) { + throw e; + } + } +} + + +class VM { + constructor(context={}) { + this._context = vm.createContext(context); + this._assert = { + deepStrictEqual + }; + } + + get context() { + return this._context; + } + + get assert() { + return this._assert; + } + + get(names) { + if (typeof names === 'string') { + return getContextEnvironmentRecords(this._context, [names])[0]; + } else if (Array.isArray(names)) { + return getContextEnvironmentRecords(this._context, names); + } else { + throw new Error('Invalid argument'); + } + } + + set(values) { + if (typeof values === 'object' && values !== null) { + Object.assign(this._context, values); + } else { + throw new Error('Invalid argument'); + } + } + + execute(fileNames) { + const single = !Array.isArray(fileNames); + if (single) { + fileNames = [fileNames]; + } + + const results = []; + for (const fileName of fileNames) { + const absoluteFileName = path.resolve(__dirname, '..', 'ext', fileName); + const source = fs.readFileSync(absoluteFileName, {encoding: 'utf8'}); + const script = new vm.Script(source, {filename: absoluteFileName}); + results.push(script.runInContext(this._context, {})); + } + + return single ? results[0] : results; + } +} + + +module.exports = { + VM +}; -- cgit v1.2.3 From de8d9e6bf12f693d6579a2fb965fd7597a04288c Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 4 Mar 2020 05:28:22 +0200 Subject: fix return type --- ext/bg/js/backend.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 0677b4b1..5b7ab084 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -278,10 +278,11 @@ class Backend { // Message handlers - async _onApiYomichanCoreReady(_params, sender) { + _onApiYomichanCoreReady(_params, sender) { // tab ID isn't set in background (e.g. browser_action) if (typeof sender.tab === 'undefined') { - return chrome.runtime.sendMessage({action: 'backendPrepared'}); + chrome.runtime.sendMessage({action: 'backendPrepared'}); + return Promise.resolve(); } const tabId = sender.tab.id; -- cgit v1.2.3 From 8b76761744ca36d9b0c9523d3d7a80430d21abcb Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 7 Mar 2020 00:32:45 +0200 Subject: reconnect FrontendApiSender after disconnecting --- ext/fg/js/frontend-api-sender.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index 8dc6aaf3..4431df61 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -31,6 +31,8 @@ class FrontendApiSender { invoke(action, params, target) { if (this.disconnected) { + // attempt to reconnect the next time + this.disconnected = false; return Promise.reject(new Error('Disconnected')); } @@ -70,6 +72,7 @@ class FrontendApiSender { onDisconnect() { this.disconnected = true; + this.port = null; for (const id of this.callbacks.keys()) { this.onError(id, 'Disconnected'); -- cgit v1.2.3 From 86be737508d3148c90ea04d702fab30fdfb464d2 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 7 Mar 2020 03:52:36 +0200 Subject: fix popup containsPoint offset --- ext/fg/js/popup-proxy-host.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index bef2cb16..b9a5617c 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -116,7 +116,8 @@ class PopupProxyHost { async _onApiContainsPoint({id, x, y}) { const popup = this._getPopup(id); - return await popup.containsPoint(x, y); + const rootPagePoint = PopupProxyHost._convertPopupPointToRootPagePoint(popup, x, y); + return await popup.containsPoint(...rootPagePoint); } async _onApiShowContent({id, elementRect, writingMode, type, details}) { @@ -152,14 +153,17 @@ class PopupProxyHost { } static _convertJsonRectToDOMRect(popup, jsonRect) { - let x = jsonRect.x; - let y = jsonRect.y; + const [x, y] = PopupProxyHost._convertPopupPointToRootPagePoint(popup, jsonRect.x, jsonRect.y); + return new DOMRect(x, y, jsonRect.width, jsonRect.height); + } + + static _convertPopupPointToRootPagePoint(popup, x, y) { if (popup.parent !== null) { const popupRect = popup.parent.getContainerRect(); x += popupRect.x; y += popupRect.y; } - return new DOMRect(x, y, jsonRect.width, jsonRect.height); + return [x, y]; } static _popupCanShow(popup) { -- cgit v1.2.3 From 9fef0751f3dc7f213f158a50124371f0c6fb5a17 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 7 Mar 2020 04:11:18 +0200 Subject: replace spread with destructuring --- ext/fg/js/popup-proxy-host.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index b9a5617c..7d86aa67 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -116,8 +116,8 @@ class PopupProxyHost { async _onApiContainsPoint({id, x, y}) { const popup = this._getPopup(id); - const rootPagePoint = PopupProxyHost._convertPopupPointToRootPagePoint(popup, x, y); - return await popup.containsPoint(...rootPagePoint); + [x, y] = PopupProxyHost._convertPopupPointToRootPagePoint(popup, x, y); + return await popup.containsPoint(x, y); } async _onApiShowContent({id, elementRect, writingMode, type, details}) { -- cgit v1.2.3 From 387e93dbbc3ebe13f585860da4df0eeda833d49a Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 7 Mar 2020 14:20:08 +0200 Subject: use docSentenceExtract in QueryParser --- ext/bg/js/search-query-parser.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 11c7baa2..241dda87 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, TextScanner, QueryParserGenerator*/ +/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, TextScanner, QueryParserGenerator, docSentenceExtract*/ class QueryParser extends TextScanner { constructor(search) { @@ -55,12 +55,14 @@ class QueryParser extends TextScanner { const {definitions, length} = await apiTermsFind(searchText, {}, this.search.getOptionsContext()); if (definitions.length === 0) { return null; } + const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); + textSource.setEndOffset(length); this.search.setContent('terms', {definitions, context: { focus: false, disableHistory: cause === 'mouse', - sentence: {text: searchText, offset: 0}, + sentence, url: window.location.href }}); -- cgit v1.2.3 From 24d9001b1449a318113f5ecc5441113cabe7248f Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 7 Mar 2020 14:27:44 +0200 Subject: fix options object location --- ext/bg/js/search-query-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 241dda87..c64d0fea 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -55,7 +55,7 @@ class QueryParser extends TextScanner { const {definitions, length} = await apiTermsFind(searchText, {}, this.search.getOptionsContext()); if (definitions.length === 0) { return null; } - const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); + const sentence = docSentenceExtract(textSource, this.search.options.anki.sentenceExt); textSource.setEndOffset(length); -- cgit v1.2.3 From 7822230b7f969b74d3a307fe383a62be9e31c713 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 10:41:31 -0500 Subject: Use events for ClipboardMonitor --- ext/bg/js/backend.js | 4 ++-- ext/bg/js/clipboard-monitor.js | 9 +++------ ext/bg/js/search.js | 5 ++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 5b7ab084..2e46088c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -117,7 +117,7 @@ class Backend { chrome.tabs.create({url: chrome.runtime.getURL('/bg/guide.html')}); } - this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); + this.clipboardMonitor.on('change', this._onClipboardText.bind(this)); this._sendMessageAllTabs('backendPrepared'); chrome.runtime.sendMessage({action: 'backendPrepared'}); @@ -154,7 +154,7 @@ class Backend { } } - _onClipboardText(text) { + _onClipboardText({text}) { this._onCommandSearch({mode: 'popup', query: text}); } diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index c2f41385..c102572f 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -18,18 +18,15 @@ /*global apiClipboardGet, jpIsStringPartiallyJapanese*/ -class ClipboardMonitor { +class ClipboardMonitor extends EventDispatcher { constructor() { + super(); this.timerId = null; this.timerToken = null; this.interval = 250; this.previousText = null; } - onClipboardText(_text) { - throw new Error('Override me'); - } - start() { this.stop(); @@ -55,7 +52,7 @@ class ClipboardMonitor { ) { this.previousText = text; if (jpIsStringPartiallyJapanese(text)) { - this.onClipboardText(text); + this.trigger('change', {text}); } } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index f3cba7ae..9acccb90 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -102,8 +102,7 @@ class DisplaySearch extends Display { this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); window.addEventListener('popstate', this.onPopState.bind(this)); window.addEventListener('copy', this.onCopy.bind(this)); - - this.clipboardMonitor.onClipboardText = this.onExternalSearchUpdate.bind(this); + this.clipboardMonitor.on('change', this.onExternalSearchUpdate.bind(this)); this.updateSearchButton(); } catch (e) { @@ -198,7 +197,7 @@ class DisplaySearch extends Display { this.clipboardMonitor.setPreviousText(document.getSelection().toString().trim()); } - onExternalSearchUpdate(text) { + onExternalSearchUpdate({text}) { this.setQuery(text); const url = new URL(window.location.href); url.searchParams.set('query', text); -- cgit v1.2.3 From 93aa275d827816c624f30548ac635b4fea1d23eb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 10:47:30 -0500 Subject: Use explicit dependency injection for ClipboardMonitor --- ext/bg/js/api.js | 4 ---- ext/bg/js/backend.js | 2 +- ext/bg/js/clipboard-monitor.js | 9 +++++---- ext/bg/js/search.js | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 0c244ffa..93e43a7d 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -25,10 +25,6 @@ function apiAudioGetUrl(definition, source, optionsContext) { return _apiInvoke('audioGetUrl', {definition, source, optionsContext}); } -function apiClipboardGet() { - return _apiInvoke('clipboardGet'); -} - function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 2e46088c..3226dd9c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -30,7 +30,7 @@ class Backend { this.translator = new Translator(); this.anki = new AnkiNull(); this.mecab = new Mecab(); - this.clipboardMonitor = new ClipboardMonitor(); + this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index c102572f..2ba6d487 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -16,22 +16,23 @@ * along with this program. If not, see . */ -/*global apiClipboardGet, jpIsStringPartiallyJapanese*/ +/*global jpIsStringPartiallyJapanese*/ class ClipboardMonitor extends EventDispatcher { - constructor() { + constructor({getClipboard}) { super(); this.timerId = null; this.timerToken = null; this.interval = 250; this.previousText = null; + this.getClipboard = getClipboard; } start() { this.stop(); // The token below is used as a unique identifier to ensure that a new clipboard monitor - // hasn't been started during the await call. The check below the await apiClipboardGet() + // hasn't been started during the await call. The check below the await this.getClipboard() // call will exit early if the reference has changed. const token = {}; const intervalCallback = async () => { @@ -39,7 +40,7 @@ class ClipboardMonitor extends EventDispatcher { let text = null; try { - text = await apiClipboardGet(); + text = await this.getClipboard(); } catch (e) { // NOP } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 9acccb90..f9481ea2 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global apiOptionsSet, apiTermsFind, Display, QueryParser, ClipboardMonitor*/ +/*global apiOptionsSet, apiTermsFind, apiClipboardGet, Display, QueryParser, ClipboardMonitor*/ class DisplaySearch extends Display { constructor() { @@ -38,7 +38,7 @@ class DisplaySearch extends Display { this.introVisible = true; this.introAnimationTimer = null; - this.clipboardMonitor = new ClipboardMonitor(); + this.clipboardMonitor = new ClipboardMonitor({getClipboard: apiClipboardGet}); this._onKeyDownIgnoreKeys = new Map([ ['ANY_MOD', new Set([ -- cgit v1.2.3 From ba64f34df19d446cbe5b8ec2e367d4f6a4d1061f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 10:48:56 -0500 Subject: Mark fields as private --- ext/bg/js/clipboard-monitor.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index 2ba6d487..a6d73c79 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -21,59 +21,59 @@ class ClipboardMonitor extends EventDispatcher { constructor({getClipboard}) { super(); - this.timerId = null; - this.timerToken = null; - this.interval = 250; - this.previousText = null; - this.getClipboard = getClipboard; + this._timerId = null; + this._timerToken = null; + this._interval = 250; + this._previousText = null; + this._getClipboard = getClipboard; } start() { this.stop(); // The token below is used as a unique identifier to ensure that a new clipboard monitor - // hasn't been started during the await call. The check below the await this.getClipboard() + // hasn't been started during the await call. The check below the await this._getClipboard() // call will exit early if the reference has changed. const token = {}; const intervalCallback = async () => { - this.timerId = null; + this._timerId = null; let text = null; try { - text = await this.getClipboard(); + text = await this._getClipboard(); } catch (e) { // NOP } - if (this.timerToken !== token) { return; } + if (this._timerToken !== token) { return; } if ( typeof text === 'string' && (text = text.trim()).length > 0 && - text !== this.previousText + text !== this._previousText ) { - this.previousText = text; + this._previousText = text; if (jpIsStringPartiallyJapanese(text)) { this.trigger('change', {text}); } } - this.timerId = setTimeout(intervalCallback, this.interval); + this._timerId = setTimeout(intervalCallback, this._interval); }; - this.timerToken = token; + this._timerToken = token; intervalCallback(); } stop() { - this.timerToken = null; - if (this.timerId !== null) { - clearTimeout(this.timerId); - this.timerId = null; + this._timerToken = null; + if (this._timerId !== null) { + clearTimeout(this._timerId); + this._timerId = null; } } setPreviousText(text) { - this.previousText = text; + this._previousText = text; } } -- cgit v1.2.3 From eea9dc68b9f6c95ad2b98a5410a0340ba2151640 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 17:41:37 -0500 Subject: Fix runtime.lastError error on startup in Firefox --- ext/bg/js/backend.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 5b7ab084..4595dbb3 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -120,7 +120,8 @@ class Backend { this.clipboardMonitor.onClipboardText = this._onClipboardText.bind(this); this._sendMessageAllTabs('backendPrepared'); - chrome.runtime.sendMessage({action: 'backendPrepared'}); + const callback = () => this.checkLastError(chrome.runtime.lastError); + chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); } _sendMessageAllTabs(action, params={}) { @@ -281,7 +282,8 @@ class Backend { _onApiYomichanCoreReady(_params, sender) { // tab ID isn't set in background (e.g. browser_action) if (typeof sender.tab === 'undefined') { - chrome.runtime.sendMessage({action: 'backendPrepared'}); + const callback = () => this.checkLastError(chrome.runtime.lastError); + chrome.runtime.sendMessage({action: 'backendPrepared'}, callback); return Promise.resolve(); } -- cgit v1.2.3 From 110e561eae9d7a892a55f904d63a4f5ca08fae0f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 12:44:14 -0500 Subject: Create new AudioSystem class --- ext/mixed/js/audio.js | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index b5a025be..6a338cca 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -83,6 +83,124 @@ class TextToSpeechAudio { } } +class AudioSystem { + constructor() { + this._cache = new Map(); + this._cacheSizeMaximum = 32; + + if (typeof speechSynthesis !== 'undefined') { + // speechSynthesis.getVoices() will not be populated unless some API call is made. + speechSynthesis.addEventListener('voiceschanged', this._onVoicesChanged.bind(this)); + } + } + + async getExpressionAudio(expression, sources, optionsContext, details) { + const key = `${expression.expression}:${expression.reading}`; + const cacheValue = this._cache.get(expression); + if (typeof cacheValue !== 'undefined') { + const {audio, uri, source} = cacheValue; + return {audio, uri, source}; + } + + for (const source of sources) { + const uri = await apiAudioGetUrl(expression, source, optionsContext); + if (uri === null) { continue; } + + try { + const audio = await this._createAudio(uri, details); + this._cacheCheck(); + this._cache.set(key, {audio, uri, source}); + return {audio, uri, source}; + } catch (e) { + // NOP + } + } + + throw new Error('Could not create audio'); + } + + createTextToSpeechAudio({text, voiceUri}) { + const voice = this._getTextToSpeechVoiceFromVoiceUri(voiceUri); + if (voice === null) { + throw new Error('Invalid text-to-speech voice'); + } + return new TextToSpeechAudio(text, voice); + } + + _onVoicesChanged() { + // NOP + } + + async _createAudio(uri, details) { + const ttsParameters = this._getTextToSpeechParameters(uri); + if (ttsParameters !== null) { + if (typeof details === 'object' && details !== null) { + if (details.tts === false) { + throw new Error('Text-to-speech not permitted'); + } + } + return this.createTextToSpeechAudio(ttsParameters); + } + + return await this._createAudioFromUrl(uri); + } + + _createAudioFromUrl(url) { + return new Promise((resolve, reject) => { + const audio = new Audio(url); + audio.addEventListener('loadeddata', () => { + const duration = audio.duration; + if (duration === 5.694694 || duration === 5.720718) { + // Hardcoded values for invalid audio + reject(new Error('Could not retrieve audio')); + } else { + resolve(audio); + } + }); + audio.addEventListener('error', () => reject(audio.error)); + }); + } + + _getTextToSpeechVoiceFromVoiceUri(voiceUri) { + try { + for (const voice of speechSynthesis.getVoices()) { + if (voice.voiceURI === voiceUri) { + return voice; + } + } + } catch (e) { + // NOP + } + return null; + } + + _getTextToSpeechParameters(uri) { + const m = /^tts:[^#?]*\?([^#]*)/.exec(uri); + if (m === null) { return null; } + + const searchParameters = new URLSearchParams(m[1]); + const text = searchParameters.get('text'); + const voiceUri = searchParameters.get('voice'); + return (text !== null && voiceUri !== null ? {text, voiceUri} : null); + } + + _cacheCheck() { + const removeCount = this._cache.size - this._cacheSizeMaximum; + if (removeCount <= 0) { return; } + + const removeKeys = []; + for (const key of this._cache.keys()) { + removeKeys.push(key); + if (removeKeys.length >= removeCount) { break; } + } + + for (const key of removeKeys) { + this._cache.delete(key); + } + } +} + + function audioGetFromUrl(url, willDownload) { const tts = TextToSpeechAudio.createFromUri(url); if (tts !== null) { -- cgit v1.2.3 From e048a1efce5931a3aab4a8a068ee6f5ec9805ce8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 13:18:48 -0500 Subject: Use AudioSystem in Display --- ext/mixed/js/display.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 6a762a65..e0b12f7d 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -19,8 +19,7 @@ /*global docRangeFromPoint, docSentenceExtract apiKanjiFind, apiTermsFind, apiNoteView, apiOptionsGet, apiDefinitionsAddable, apiDefinitionAdd apiScreenshotGet, apiForward -audioPrepareTextToSpeech, audioGetFromSources -DisplayGenerator, WindowScroll, DisplayContext, DOM*/ +AudioSystem, DisplayGenerator, WindowScroll, DisplayContext, DOM*/ class Display { constructor(spinner, container) { @@ -32,7 +31,7 @@ class Display { this.index = 0; this.audioPlaying = null; this.audioFallback = null; - this.audioCache = new Map(); + this.audioSystem = new AudioSystem(); this.styleNode = null; this.eventListeners = new EventListenerCollection(); @@ -364,7 +363,6 @@ class Display { this.updateDocumentOptions(this.options); this.updateTheme(this.options.general.popupTheme); this.setCustomCss(this.options.general.customPopupCss); - audioPrepareTextToSpeech(this.options); } updateDocumentOptions(options) { @@ -775,16 +773,16 @@ class Display { } const sources = this.options.audio.sources; - let {audio, source} = await audioGetFromSources(expression, sources, this.getOptionsContext(), false, this.audioCache); - let info; - if (audio === null) { + let audio, source, info; + try { + ({audio, source} = await this.audioSystem.getExpressionAudio(expression, sources, this.getOptionsContext())); + info = `From source ${1 + sources.indexOf(source)}: ${source}`; + } catch (e) { if (this.audioFallback === null) { this.audioFallback = new Audio('/mixed/mp3/button.mp3'); } audio = this.audioFallback; info = 'Could not find audio'; - } else { - info = `From source ${1 + sources.indexOf(source)}: ${source}`; } const button = this.audioButtonFindImage(entryIndex); -- cgit v1.2.3 From cadcd72fadd7d8f8823e14f1ccfdd165e076734d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 13:19:12 -0500 Subject: Use AudioSystem in Backend --- ext/bg/js/audio.js | 14 ++++++-------- ext/bg/js/backend.js | 6 ++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 972e2b8b..0732e25e 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global jpIsStringEntirelyKana, audioGetFromSources*/ +/*global jpIsStringEntirelyKana*/ const audioUrlBuilders = new Map([ ['jpod101', async (definition) => { @@ -154,7 +154,7 @@ function audioBuildFilename(definition) { return null; } -async function audioInject(definition, fields, sources, optionsContext) { +async function audioInject(definition, fields, sources, optionsContext, audioSystem) { let usesAudio = false; for (const fieldValue of Object.values(fields)) { if (fieldValue.includes('{audio}')) { @@ -171,12 +171,10 @@ async function audioInject(definition, fields, sources, optionsContext) { const expressions = definition.expressions; const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, true); - if (url !== null) { - const filename = audioBuildFilename(audioSourceDefinition); - if (filename !== null) { - definition.audio = {url, filename}; - } + const {uri} = await audioSystem.getExpressionAudio(audioSourceDefinition, sources, optionsContext, {tts: false}); + const filename = audioBuildFilename(audioSourceDefinition); + if (filename !== null) { + definition.audio = {url: uri, filename}; } return true; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 04bf240d..abf4c673 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -23,7 +23,7 @@ requestText, requestJson, optionsLoad dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat audioGetUrl, audioInject jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ +AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ class Backend { constructor() { @@ -34,6 +34,7 @@ class Backend { this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; + this.audioSystem = new AudioSystem(); this.optionsContext = { depth: 0, url: window.location.href @@ -436,7 +437,8 @@ class Backend { definition, options.anki.terms.fields, options.audio.sources, - optionsContext + optionsContext, + this.audioSystem ); } -- cgit v1.2.3 From d8e2e69ca5ac7afff3cc385cc0cd18852c8d850b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 13:26:15 -0500 Subject: Use AudioSystem on the audio settings page --- ext/bg/js/settings/audio.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 6d183a43..87ce1ffb 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -17,11 +17,14 @@ */ /*global getOptionsContext, getOptionsMutable, settingsSaveOptions -AudioSourceUI, audioGetTextToSpeechVoice*/ +AudioSystem, AudioSourceUI*/ let audioSourceUI = null; +let audioSystem = null; async function audioSettingsInitialize() { + audioSystem = new AudioSystem(); + const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); audioSourceUI = new AudioSourceUI.Container( @@ -100,16 +103,11 @@ function textToSpeechVoiceCompare(a, b) { function textToSpeechTest() { try { const text = document.querySelector('#text-to-speech-voice-test').dataset.speechText || ''; - const voiceURI = document.querySelector('#text-to-speech-voice').value; - const voice = audioGetTextToSpeechVoice(voiceURI); - if (voice === null) { return; } - - const utterance = new SpeechSynthesisUtterance(text); - utterance.lang = 'ja-JP'; - utterance.voice = voice; - utterance.volume = 1.0; + const voiceUri = document.querySelector('#text-to-speech-voice').value; - speechSynthesis.speak(utterance); + const audio = audioSystem.createTextToSpeechAudio({text, voiceUri}); + audio.volume = 1.0; + audio.play(); } catch (e) { // NOP } -- cgit v1.2.3 From 75eac153d625c54892a6f7194d0cfa4160ffe722 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 13:27:44 -0500 Subject: Remove old APIs --- ext/mixed/js/audio.js | 110 -------------------------------------------------- 1 file changed, 110 deletions(-) diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index 6a338cca..d2feae04 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -66,21 +66,6 @@ class TextToSpeechAudio { // NOP } } - - static createFromUri(ttsUri) { - const m = /^tts:[^#?]*\?([^#]*)/.exec(ttsUri); - if (m === null) { return null; } - - const searchParameters = new URLSearchParams(m[1]); - const text = searchParameters.get('text'); - let voice = searchParameters.get('voice'); - if (text === null || voice === null) { return null; } - - voice = audioGetTextToSpeechVoice(voice); - if (voice === null) { return null; } - - return new TextToSpeechAudio(text, voice); - } } class AudioSystem { @@ -199,98 +184,3 @@ class AudioSystem { } } } - - -function audioGetFromUrl(url, willDownload) { - const tts = TextToSpeechAudio.createFromUri(url); - if (tts !== null) { - if (willDownload) { - throw new Error('AnkiConnect does not support downloading text-to-speech audio.'); - } - return Promise.resolve(tts); - } - - return new Promise((resolve, reject) => { - const audio = new Audio(url); - audio.addEventListener('loadeddata', () => { - if (audio.duration === 5.694694 || audio.duration === 5.720718) { - // Hardcoded values for invalid audio - reject(new Error('Could not retrieve audio')); - } else { - resolve(audio); - } - }); - audio.addEventListener('error', () => reject(audio.error)); - }); -} - -async function audioGetFromSources(expression, sources, optionsContext, willDownload, cache=null) { - const key = `${expression.expression}:${expression.reading}`; - if (cache !== null) { - const cacheValue = cache.get(expression); - if (typeof cacheValue !== 'undefined') { - return cacheValue; - } - } - - for (let i = 0, ii = sources.length; i < ii; ++i) { - const source = sources[i]; - const url = await apiAudioGetUrl(expression, source, optionsContext); - if (url === null) { - continue; - } - - try { - let audio = await audioGetFromUrl(url, willDownload); - if (willDownload) { - // AnkiConnect handles downloading URLs into cards - audio = null; - } - const result = {audio, url, source}; - if (cache !== null) { - cache.set(key, result); - } - return result; - } catch (e) { - // NOP - } - } - return {audio: null, url: null, source: null}; -} - -function audioGetTextToSpeechVoice(voiceURI) { - try { - for (const voice of speechSynthesis.getVoices()) { - if (voice.voiceURI === voiceURI) { - return voice; - } - } - } catch (e) { - // NOP - } - return null; -} - -function audioPrepareTextToSpeech(options) { - if ( - audioPrepareTextToSpeech.state || - !options.audio.textToSpeechVoice || - !( - options.audio.sources.includes('text-to-speech') || - options.audio.sources.includes('text-to-speech-reading') - ) - ) { - // Text-to-speech not in use. - return; - } - - // Chrome needs this value called once before it will become populated. - // The first call will return an empty list. - audioPrepareTextToSpeech.state = true; - try { - speechSynthesis.getVoices(); - } catch (e) { - // NOP - } -} -audioPrepareTextToSpeech.state = false; -- cgit v1.2.3 From a8eb50d96f50ae20033ccc05094caaedbae81936 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:10:43 -0500 Subject: Use dependency injection for getAudioUri implementation --- ext/bg/js/api.js | 4 ---- ext/bg/js/audio.js | 2 +- ext/bg/js/backend.js | 12 +++++++++++- ext/bg/js/settings/audio.js | 9 +++++++-- ext/mixed/js/audio.js | 9 ++++----- ext/mixed/js/display.js | 11 ++++++++--- 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 93e43a7d..4e5d81db 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -21,10 +21,6 @@ function apiTemplateRender(template, data) { return _apiInvoke('templateRender', {data, template}); } -function apiAudioGetUrl(definition, source, optionsContext) { - return _apiInvoke('audioGetUrl', {definition, source, optionsContext}); -} - function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 0732e25e..3bcfc4e7 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -171,7 +171,7 @@ async function audioInject(definition, fields, sources, optionsContext, audioSys const expressions = definition.expressions; const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - const {uri} = await audioSystem.getExpressionAudio(audioSourceDefinition, sources, optionsContext, {tts: false}); + const {uri} = await audioSystem.getExpressionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); const filename = audioBuildFilename(audioSourceDefinition); if (filename !== null) { definition.audio = {url: uri, filename}; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index abf4c673..60a87916 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -34,7 +34,7 @@ class Backend { this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; - this.audioSystem = new AudioSystem(); + this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); this.optionsContext = { depth: 0, url: window.location.href @@ -764,6 +764,16 @@ class Backend { // Utilities + async _getAudioUri(definition, source, details) { + let optionsContext = (typeof details === 'object' && details !== null ? details.optionsContext : null); + if (!(typeof optionsContext === 'object' && optionsContext !== null)) { + optionsContext = this.optionsContext; + } + + const options = this.getOptions(optionsContext); + return await audioGetUrl(definition, source, options); + } + async _injectScreenshot(definition, fields, screenshot) { let usesScreenshot = false; for (const fieldValue of Object.values(fields)) { diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 87ce1ffb..6f581d9b 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -16,14 +16,19 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, settingsSaveOptions +/*global getOptionsContext, getOptionsMutable, settingsSaveOptions, apiAudioGetUrl AudioSystem, AudioSourceUI*/ let audioSourceUI = null; let audioSystem = null; async function audioSettingsInitialize() { - audioSystem = new AudioSystem(); + audioSystem = new AudioSystem({ + getAudioUri: async (definition, source) => { + const optionsContext = getOptionsContext(); + return await apiAudioGetUrl(definition, source, optionsContext); + } + }); const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index d2feae04..1da5d48c 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -/*global apiAudioGetUrl*/ - class TextToSpeechAudio { constructor(text, voice) { this.text = text; @@ -69,9 +67,10 @@ class TextToSpeechAudio { } class AudioSystem { - constructor() { + constructor({getAudioUri}) { this._cache = new Map(); this._cacheSizeMaximum = 32; + this._getAudioUri = getAudioUri; if (typeof speechSynthesis !== 'undefined') { // speechSynthesis.getVoices() will not be populated unless some API call is made. @@ -79,7 +78,7 @@ class AudioSystem { } } - async getExpressionAudio(expression, sources, optionsContext, details) { + async getExpressionAudio(expression, sources, details) { const key = `${expression.expression}:${expression.reading}`; const cacheValue = this._cache.get(expression); if (typeof cacheValue !== 'undefined') { @@ -88,7 +87,7 @@ class AudioSystem { } for (const source of sources) { - const uri = await apiAudioGetUrl(expression, source, optionsContext); + const uri = await this._getAudioUri(expression, source, details); if (uri === null) { continue; } try { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index e0b12f7d..9d2746fd 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -18,7 +18,7 @@ /*global docRangeFromPoint, docSentenceExtract apiKanjiFind, apiTermsFind, apiNoteView, apiOptionsGet, apiDefinitionsAddable, apiDefinitionAdd -apiScreenshotGet, apiForward +apiScreenshotGet, apiForward, apiAudioGetUrl AudioSystem, DisplayGenerator, WindowScroll, DisplayContext, DOM*/ class Display { @@ -31,7 +31,7 @@ class Display { this.index = 0; this.audioPlaying = null; this.audioFallback = null; - this.audioSystem = new AudioSystem(); + this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); this.styleNode = null; this.eventListeners = new EventListenerCollection(); @@ -775,7 +775,7 @@ class Display { const sources = this.options.audio.sources; let audio, source, info; try { - ({audio, source} = await this.audioSystem.getExpressionAudio(expression, sources, this.getOptionsContext())); + ({audio, source} = await this.audioSystem.getExpressionAudio(expression, sources)); info = `From source ${1 + sources.indexOf(source)}: ${source}`; } catch (e) { if (this.audioFallback === null) { @@ -916,4 +916,9 @@ class Display { const key = event.key; return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); } + + async _getAudioUri(definition, source) { + const optionsContext = this.getOptionsContext(); + return await apiAudioGetUrl(definition, source, optionsContext); + } } -- cgit v1.2.3 From 6adf2cf63f3f67020d3d06adfdfa0f136a7e09e0 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:11:28 -0500 Subject: Rename function --- ext/bg/js/audio.js | 2 +- ext/mixed/js/audio.js | 8 ++++---- ext/mixed/js/display.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 3bcfc4e7..c94121ae 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -171,7 +171,7 @@ async function audioInject(definition, fields, sources, optionsContext, audioSys const expressions = definition.expressions; const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - const {uri} = await audioSystem.getExpressionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); + const {uri} = await audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); const filename = audioBuildFilename(audioSourceDefinition); if (filename !== null) { definition.audio = {url: uri, filename}; diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index 1da5d48c..31c476b1 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -78,16 +78,16 @@ class AudioSystem { } } - async getExpressionAudio(expression, sources, details) { - const key = `${expression.expression}:${expression.reading}`; - const cacheValue = this._cache.get(expression); + async getDefinitionAudio(definition, sources, details) { + const key = `${definition.expression}:${definition.reading}`; + const cacheValue = this._cache.get(definition); if (typeof cacheValue !== 'undefined') { const {audio, uri, source} = cacheValue; return {audio, uri, source}; } for (const source of sources) { - const uri = await this._getAudioUri(expression, source, details); + const uri = await this._getAudioUri(definition, source, details); if (uri === null) { continue; } try { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 9d2746fd..3fe8e684 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -775,7 +775,7 @@ class Display { const sources = this.options.audio.sources; let audio, source, info; try { - ({audio, source} = await this.audioSystem.getExpressionAudio(expression, sources)); + ({audio, source} = await this.audioSystem.getDefinitionAudio(expression, sources)); info = `From source ${1 + sources.indexOf(source)}: ${source}`; } catch (e) { if (this.audioFallback === null) { -- cgit v1.2.3 From dceaa853098a465b2eaa1b90900b5c1832131f26 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:16:19 -0500 Subject: Rename audio.js to audio-system.js --- ext/bg/background.html | 2 +- ext/bg/search.html | 2 +- ext/bg/settings.html | 2 +- ext/fg/float.html | 2 +- ext/mixed/js/audio-system.js | 185 +++++++++++++++++++++++++++++++++++++++++++ ext/mixed/js/audio.js | 185 ------------------------------------------- 6 files changed, 189 insertions(+), 189 deletions(-) create mode 100644 ext/mixed/js/audio-system.js delete mode 100644 ext/mixed/js/audio.js diff --git a/ext/bg/background.html b/ext/bg/background.html index 7fd1c477..f2f70d4d 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -39,7 +39,7 @@ - + diff --git a/ext/bg/search.html b/ext/bg/search.html index d6336826..f4c1a737 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -80,7 +80,7 @@ - + diff --git a/ext/bg/settings.html b/ext/bg/settings.html index b048a36c..e9fc6be5 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1098,7 +1098,7 @@ - + diff --git a/ext/fg/float.html b/ext/fg/float.html index 352a866a..7bbed565 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -46,7 +46,7 @@ - + diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js new file mode 100644 index 00000000..31c476b1 --- /dev/null +++ b/ext/mixed/js/audio-system.js @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2019-2020 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class TextToSpeechAudio { + constructor(text, voice) { + this.text = text; + this.voice = voice; + this._utterance = null; + this._volume = 1; + } + + get currentTime() { + return 0; + } + set currentTime(value) { + // NOP + } + + get volume() { + return this._volume; + } + set volume(value) { + this._volume = value; + if (this._utterance !== null) { + this._utterance.volume = value; + } + } + + play() { + try { + if (this._utterance === null) { + this._utterance = new SpeechSynthesisUtterance(this.text || ''); + this._utterance.lang = 'ja-JP'; + this._utterance.volume = this._volume; + this._utterance.voice = this.voice; + } + + speechSynthesis.cancel(); + speechSynthesis.speak(this._utterance); + } catch (e) { + // NOP + } + } + + pause() { + try { + speechSynthesis.cancel(); + } catch (e) { + // NOP + } + } +} + +class AudioSystem { + constructor({getAudioUri}) { + this._cache = new Map(); + this._cacheSizeMaximum = 32; + this._getAudioUri = getAudioUri; + + if (typeof speechSynthesis !== 'undefined') { + // speechSynthesis.getVoices() will not be populated unless some API call is made. + speechSynthesis.addEventListener('voiceschanged', this._onVoicesChanged.bind(this)); + } + } + + async getDefinitionAudio(definition, sources, details) { + const key = `${definition.expression}:${definition.reading}`; + const cacheValue = this._cache.get(definition); + if (typeof cacheValue !== 'undefined') { + const {audio, uri, source} = cacheValue; + return {audio, uri, source}; + } + + for (const source of sources) { + const uri = await this._getAudioUri(definition, source, details); + if (uri === null) { continue; } + + try { + const audio = await this._createAudio(uri, details); + this._cacheCheck(); + this._cache.set(key, {audio, uri, source}); + return {audio, uri, source}; + } catch (e) { + // NOP + } + } + + throw new Error('Could not create audio'); + } + + createTextToSpeechAudio({text, voiceUri}) { + const voice = this._getTextToSpeechVoiceFromVoiceUri(voiceUri); + if (voice === null) { + throw new Error('Invalid text-to-speech voice'); + } + return new TextToSpeechAudio(text, voice); + } + + _onVoicesChanged() { + // NOP + } + + async _createAudio(uri, details) { + const ttsParameters = this._getTextToSpeechParameters(uri); + if (ttsParameters !== null) { + if (typeof details === 'object' && details !== null) { + if (details.tts === false) { + throw new Error('Text-to-speech not permitted'); + } + } + return this.createTextToSpeechAudio(ttsParameters); + } + + return await this._createAudioFromUrl(uri); + } + + _createAudioFromUrl(url) { + return new Promise((resolve, reject) => { + const audio = new Audio(url); + audio.addEventListener('loadeddata', () => { + const duration = audio.duration; + if (duration === 5.694694 || duration === 5.720718) { + // Hardcoded values for invalid audio + reject(new Error('Could not retrieve audio')); + } else { + resolve(audio); + } + }); + audio.addEventListener('error', () => reject(audio.error)); + }); + } + + _getTextToSpeechVoiceFromVoiceUri(voiceUri) { + try { + for (const voice of speechSynthesis.getVoices()) { + if (voice.voiceURI === voiceUri) { + return voice; + } + } + } catch (e) { + // NOP + } + return null; + } + + _getTextToSpeechParameters(uri) { + const m = /^tts:[^#?]*\?([^#]*)/.exec(uri); + if (m === null) { return null; } + + const searchParameters = new URLSearchParams(m[1]); + const text = searchParameters.get('text'); + const voiceUri = searchParameters.get('voice'); + return (text !== null && voiceUri !== null ? {text, voiceUri} : null); + } + + _cacheCheck() { + const removeCount = this._cache.size - this._cacheSizeMaximum; + if (removeCount <= 0) { return; } + + const removeKeys = []; + for (const key of this._cache.keys()) { + removeKeys.push(key); + if (removeKeys.length >= removeCount) { break; } + } + + for (const key of removeKeys) { + this._cache.delete(key); + } + } +} diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js deleted file mode 100644 index 31c476b1..00000000 --- a/ext/mixed/js/audio.js +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2019-2020 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -class TextToSpeechAudio { - constructor(text, voice) { - this.text = text; - this.voice = voice; - this._utterance = null; - this._volume = 1; - } - - get currentTime() { - return 0; - } - set currentTime(value) { - // NOP - } - - get volume() { - return this._volume; - } - set volume(value) { - this._volume = value; - if (this._utterance !== null) { - this._utterance.volume = value; - } - } - - play() { - try { - if (this._utterance === null) { - this._utterance = new SpeechSynthesisUtterance(this.text || ''); - this._utterance.lang = 'ja-JP'; - this._utterance.volume = this._volume; - this._utterance.voice = this.voice; - } - - speechSynthesis.cancel(); - speechSynthesis.speak(this._utterance); - } catch (e) { - // NOP - } - } - - pause() { - try { - speechSynthesis.cancel(); - } catch (e) { - // NOP - } - } -} - -class AudioSystem { - constructor({getAudioUri}) { - this._cache = new Map(); - this._cacheSizeMaximum = 32; - this._getAudioUri = getAudioUri; - - if (typeof speechSynthesis !== 'undefined') { - // speechSynthesis.getVoices() will not be populated unless some API call is made. - speechSynthesis.addEventListener('voiceschanged', this._onVoicesChanged.bind(this)); - } - } - - async getDefinitionAudio(definition, sources, details) { - const key = `${definition.expression}:${definition.reading}`; - const cacheValue = this._cache.get(definition); - if (typeof cacheValue !== 'undefined') { - const {audio, uri, source} = cacheValue; - return {audio, uri, source}; - } - - for (const source of sources) { - const uri = await this._getAudioUri(definition, source, details); - if (uri === null) { continue; } - - try { - const audio = await this._createAudio(uri, details); - this._cacheCheck(); - this._cache.set(key, {audio, uri, source}); - return {audio, uri, source}; - } catch (e) { - // NOP - } - } - - throw new Error('Could not create audio'); - } - - createTextToSpeechAudio({text, voiceUri}) { - const voice = this._getTextToSpeechVoiceFromVoiceUri(voiceUri); - if (voice === null) { - throw new Error('Invalid text-to-speech voice'); - } - return new TextToSpeechAudio(text, voice); - } - - _onVoicesChanged() { - // NOP - } - - async _createAudio(uri, details) { - const ttsParameters = this._getTextToSpeechParameters(uri); - if (ttsParameters !== null) { - if (typeof details === 'object' && details !== null) { - if (details.tts === false) { - throw new Error('Text-to-speech not permitted'); - } - } - return this.createTextToSpeechAudio(ttsParameters); - } - - return await this._createAudioFromUrl(uri); - } - - _createAudioFromUrl(url) { - return new Promise((resolve, reject) => { - const audio = new Audio(url); - audio.addEventListener('loadeddata', () => { - const duration = audio.duration; - if (duration === 5.694694 || duration === 5.720718) { - // Hardcoded values for invalid audio - reject(new Error('Could not retrieve audio')); - } else { - resolve(audio); - } - }); - audio.addEventListener('error', () => reject(audio.error)); - }); - } - - _getTextToSpeechVoiceFromVoiceUri(voiceUri) { - try { - for (const voice of speechSynthesis.getVoices()) { - if (voice.voiceURI === voiceUri) { - return voice; - } - } - } catch (e) { - // NOP - } - return null; - } - - _getTextToSpeechParameters(uri) { - const m = /^tts:[^#?]*\?([^#]*)/.exec(uri); - if (m === null) { return null; } - - const searchParameters = new URLSearchParams(m[1]); - const text = searchParameters.get('text'); - const voiceUri = searchParameters.get('voice'); - return (text !== null && voiceUri !== null ? {text, voiceUri} : null); - } - - _cacheCheck() { - const removeCount = this._cache.size - this._cacheSizeMaximum; - if (removeCount <= 0) { return; } - - const removeKeys = []; - for (const key of this._cache.keys()) { - removeKeys.push(key); - if (removeKeys.length >= removeCount) { break; } - } - - for (const key of removeKeys) { - this._cache.delete(key); - } - } -} -- cgit v1.2.3 From 69cce49b0d5d9f11f4ffb529ae3d060536297c07 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 15:14:05 -0500 Subject: Move Anki note generation functionality into a new class --- ext/bg/background.html | 1 + ext/bg/js/anki-note-builder.js | 110 +++++++++++++++++++++++++++++++++++ ext/bg/js/backend.js | 9 +-- ext/bg/js/dictionary.js | 88 ---------------------------- ext/bg/js/settings/anki-templates.js | 8 ++- ext/bg/settings.html | 1 + 6 files changed, 122 insertions(+), 95 deletions(-) create mode 100644 ext/bg/js/anki-note-builder.js diff --git a/ext/bg/background.html b/ext/bg/background.html index f2f70d4d..8db017f1 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -22,6 +22,7 @@ + diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js new file mode 100644 index 00000000..f7555280 --- /dev/null +++ b/ext/bg/js/anki-note-builder.js @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/*global apiTemplateRender*/ + +class AnkiNoteBuilder { + constructor() { + this._markers = new Set([ + 'audio', + 'character', + 'cloze-body', + 'cloze-prefix', + 'cloze-suffix', + 'dictionary', + 'expression', + 'furigana', + 'furigana-plain', + 'glossary', + 'glossary-brief', + 'kunyomi', + 'onyomi', + 'reading', + 'screenshot', + 'sentence', + 'tags', + 'url' + ]); + } + + async createNote(definition, mode, options, templates) { + const isKanji = (mode === 'kanji'); + const tags = options.anki.tags; + const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; + const modeOptionsFieldEntries = Object.entries(modeOptions.fields); + + const note = { + fields: {}, + tags, + deckName: modeOptions.deck, + modelName: modeOptions.model + }; + + for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { + note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, options, templates, null); + } + + if (!isKanji && definition.audio) { + const audioFields = []; + + for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { + if (fieldValue.includes('{audio}')) { + audioFields.push(fieldName); + } + } + + if (audioFields.length > 0) { + note.audio = { + url: definition.audio.url, + filename: definition.audio.filename, + skipHash: '7e2c2f954ef6051373ba916f000168dc', // hash of audio data that should be skipped + fields: audioFields + }; + } + } + + return note; + } + + async formatField(field, definition, mode, options, templates, errors=null) { + const data = { + marker: null, + definition, + group: options.general.resultOutputMode === 'group', + merge: options.general.resultOutputMode === 'merge', + modeTermKanji: mode === 'term-kanji', + modeTermKana: mode === 'term-kana', + modeKanji: mode === 'kanji', + compactGlossaries: options.general.compactGlossaries + }; + const markers = this._markers; + const pattern = /\{([\w-]+)\}/g; + return await stringReplaceAsync(field, pattern, async (g0, marker) => { + if (!markers.has(marker)) { + return g0; + } + data.marker = marker; + try { + return await apiTemplateRender(templates, data); + } catch (e) { + if (errors) { errors.push(e); } + return `{${marker}-render-error}`; + } + }); + } +} diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 60a87916..929281da 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -20,10 +20,10 @@ conditionsTestValue, profileConditionsDescriptor handlebarsRenderDynamic requestText, requestJson, optionsLoad -dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat +dictConfigured, dictTermsSort, dictEnabledSet audioGetUrl, audioInject jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ +AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ class Backend { constructor() { @@ -31,6 +31,7 @@ class Backend { this.anki = new AnkiNull(); this.mecab = new Mecab(); this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); + this.ankiNoteBuilder = new AnkiNoteBuilder(); this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; @@ -450,7 +451,7 @@ class Backend { ); } - const note = await dictNoteFormat(definition, mode, options, templates); + const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates); return this.anki.addNote(note); } @@ -463,7 +464,7 @@ class Backend { const notes = []; for (const definition of definitions) { for (const mode of modes) { - const note = await dictNoteFormat(definition, mode, options, templates); + const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates); notes.push(note); } } diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index ffeac80a..3dd1d0c1 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -/*global apiTemplateRender*/ - function dictEnabledSet(options) { const enabledDictionaryMap = new Map(); for (const [title, {enabled, priority, allowSecondarySearches}] of Object.entries(options.dictionaries)) { @@ -333,89 +331,3 @@ function dictTagsSort(tags) { function dictFieldSplit(field) { return field.length === 0 ? [] : field.split(' '); } - -async function dictFieldFormat(field, definition, mode, options, templates, exceptions) { - const data = { - marker: null, - definition, - group: options.general.resultOutputMode === 'group', - merge: options.general.resultOutputMode === 'merge', - modeTermKanji: mode === 'term-kanji', - modeTermKana: mode === 'term-kana', - modeKanji: mode === 'kanji', - compactGlossaries: options.general.compactGlossaries - }; - const markers = dictFieldFormat.markers; - const pattern = /\{([\w-]+)\}/g; - return await stringReplaceAsync(field, pattern, async (g0, marker) => { - if (!markers.has(marker)) { - return g0; - } - data.marker = marker; - try { - return await apiTemplateRender(templates, data); - } catch (e) { - if (exceptions) { exceptions.push(e); } - return `{${marker}-render-error}`; - } - }); -} -dictFieldFormat.markers = new Set([ - 'audio', - 'character', - 'cloze-body', - 'cloze-prefix', - 'cloze-suffix', - 'dictionary', - 'expression', - 'furigana', - 'furigana-plain', - 'glossary', - 'glossary-brief', - 'kunyomi', - 'onyomi', - 'reading', - 'screenshot', - 'sentence', - 'tags', - 'url' -]); - -async function dictNoteFormat(definition, mode, options, templates) { - const isKanji = (mode === 'kanji'); - const tags = options.anki.tags; - const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; - const modeOptionsFieldEntries = Object.entries(modeOptions.fields); - - const note = { - fields: {}, - tags, - deckName: modeOptions.deck, - modelName: modeOptions.model - }; - - for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { - note.fields[fieldName] = await dictFieldFormat(fieldValue, definition, mode, options, templates); - } - - if (!isKanji && definition.audio) { - const audioFields = []; - - for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { - if (fieldValue.includes('{audio}')) { - audioFields.push(fieldName); - } - } - - if (audioFields.length > 0) { - note.audio = { - url: definition.audio.url, - filename: definition.audio.filename, - skipHash: '7e2c2f954ef6051373ba916f000168dc', - fields: audioFields - }; - } - } - - return note; -} diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 244ec42e..32a990f9 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -17,8 +17,9 @@ */ /*global getOptionsContext, getOptionsMutable, settingsSaveOptions -ankiGetFieldMarkers, ankiGetFieldMarkersHtml, dictFieldFormat -apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates*/ +ankiGetFieldMarkers, ankiGetFieldMarkersHtml +apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates, +AnkiNoteBuilder*/ function onAnkiFieldTemplatesReset(e) { e.preventDefault(); @@ -92,7 +93,8 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i const options = await apiOptionsGet(optionsContext); let templates = options.anki.fieldTemplates; if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); } - result = await dictFieldFormat(field, definition, mode, options, templates, exceptions); + const ankiNoteBuilder = new AnkiNoteBuilder(); + result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions); } } catch (e) { exceptions.push(e); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index e9fc6be5..0db76d71 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1090,6 +1090,7 @@ + -- cgit v1.2.3 From 7ac1c843a92cbefd0a625f06b5093217b585f7cf Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 15:20:45 -0500 Subject: Use dependency injection for apiTemplateRender --- ext/bg/js/anki-note-builder.js | 7 +++---- ext/bg/js/api.js | 4 ---- ext/bg/js/backend.js | 8 ++++++-- ext/bg/js/settings/anki-templates.js | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index f7555280..be39ff43 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -/*global apiTemplateRender*/ - class AnkiNoteBuilder { - constructor() { + constructor({renderTemplate}) { + this._renderTemplate = renderTemplate; this._markers = new Set([ 'audio', 'character', @@ -100,7 +99,7 @@ class AnkiNoteBuilder { } data.marker = marker; try { - return await apiTemplateRender(templates, data); + return await this._renderTemplate(templates, data); } catch (e) { if (errors) { errors.push(e); } return `{${marker}-render-error}`; diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 4e5d81db..9a05023e 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -17,10 +17,6 @@ */ -function apiTemplateRender(template, data) { - return _apiInvoke('templateRender', {data, template}); -} - function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 929281da..6e5235ed 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -31,7 +31,7 @@ class Backend { this.anki = new AnkiNull(); this.mecab = new Mecab(); this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); - this.ankiNoteBuilder = new AnkiNoteBuilder(); + this.ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: this._renderTemplate.bind(this)}); this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; @@ -507,7 +507,7 @@ class Backend { } async _onApiTemplateRender({template, data}) { - return handlebarsRenderDynamic(template, data); + return this._renderTemplate(template, data); } async _onApiCommandExec({command, params}) { @@ -811,6 +811,10 @@ class Backend { definition.screenshotFileName = filename; } + async _renderTemplate(template, data) { + return handlebarsRenderDynamic(template, data); + } + static _getTabUrl(tab) { return new Promise((resolve) => { chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 32a990f9..b1665048 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -18,7 +18,7 @@ /*global getOptionsContext, getOptionsMutable, settingsSaveOptions ankiGetFieldMarkers, ankiGetFieldMarkersHtml -apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates, +apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates, apiTemplateRender AnkiNoteBuilder*/ function onAnkiFieldTemplatesReset(e) { @@ -93,7 +93,7 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i const options = await apiOptionsGet(optionsContext); let templates = options.anki.fieldTemplates; if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); } - const ankiNoteBuilder = new AnkiNoteBuilder(); + const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: apiTemplateRender}); result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions); } } catch (e) { -- cgit v1.2.3 From 8f9b6534c64dd871db92729714b46d400a52e30e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 15:23:32 -0500 Subject: Move stringReplaceAsync It is only used in AnkiNoteBuilder and it was originally created for this purpose. --- .eslintrc.json | 1 - ext/bg/js/anki-note-builder.js | 17 ++++++++++++++++- ext/mixed/js/core.js | 15 --------------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 4ee1f982..2730acb5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -86,7 +86,6 @@ "toIterable": "readonly", "stringReverse": "readonly", "promiseTimeout": "readonly", - "stringReplaceAsync": "readonly", "parseUrl": "readonly", "EventDispatcher": "readonly", "EventListenerCollection": "readonly", diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index be39ff43..2a15b20d 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -93,7 +93,7 @@ class AnkiNoteBuilder { }; const markers = this._markers; const pattern = /\{([\w-]+)\}/g; - return await stringReplaceAsync(field, pattern, async (g0, marker) => { + return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => { if (!markers.has(marker)) { return g0; } @@ -106,4 +106,19 @@ class AnkiNoteBuilder { } }); } + + static stringReplaceAsync(str, regex, replacer) { + let match; + let index = 0; + const parts = []; + while ((match = regex.exec(str)) !== null) { + parts.push(str.substring(index, match.index), replacer(...match, match.index, str)); + index = regex.lastIndex; + } + if (parts.length === 0) { + return Promise.resolve(str); + } + parts.push(str.substring(index)); + return Promise.all(parts).then((v) => v.join('')); + } } diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 0e22b9ac..0d50e915 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -175,21 +175,6 @@ function promiseTimeout(delay, resolveValue) { return promise; } -function stringReplaceAsync(str, regex, replacer) { - let match; - let index = 0; - const parts = []; - while ((match = regex.exec(str)) !== null) { - parts.push(str.substring(index, match.index), replacer(...match, match.index, str)); - index = regex.lastIndex; - } - if (parts.length === 0) { - return Promise.resolve(str); - } - parts.push(str.substring(index)); - return Promise.all(parts).then((v) => v.join('')); -} - /* * Common events -- cgit v1.2.3 From 79eb4bdc167409cda6cebf3c0939189ab4cbbaa0 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 21:49:04 -0500 Subject: Remove bg/js/api.js --- ext/bg/background.html | 1 - ext/bg/js/api.js | 43 ------------------------------------------- 2 files changed, 44 deletions(-) delete mode 100644 ext/bg/js/api.js diff --git a/ext/bg/background.html b/ext/bg/background.html index 8db017f1..f6e00bf5 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -23,7 +23,6 @@ - diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js deleted file mode 100644 index 9a05023e..00000000 --- a/ext/bg/js/api.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2019-2020 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -function _apiInvoke(action, params={}) { - const data = {action, params}; - return new Promise((resolve, reject) => { - try { - const callback = (response) => { - if (response !== null && typeof response === 'object') { - if (typeof response.error !== 'undefined') { - reject(jsonToError(response.error)); - } else { - resolve(response.result); - } - } else { - const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`; - reject(new Error(`${message} (${JSON.stringify(data)})`)); - } - }; - const backend = window.yomichanBackend; - backend.onMessage({action, params}, null, callback); - } catch (e) { - reject(e); - yomichan.triggerOrphaned(e); - } - }); -} -- cgit v1.2.3 From 426c1534e7d740fa2c30488a64ad4fa6a382deed Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 21:51:56 -0500 Subject: Remove marker restrictions --- ext/bg/js/anki-note-builder.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index 2a15b20d..d0ff8205 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -19,26 +19,6 @@ class AnkiNoteBuilder { constructor({renderTemplate}) { this._renderTemplate = renderTemplate; - this._markers = new Set([ - 'audio', - 'character', - 'cloze-body', - 'cloze-prefix', - 'cloze-suffix', - 'dictionary', - 'expression', - 'furigana', - 'furigana-plain', - 'glossary', - 'glossary-brief', - 'kunyomi', - 'onyomi', - 'reading', - 'screenshot', - 'sentence', - 'tags', - 'url' - ]); } async createNote(definition, mode, options, templates) { @@ -91,12 +71,8 @@ class AnkiNoteBuilder { modeKanji: mode === 'kanji', compactGlossaries: options.general.compactGlossaries }; - const markers = this._markers; const pattern = /\{([\w-]+)\}/g; return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => { - if (!markers.has(marker)) { - return g0; - } data.marker = marker; try { return await this._renderTemplate(templates, data); -- cgit v1.2.3 From 21d194d14510abb149d22c8cbd56570cd6b62266 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:25:25 -0500 Subject: Make _audioInject internal to Backend --- ext/bg/js/audio.js | 44 -------------------------------------------- ext/bg/js/backend.js | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index c94121ae..361a19cc 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -138,47 +138,3 @@ function audioUrlNormalize(url, baseUrl, basePath) { } return url; } - -function audioBuildFilename(definition) { - if (definition.reading || definition.expression) { - let filename = 'yomichan'; - if (definition.reading) { - filename += `_${definition.reading}`; - } - if (definition.expression) { - filename += `_${definition.expression}`; - } - - return filename += '.mp3'; - } - return null; -} - -async function audioInject(definition, fields, sources, optionsContext, audioSystem) { - let usesAudio = false; - for (const fieldValue of Object.values(fields)) { - if (fieldValue.includes('{audio}')) { - usesAudio = true; - break; - } - } - - if (!usesAudio) { - return true; - } - - try { - const expressions = definition.expressions; - const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - - const {uri} = await audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); - const filename = audioBuildFilename(audioSourceDefinition); - if (filename !== null) { - definition.audio = {url: uri, filename}; - } - - return true; - } catch (e) { - return false; - } -} diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6e5235ed..1fdc4c70 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -21,7 +21,7 @@ conditionsTestValue, profileConditionsDescriptor handlebarsRenderDynamic requestText, requestJson, optionsLoad dictConfigured, dictTermsSort, dictEnabledSet -audioGetUrl, audioInject +audioGetUrl jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ @@ -434,12 +434,11 @@ class Backend { const templates = this.defaultAnkiFieldTemplates; if (mode !== 'kanji') { - await audioInject( + await this._audioInject( definition, options.anki.terms.fields, options.audio.sources, - optionsContext, - this.audioSystem + optionsContext ); } @@ -775,6 +774,35 @@ class Backend { return await audioGetUrl(definition, source, options); } + async _audioInject(definition, fields, sources, optionsContext) { + let usesAudio = false; + for (const fieldValue of Object.values(fields)) { + if (fieldValue.includes('{audio}')) { + usesAudio = true; + break; + } + } + + if (!usesAudio) { + return true; + } + + try { + const expressions = definition.expressions; + const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; + + const {uri} = await this.audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); + const filename = this._createInjectedAudioFileName(audioSourceDefinition); + if (filename !== null) { + definition.audio = {url: uri, filename}; + } + + return true; + } catch (e) { + return false; + } + } + async _injectScreenshot(definition, fields, screenshot) { let usesScreenshot = false; for (const fieldValue of Object.values(fields)) { @@ -815,6 +843,17 @@ class Backend { return handlebarsRenderDynamic(template, data); } + _createInjectedAudioFileName(definition) { + const {reading, expression} = definition; + if (!reading && !expression) { return null; } + + let filename = 'yomichan'; + if (reading) { filename += `_${reading}`; } + if (expression) { filename += `_${expression}`; } + filename += '.mp3'; + return filename; + } + static _getTabUrl(tab) { return new Promise((resolve) => { chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { -- cgit v1.2.3 From 391f3dd29af2017b540b38e67a06242af85268ba Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:36:16 -0500 Subject: Update how audio URIs are built --- ext/bg/js/audio.js | 104 +++++++++++++++++++++++++++++---------------------- ext/bg/js/backend.js | 8 ++-- 2 files changed, 64 insertions(+), 48 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 361a19cc..80e9cb9a 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -18,8 +18,49 @@ /*global jpIsStringEntirelyKana*/ -const audioUrlBuilders = new Map([ - ['jpod101', async (definition) => { +class AudioUriBuilder { + constructor() { + this._getUrlHandlers = new Map([ + ['jpod101', this._getUriJpod101.bind(this)], + ['jpod101-alternate', this._getUriJpod101Alternate.bind(this)], + ['jisho', this._getUriJisho.bind(this)], + ['text-to-speech', this._getUriTextToSpeech.bind(this)], + ['text-to-speech-reading', this._getUriTextToSpeechReading.bind(this)], + ['custom', this._getUriCustom.bind(this)] + ]); + } + + normalizeUrl(url, baseUrl, basePath) { + if (url) { + if (url[0] === '/') { + if (url.length >= 2 && url[1] === '/') { + // Begins with "//" + url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url; + } else { + // Begins with "/" + url = baseUrl + url; + } + } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) { + // No URI scheme => relative path + url = baseUrl + basePath + url; + } + } + return url; + } + + async getUri(mode, definition, options) { + const handler = this._getUrlHandlers.get(mode); + if (typeof handler === 'function') { + try { + return await handler(definition, options); + } catch (e) { + // NOP + } + } + return null; + } + + async _getUriJpod101(definition) { let kana = definition.reading; let kanji = definition.expression; @@ -37,8 +78,9 @@ const audioUrlBuilders = new Map([ } return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; - }], - ['jpod101-alternate', async (definition) => { + } + + async _getUriJpod101Alternate(definition) { const response = await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); @@ -54,7 +96,7 @@ const audioUrlBuilders = new Map([ const url = row.querySelector('audio>source[src]').getAttribute('src'); const reading = row.getElementsByClassName('dc-vocab_kana').item(0).textContent; if (url && reading && (!definition.reading || definition.reading === reading)) { - return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); + return this.normalizeUrl(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); } } catch (e) { // NOP @@ -62,8 +104,9 @@ const audioUrlBuilders = new Map([ } throw new Error('Failed to find audio URL'); - }], - ['jisho', async (definition) => { + } + + async _getUriJisho(definition) { const response = await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', `https://jisho.org/search/${definition.expression}`); @@ -78,7 +121,7 @@ const audioUrlBuilders = new Map([ if (audio !== null) { const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); if (url) { - return audioUrlNormalize(url, 'https://jisho.org', '/search/'); + return this.normalizeUrl(url, 'https://jisho.org', '/search/'); } } } catch (e) { @@ -86,55 +129,28 @@ const audioUrlBuilders = new Map([ } throw new Error('Failed to find audio URL'); - }], - ['text-to-speech', async (definition, options) => { + } + + async _getUriTextToSpeech(definition, options) { const voiceURI = options.audio.textToSpeechVoice; if (!voiceURI) { throw new Error('No voice'); } return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; - }], - ['text-to-speech-reading', async (definition, options) => { + } + + async _getUriTextToSpeechReading(definition, options) { const voiceURI = options.audio.textToSpeechVoice; if (!voiceURI) { throw new Error('No voice'); } return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; - }], - ['custom', async (definition, options) => { - const customSourceUrl = options.audio.customSourceUrl; - return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); - }] -]); - -async function audioGetUrl(definition, mode, options, download) { - const handler = audioUrlBuilders.get(mode); - if (typeof handler === 'function') { - try { - return await handler(definition, options, download); - } catch (e) { - // NOP - } } - return null; -} -function audioUrlNormalize(url, baseUrl, basePath) { - if (url) { - if (url[0] === '/') { - if (url.length >= 2 && url[1] === '/') { - // Begins with "//" - url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url; - } else { - // Begins with "/" - url = baseUrl + url; - } - } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) { - // No URI scheme => relative path - url = baseUrl + basePath + url; - } + async _getUriCustom(definition, options) { + const customSourceUrl = options.audio.customSourceUrl; + return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); } - return url; } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 1fdc4c70..66378b0c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -21,9 +21,8 @@ conditionsTestValue, profileConditionsDescriptor handlebarsRenderDynamic requestText, requestJson, optionsLoad dictConfigured, dictTermsSort, dictEnabledSet -audioGetUrl jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ +AnkiNoteBuilder, AudioSystem, AudioUriBuilder, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ class Backend { constructor() { @@ -36,6 +35,7 @@ class Backend { this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); + this.audioUriBuilder = new AudioUriBuilder(); this.optionsContext = { depth: 0, url: window.location.href @@ -515,7 +515,7 @@ class Backend { async _onApiAudioGetUrl({definition, source, optionsContext}) { const options = this.getOptions(optionsContext); - return await audioGetUrl(definition, source, options); + return await this.audioUriBuilder.getUri(source, definition, options); } _onApiScreenshotGet({options}, sender) { @@ -771,7 +771,7 @@ class Backend { } const options = this.getOptions(optionsContext); - return await audioGetUrl(definition, source, options); + return await this.audioUriBuilder.getUri(source, definition, options); } async _audioInject(definition, fields, sources, optionsContext) { -- cgit v1.2.3 From aad4ab5eccaeed14514d676c0de4f3e2db718072 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:37:44 -0500 Subject: Rename audio functions using "url" to use "uri" --- ext/bg/js/backend.js | 4 ++-- ext/bg/js/settings/audio.js | 4 ++-- ext/mixed/js/api.js | 4 ++-- ext/mixed/js/display.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 66378b0c..eb88a6c1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -67,7 +67,7 @@ class Backend { ['noteView', this._onApiNoteView.bind(this)], ['templateRender', this._onApiTemplateRender.bind(this)], ['commandExec', this._onApiCommandExec.bind(this)], - ['audioGetUrl', this._onApiAudioGetUrl.bind(this)], + ['audioGetUri', this._onApiAudioGetUri.bind(this)], ['screenshotGet', this._onApiScreenshotGet.bind(this)], ['forward', this._onApiForward.bind(this)], ['frameInformationGet', this._onApiFrameInformationGet.bind(this)], @@ -513,7 +513,7 @@ class Backend { return this._runCommand(command, params); } - async _onApiAudioGetUrl({definition, source, optionsContext}) { + async _onApiAudioGetUri({definition, source, optionsContext}) { const options = this.getOptions(optionsContext); return await this.audioUriBuilder.getUri(source, definition, options); } diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 6f581d9b..c825be6b 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, settingsSaveOptions, apiAudioGetUrl +/*global getOptionsContext, getOptionsMutable, settingsSaveOptions, apiAudioGetUri AudioSystem, AudioSourceUI*/ let audioSourceUI = null; @@ -26,7 +26,7 @@ async function audioSettingsInitialize() { audioSystem = new AudioSystem({ getAudioUri: async (definition, source) => { const optionsContext = getOptionsContext(); - return await apiAudioGetUrl(definition, source, optionsContext); + return await apiAudioGetUri(definition, source, optionsContext); } }); diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 26f4389d..0ab07039 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -69,8 +69,8 @@ function apiTemplateRender(template, data) { return _apiInvoke('templateRender', {data, template}); } -function apiAudioGetUrl(definition, source, optionsContext) { - return _apiInvoke('audioGetUrl', {definition, source, optionsContext}); +function apiAudioGetUri(definition, source, optionsContext) { + return _apiInvoke('audioGetUri', {definition, source, optionsContext}); } function apiCommandExec(command, params) { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 3fe8e684..a220c1f7 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -18,7 +18,7 @@ /*global docRangeFromPoint, docSentenceExtract apiKanjiFind, apiTermsFind, apiNoteView, apiOptionsGet, apiDefinitionsAddable, apiDefinitionAdd -apiScreenshotGet, apiForward, apiAudioGetUrl +apiScreenshotGet, apiForward, apiAudioGetUri AudioSystem, DisplayGenerator, WindowScroll, DisplayContext, DOM*/ class Display { @@ -919,6 +919,6 @@ class Display { async _getAudioUri(definition, source) { const optionsContext = this.getOptionsContext(); - return await apiAudioGetUrl(definition, source, optionsContext); + return await apiAudioGetUri(definition, source, optionsContext); } } -- cgit v1.2.3 From 9cd4a52b9e701f47c7ca7b44c52cbcd66b7bcb05 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 7 Mar 2020 14:39:25 -0500 Subject: Rename audio.js to audio-uri-builder.js --- ext/bg/background.html | 2 +- ext/bg/js/audio-uri-builder.js | 156 +++++++++++++++++++++++++++++++++++++++++ ext/bg/js/audio.js | 156 ----------------------------------------- 3 files changed, 157 insertions(+), 157 deletions(-) create mode 100644 ext/bg/js/audio-uri-builder.js delete mode 100644 ext/bg/js/audio.js diff --git a/ext/bg/background.html b/ext/bg/background.html index f6e00bf5..44abe8fd 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -24,7 +24,7 @@ - + diff --git a/ext/bg/js/audio-uri-builder.js b/ext/bg/js/audio-uri-builder.js new file mode 100644 index 00000000..80e9cb9a --- /dev/null +++ b/ext/bg/js/audio-uri-builder.js @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2017-2020 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/*global jpIsStringEntirelyKana*/ + +class AudioUriBuilder { + constructor() { + this._getUrlHandlers = new Map([ + ['jpod101', this._getUriJpod101.bind(this)], + ['jpod101-alternate', this._getUriJpod101Alternate.bind(this)], + ['jisho', this._getUriJisho.bind(this)], + ['text-to-speech', this._getUriTextToSpeech.bind(this)], + ['text-to-speech-reading', this._getUriTextToSpeechReading.bind(this)], + ['custom', this._getUriCustom.bind(this)] + ]); + } + + normalizeUrl(url, baseUrl, basePath) { + if (url) { + if (url[0] === '/') { + if (url.length >= 2 && url[1] === '/') { + // Begins with "//" + url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url; + } else { + // Begins with "/" + url = baseUrl + url; + } + } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) { + // No URI scheme => relative path + url = baseUrl + basePath + url; + } + } + return url; + } + + async getUri(mode, definition, options) { + const handler = this._getUrlHandlers.get(mode); + if (typeof handler === 'function') { + try { + return await handler(definition, options); + } catch (e) { + // NOP + } + } + return null; + } + + async _getUriJpod101(definition) { + let kana = definition.reading; + let kanji = definition.expression; + + if (!kana && jpIsStringEntirelyKana(kanji)) { + kana = kanji; + kanji = null; + } + + const params = []; + if (kanji) { + params.push(`kanji=${encodeURIComponent(kanji)}`); + } + if (kana) { + params.push(`kana=${encodeURIComponent(kana)}`); + } + + return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; + } + + async _getUriJpod101Alternate(definition) { + const response = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.send(`post=dictionary_reference&match_type=exact&search_query=${encodeURIComponent(definition.expression)}`); + }); + + const dom = new DOMParser().parseFromString(response, 'text/html'); + for (const row of dom.getElementsByClassName('dc-result-row')) { + try { + const url = row.querySelector('audio>source[src]').getAttribute('src'); + const reading = row.getElementsByClassName('dc-vocab_kana').item(0).textContent; + if (url && reading && (!definition.reading || definition.reading === reading)) { + return this.normalizeUrl(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); + } + } catch (e) { + // NOP + } + } + + throw new Error('Failed to find audio URL'); + } + + async _getUriJisho(definition) { + const response = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', `https://jisho.org/search/${definition.expression}`); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.send(); + }); + + const dom = new DOMParser().parseFromString(response, 'text/html'); + try { + const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); + if (audio !== null) { + const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); + if (url) { + return this.normalizeUrl(url, 'https://jisho.org', '/search/'); + } + } + } catch (e) { + // NOP + } + + throw new Error('Failed to find audio URL'); + } + + async _getUriTextToSpeech(definition, options) { + const voiceURI = options.audio.textToSpeechVoice; + if (!voiceURI) { + throw new Error('No voice'); + } + + return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + } + + async _getUriTextToSpeechReading(definition, options) { + const voiceURI = options.audio.textToSpeechVoice; + if (!voiceURI) { + throw new Error('No voice'); + } + + return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + } + + async _getUriCustom(definition, options) { + const customSourceUrl = options.audio.customSourceUrl; + return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); + } +} diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js deleted file mode 100644 index 80e9cb9a..00000000 --- a/ext/bg/js/audio.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2017-2020 Alex Yatskov - * Author: Alex Yatskov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/*global jpIsStringEntirelyKana*/ - -class AudioUriBuilder { - constructor() { - this._getUrlHandlers = new Map([ - ['jpod101', this._getUriJpod101.bind(this)], - ['jpod101-alternate', this._getUriJpod101Alternate.bind(this)], - ['jisho', this._getUriJisho.bind(this)], - ['text-to-speech', this._getUriTextToSpeech.bind(this)], - ['text-to-speech-reading', this._getUriTextToSpeechReading.bind(this)], - ['custom', this._getUriCustom.bind(this)] - ]); - } - - normalizeUrl(url, baseUrl, basePath) { - if (url) { - if (url[0] === '/') { - if (url.length >= 2 && url[1] === '/') { - // Begins with "//" - url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url; - } else { - // Begins with "/" - url = baseUrl + url; - } - } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) { - // No URI scheme => relative path - url = baseUrl + basePath + url; - } - } - return url; - } - - async getUri(mode, definition, options) { - const handler = this._getUrlHandlers.get(mode); - if (typeof handler === 'function') { - try { - return await handler(definition, options); - } catch (e) { - // NOP - } - } - return null; - } - - async _getUriJpod101(definition) { - let kana = definition.reading; - let kanji = definition.expression; - - if (!kana && jpIsStringEntirelyKana(kanji)) { - kana = kanji; - kanji = null; - } - - const params = []; - if (kanji) { - params.push(`kanji=${encodeURIComponent(kanji)}`); - } - if (kana) { - params.push(`kana=${encodeURIComponent(kana)}`); - } - - return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; - } - - async _getUriJpod101Alternate(definition) { - const response = await new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); - xhr.addEventListener('load', () => resolve(xhr.responseText)); - xhr.send(`post=dictionary_reference&match_type=exact&search_query=${encodeURIComponent(definition.expression)}`); - }); - - const dom = new DOMParser().parseFromString(response, 'text/html'); - for (const row of dom.getElementsByClassName('dc-result-row')) { - try { - const url = row.querySelector('audio>source[src]').getAttribute('src'); - const reading = row.getElementsByClassName('dc-vocab_kana').item(0).textContent; - if (url && reading && (!definition.reading || definition.reading === reading)) { - return this.normalizeUrl(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); - } - } catch (e) { - // NOP - } - } - - throw new Error('Failed to find audio URL'); - } - - async _getUriJisho(definition) { - const response = await new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', `https://jisho.org/search/${definition.expression}`); - xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); - xhr.addEventListener('load', () => resolve(xhr.responseText)); - xhr.send(); - }); - - const dom = new DOMParser().parseFromString(response, 'text/html'); - try { - const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); - if (audio !== null) { - const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); - if (url) { - return this.normalizeUrl(url, 'https://jisho.org', '/search/'); - } - } - } catch (e) { - // NOP - } - - throw new Error('Failed to find audio URL'); - } - - async _getUriTextToSpeech(definition, options) { - const voiceURI = options.audio.textToSpeechVoice; - if (!voiceURI) { - throw new Error('No voice'); - } - - return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; - } - - async _getUriTextToSpeechReading(definition, options) { - const voiceURI = options.audio.textToSpeechVoice; - if (!voiceURI) { - throw new Error('No voice'); - } - - return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; - } - - async _getUriCustom(definition, options) { - const customSourceUrl = options.audio.customSourceUrl; - return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); - } -} -- cgit v1.2.3 From e2bf22831ab9aa26a6f30ff75c584202f79dc523 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 9 Mar 2020 02:10:28 +0200 Subject: start popup depth from 1 on search page --- ext/fg/js/frontend-initialize.js | 2 +- ext/fg/js/popup-proxy-host.js | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js index bbb789cc..e674724e 100644 --- a/ext/fg/js/frontend-initialize.js +++ b/ext/fg/js/frontend-initialize.js @@ -31,7 +31,7 @@ async function main() { const popupHost = new PopupProxyHost(); await popupHost.prepare(); - popup = popupHost.getOrCreatePopup(); + popup = popupHost.getOrCreatePopup(null, null, depth); } const frontend = new Frontend(popup, ignoreNodes); diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 7d86aa67..fdb54cdd 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -47,7 +47,7 @@ class PopupProxyHost { ])); } - getOrCreatePopup(id=null, parentId=null) { + getOrCreatePopup(id=null, parentId=null, depth=0) { // Find by existing id if (id !== null) { const popup = this._popups.get(id); @@ -76,7 +76,12 @@ class PopupProxyHost { } // Create new popup - const depth = (parent !== null ? parent.depth + 1 : 0); + if (parent !== null) { + if (depth !== 0) { + throw new Error('Depth cannot be set when parent exists'); + } + depth = parent.depth + 1; + } const popup = new Popup(id, depth, this._frameIdPromise); if (parent !== null) { popup.setParent(parent); -- cgit v1.2.3 From 9069bcb3fe683d8422061c9f9218213a0b252dd2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 8 Mar 2020 21:30:37 -0400 Subject: Remove duplicate file --- test/test-stylesheet.css | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 test/test-stylesheet.css diff --git a/test/test-stylesheet.css b/test/test-stylesheet.css deleted file mode 100644 index ab25732e..00000000 --- a/test/test-stylesheet.css +++ /dev/null @@ -1,32 +0,0 @@ -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - max-width: 680px; - padding: 0 1em; - box-sizing: border-box; - margin: 0 auto; - background-color: #f8f8f8; - counter-reset: test-id; -} - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -.test { - background-color: #ffffff; - margin: 1em 0; - padding: 0.5em; - box-shadow: rgba(64, 64, 64, 0.3) 0px 1px 2px 0px, rgba(64, 64, 64, 0.15) 0px 1px 3px 1px; - border-radius: 4px; -} - -.test:before { - content: "Test " counter(test-id); - display: block; - counter-increment: test-id; - margin-bottom: 0.5em; - border-bottom: 1px solid #d8d8d8; - font-weight: bold; -} -- cgit v1.2.3 From 0cd1f8f20a1b632847e14b280849e4af1b6dd9ad Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 8 Mar 2020 21:31:18 -0400 Subject: Add missing copyright comment --- test/test-document.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test-document.js b/test/test-document.js index ab5f5716..80b9719d 100644 --- a/test/test-document.js +++ b/test/test-document.js @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + const fs = require('fs'); const path = require('path'); const assert = require('assert'); -- cgit v1.2.3 From 0112dbab33ab214f9e1dc930558833956d4ad1c4 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 9 Mar 2020 04:06:31 +0200 Subject: fix searchQueryUpdate --- ext/bg/js/backend.js | 2 +- ext/bg/js/search.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6e5235ed..d0d53a36 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -695,7 +695,7 @@ class Backend { await Backend._focusTab(tab); if (queryParams.query) { await new Promise((resolve) => chrome.tabs.sendMessage( - tab.id, {action: 'searchQueryUpdate', params: {query: queryParams.query}}, resolve + tab.id, {action: 'searchQueryUpdate', params: {text: queryParams.query}}, resolve )); } return true; diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index f9481ea2..5881f6f8 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -56,7 +56,7 @@ class DisplaySearch extends Display { ]); this._runtimeMessageHandlers = new Map([ - ['searchQueryUpdate', ({query}) => { this.onExternalSearchUpdate(query); }] + ['searchQueryUpdate', this.onExternalSearchUpdate.bind(this)] ]); } -- cgit v1.2.3 From 2ca88b9b9f2a6152e8e953cac284f31c8a285446 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 9 Mar 2020 11:56:06 +0200 Subject: strict check if popup depth has been set --- ext/fg/js/popup-proxy-host.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index fdb54cdd..49123ee1 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -47,7 +47,7 @@ class PopupProxyHost { ])); } - getOrCreatePopup(id=null, parentId=null, depth=0) { + getOrCreatePopup(id=null, parentId=null, depth=null) { // Find by existing id if (id !== null) { const popup = this._popups.get(id); @@ -77,10 +77,12 @@ class PopupProxyHost { // Create new popup if (parent !== null) { - if (depth !== 0) { + if (depth !== null) { throw new Error('Depth cannot be set when parent exists'); } depth = parent.depth + 1; + } else if (depth === null) { + depth = 0; } const popup = new Popup(id, depth, this._frameIdPromise); if (parent !== null) { -- cgit v1.2.3 From 0cbf427ab50061b48c9027e63e9ee8a209946d37 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 9 Mar 2020 21:00:57 -0400 Subject: Update argument order --- ext/bg/js/audio-uri-builder.js | 4 ++-- ext/bg/js/backend.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/bg/js/audio-uri-builder.js b/ext/bg/js/audio-uri-builder.js index 80e9cb9a..15cea995 100644 --- a/ext/bg/js/audio-uri-builder.js +++ b/ext/bg/js/audio-uri-builder.js @@ -48,8 +48,8 @@ class AudioUriBuilder { return url; } - async getUri(mode, definition, options) { - const handler = this._getUrlHandlers.get(mode); + async getUri(definition, source, options) { + const handler = this._getUrlHandlers.get(source); if (typeof handler === 'function') { try { return await handler(definition, options); diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index eb88a6c1..adc6f13d 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -515,7 +515,7 @@ class Backend { async _onApiAudioGetUri({definition, source, optionsContext}) { const options = this.getOptions(optionsContext); - return await this.audioUriBuilder.getUri(source, definition, options); + return await this.audioUriBuilder.getUri(definition, source, options); } _onApiScreenshotGet({options}, sender) { @@ -771,7 +771,7 @@ class Backend { } const options = this.getOptions(optionsContext); - return await this.audioUriBuilder.getUri(source, definition, options); + return await this.audioUriBuilder.getUri(definition, source, options); } async _audioInject(definition, fields, sources, optionsContext) { -- cgit v1.2.3 From 72219ba3530f9da02d3ec1d704baac6f88863682 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 9 Mar 2020 21:34:31 -0400 Subject: Replace charCodeAt and fromCharCode with codePointAt and fromCodePoint --- ext/bg/js/handlebars.js | 4 ++-- ext/bg/js/japanese.js | 49 ++++++++++++++++++++------------------- ext/bg/js/translator.js | 11 +++++---- ext/mixed/js/display-generator.js | 2 +- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js index b1443447..3ee4e7fa 100644 --- a/ext/bg/js/handlebars.js +++ b/ext/bg/js/handlebars.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global jpIsCharCodeKanji, jpDistributeFurigana, Handlebars*/ +/*global jpIsCodePointKanji, jpDistributeFurigana, Handlebars*/ function handlebarsEscape(text) { return Handlebars.Utils.escapeExpression(text); @@ -62,7 +62,7 @@ function handlebarsFuriganaPlain(options) { function handlebarsKanjiLinks(options) { let result = ''; for (const c of options.fn(this)) { - if (jpIsCharCodeKanji(c.charCodeAt(0))) { + if (jpIsCodePointKanji(c.codePointAt(0))) { result += `${c}`; } else { result += c; diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js index abb32da4..fc69dbba 100644 --- a/ext/bg/js/japanese.js +++ b/ext/bg/js/japanese.js @@ -115,9 +115,9 @@ const JP_JAPANESE_RANGES = [ // Helper functions -function _jpIsCharCodeInRanges(charCode, ranges) { +function _jpIsCodePointInRanges(codePoint, ranges) { for (const [min, max] of ranges) { - if (charCode >= min && charCode <= max) { + if (codePoint >= min && codePoint <= max) { return true; } } @@ -127,16 +127,16 @@ function _jpIsCharCodeInRanges(charCode, ranges) { // Character code testing functions -function jpIsCharCodeKanji(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_CJK_RANGES); +function jpIsCodePointKanji(codePoint) { + return _jpIsCodePointInRanges(codePoint, JP_CJK_RANGES); } -function jpIsCharCodeKana(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_KANA_RANGES); +function jpIsCodePointKana(codePoint) { + return _jpIsCodePointInRanges(codePoint, JP_KANA_RANGES); } -function jpIsCharCodeJapanese(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_JAPANESE_RANGES); +function jpIsCodePointJapanese(codePoint) { + return _jpIsCodePointInRanges(codePoint, JP_JAPANESE_RANGES); } @@ -144,8 +144,8 @@ function jpIsCharCodeJapanese(charCode) { function jpIsStringEntirelyKana(str) { if (str.length === 0) { return false; } - for (let i = 0, ii = str.length; i < ii; ++i) { - if (!jpIsCharCodeKana(str.charCodeAt(i))) { + for (const c of str) { + if (!jpIsCodePointKana(c.codePointAt(0))) { return false; } } @@ -154,8 +154,8 @@ function jpIsStringEntirelyKana(str) { function jpIsStringPartiallyJapanese(str) { if (str.length === 0) { return false; } - for (let i = 0, ii = str.length; i < ii; ++i) { - if (jpIsCharCodeJapanese(str.charCodeAt(i))) { + for (const c of str) { + if (jpIsCodePointJapanese(c.codePointAt(0))) { return true; } } @@ -264,8 +264,8 @@ function jpDistributeFurigana(expression, reading) { const groups = []; let modePrev = null; for (const c of expression) { - const charCode = c.charCodeAt(0); - const modeCurr = jpIsCharCodeKanji(charCode) || charCode === JP_ITERATION_MARK_CHAR_CODE ? 'kanji' : 'kana'; + const codePoint = c.codePointAt(0); + const modeCurr = jpIsCodePointKanji(codePoint) || codePoint === JP_ITERATION_MARK_CHAR_CODE ? 'kanji' : 'kana'; if (modeCurr === modePrev) { groups[groups.length - 1].text += c; } else { @@ -311,10 +311,11 @@ function jpDistributeFuriganaInflected(expression, reading, source) { function jpConvertHalfWidthKanaToFullWidth(text, sourceMapping) { let result = ''; - const ii = text.length; const hasSourceMapping = Array.isArray(sourceMapping); - for (let i = 0; i < ii; ++i) { + // This function is safe to use charCodeAt instead of codePointAt, since all + // the relevant characters are represented with a single UTF-16 character code. + for (let i = 0, ii = text.length; i < ii; ++i) { const c = text[i]; const mapping = JP_HALFWIDTH_KATAKANA_MAPPING.get(c); if (typeof mapping !== 'string') { @@ -355,13 +356,13 @@ function jpConvertHalfWidthKanaToFullWidth(text, sourceMapping) { function jpConvertNumericTofullWidth(text) { let result = ''; - for (let i = 0, ii = text.length; i < ii; ++i) { - let c = text.charCodeAt(i); + for (const char of text) { + let c = char.codePointAt(0); if (c >= 0x30 && c <= 0x39) { // ['0', '9'] c += 0xff10 - 0x30; // 0xff10 = '0' full width - result += String.fromCharCode(c); + result += String.fromCodePoint(c); } else { - result += text[i]; + result += char; } } return result; @@ -377,9 +378,9 @@ function jpConvertAlphabeticToKana(text, sourceMapping) { sourceMapping.fill(1); } - for (let i = 0; i < ii; ++i) { + for (const char of text) { // Note: 0x61 is the character code for 'a' - let c = text.charCodeAt(i); + let c = char.codePointAt(0); if (c >= 0x41 && c <= 0x5a) { // ['A', 'Z'] c += (0x61 - 0x41); } else if (c >= 0x61 && c <= 0x7a) { // ['a', 'z'] @@ -395,10 +396,10 @@ function jpConvertAlphabeticToKana(text, sourceMapping) { result += jpToHiragana(part, sourceMapping, result.length); part = ''; } - result += text[i]; + result += char; continue; } - part += String.fromCharCode(c); + part += String.fromCodePoint(c); } if (part.length > 0) { diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 781eb968..c01a7124 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -20,7 +20,7 @@ dictTermsMergeBySequence, dictTagBuildSource, dictTermsMergeByGloss, dictTermsSort, dictTagsSort dictEnabledSet, dictTermsGroup, dictTermsCompressTags, dictTermsUndupe, dictTagSanitize jpDistributeFurigana, jpConvertHalfWidthKanaToFullWidth, jpConvertNumericTofullWidth -jpConvertAlphabeticToKana, jpHiraganaToKatakana, jpKatakanaToHiragana, jpIsCharCodeJapanese +jpConvertAlphabeticToKana, jpHiraganaToKatakana, jpKatakanaToHiragana, jpIsCodePointJapanese Database, Deinflector*/ class Translator { @@ -621,13 +621,14 @@ class Translator { static getSearchableText(text, options) { if (!options.scanning.alphanumeric) { - const ii = text.length; - for (let i = 0; i < ii; ++i) { - if (!jpIsCharCodeJapanese(text.charCodeAt(i))) { - text = text.substring(0, i); + let newText = ''; + for (const c of text) { + if (!jpIsCodePointJapanese(c.codePointAt(0))) { break; } + newText += c; } + text = newText; } return text; diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index d7e77cc0..470e2a15 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -298,7 +298,7 @@ class DisplayGenerator { } static _isCharacterKanji(c) { - const code = c.charCodeAt(0); + const code = c.codePointAt(0); return ( code >= 0x4e00 && code < 0x9fb0 || code >= 0x3400 && code < 0x4dc0 -- cgit v1.2.3 From 5837d273f6a84dba9d64d67d14c580dcf1dd6992 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 10 Mar 2020 22:28:33 -0400 Subject: Add script to validate and fix global declaration format --- test/lint/global-declarations.js | 105 +++++++++++++++++++++++++++++++++++++++ test/yomichan-test.js | 21 ++++++++ 2 files changed, 126 insertions(+) create mode 100644 test/lint/global-declarations.js diff --git a/test/lint/global-declarations.js b/test/lint/global-declarations.js new file mode 100644 index 00000000..2629cc5e --- /dev/null +++ b/test/lint/global-declarations.js @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {getAllFiles} = require('../yomichan-test'); + + +function countOccurences(string, pattern) { + return (string.match(pattern) || []).length; +} + +function getNewline(string) { + const count1 = countOccurences(string, /(?:^|[^\r])\n/g); + const count2 = countOccurences(string, /\r\n/g); + const count3 = countOccurences(string, /\r(?:[^\n]|$)/g); + if (count2 > count1) { + return (count3 > count2) ? '\r' : '\r\n'; + } else { + return (count3 > count1) ? '\r' : '\n'; + } +} + + +function validateGlobals(fileName, fix) { + const pattern = /\/\*\s*global\s+([\w\W]*?)\*\//g; + const trimPattern = /^[\s,*]+|[\s,*]+$/g; + const splitPattern = /[\s,*]+/; + const source = fs.readFileSync(fileName, {encoding: 'utf8'}); + let match; + let first = true; + let endIndex = 0; + let newSource = ''; + const newline = getNewline(source); + while ((match = pattern.exec(source)) !== null) { + if (!first) { + console.error(`Encountered more than one global declaration in ${fileName}`); + return false; + } + first = false; + + const parts = match[1].replace(trimPattern, '').split(splitPattern); + parts.sort(); + + const actual = match[0]; + const expected = `/* global${parts.map((v) => `${newline} * ${v}`).join('')}${newline} */`; + + try { + assert.strictEqual(actual, expected); + } catch (e) { + console.error(`Global declaration error encountered in ${fileName}:`); + console.error(e.message); + if (!fix) { + return false; + } + } + + newSource += source.substring(0, match.index); + newSource += expected; + endIndex = match.index + match[0].length; + } + + newSource += source.substring(endIndex); + + if (fix) { + fs.writeFileSync(fileName, newSource, {encoding: 'utf8'}); + } + + return true; +} + + +function main() { + const fix = (process.argv.length >= 2 && process.argv[2] === '--fix'); + const directory = path.resolve(__dirname, '..', '..', 'ext'); + const pattern = /\.js$/; + const ignorePattern = /[\\/]ext[\\/]mixed[\\/]lib[\\/]/; + const fileNames = getAllFiles(directory, (f) => pattern.test(f) && !ignorePattern.test(f)); + for (const fileName of fileNames) { + if (!validateGlobals(fileName, fix)) { + process.exit(-1); + return; + } + } + process.exit(0); +} + + +if (require.main === module) { main(); } diff --git a/test/yomichan-test.js b/test/yomichan-test.js index 0fc97b4b..5fa7730b 100644 --- a/test/yomichan-test.js +++ b/test/yomichan-test.js @@ -50,8 +50,29 @@ function createTestDictionaryArchive(dictionary, dictionaryName) { return archive; } +function getAllFiles(baseDirectory, predicate=null) { + const results = []; + const directories = [path.resolve(baseDirectory)]; + while (directories.length > 0) { + const directory = directories.shift(); + for (const fileName of fs.readdirSync(directory)) { + const fullFileName = path.resolve(directory, fileName); + const stats = fs.statSync(fullFileName); + if (stats.isFile()) { + if (typeof predicate !== 'function' || predicate(fullFileName, directory, baseDirectory)) { + results.push(fullFileName); + } + } else if (stats.isDirectory()) { + directories.push(fullFileName); + } + } + } + return results; +} + module.exports = { createTestDictionaryArchive, + getAllFiles, get JSZip() { return getJSZip(); } }; -- cgit v1.2.3 From 64fc0349a17c16355491fac4fc6830b7e68a0e58 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 10 Mar 2020 22:30:36 -0400 Subject: Update global declarations --- .eslintrc.json | 2 +- ext/bg/js/anki.js | 4 +++- ext/bg/js/audio-uri-builder.js | 4 +++- ext/bg/js/backend.js | 33 +++++++++++++++++++++++------- ext/bg/js/clipboard-monitor.js | 4 +++- ext/bg/js/context.js | 6 +++++- ext/bg/js/database.js | 7 ++++++- ext/bg/js/handlebars.js | 6 +++++- ext/bg/js/japanese.js | 4 +++- ext/bg/js/options.js | 4 +++- ext/bg/js/search-frontend.js | 4 +++- ext/bg/js/search-query-parser-generator.js | 5 ++++- ext/bg/js/search-query-parser.js | 10 ++++++++- ext/bg/js/search.js | 9 +++++++- ext/bg/js/settings/anki-templates.js | 16 +++++++++++---- ext/bg/js/settings/anki.js | 13 +++++++++--- ext/bg/js/settings/audio.js | 10 +++++++-- ext/bg/js/settings/backup.js | 14 ++++++++++--- ext/bg/js/settings/conditions-ui.js | 4 +++- ext/bg/js/settings/dictionaries.js | 22 +++++++++++++++----- ext/bg/js/settings/main.js | 27 +++++++++++++++++------- ext/bg/js/settings/popup-preview-frame.js | 8 +++++++- ext/bg/js/settings/profiles.js | 14 ++++++++++--- ext/bg/js/settings/storage.js | 4 +++- ext/bg/js/translator.js | 28 +++++++++++++++++++------ ext/fg/js/document.js | 6 +++++- ext/fg/js/float.js | 7 ++++++- ext/fg/js/frontend-initialize.js | 6 +++++- ext/fg/js/frontend.js | 9 +++++++- ext/fg/js/popup-nested.js | 4 +++- ext/fg/js/popup-proxy-host.js | 6 +++++- ext/fg/js/popup-proxy.js | 4 +++- ext/fg/js/popup.js | 5 ++++- ext/mixed/js/display-generator.js | 5 ++++- ext/mixed/js/display.js | 22 ++++++++++++++++---- ext/mixed/js/text-scanner.js | 6 +++++- 36 files changed, 272 insertions(+), 70 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2730acb5..db8ff1fa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -63,7 +63,7 @@ "semi-spacing": ["error", {"before": false, "after": true}], "space-in-parens": ["error", "never"], "space-unary-ops": "error", - "spaced-comment": ["error", "always", {"markers": ["global"]}], + "spaced-comment": ["error", "always"], "switch-colon-spacing": ["error", {"after": true, "before": false}], "template-curly-spacing": ["error", "never"], "template-tag-spacing": ["error", "never"], diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 39c6ad51..a70388bd 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global requestJson*/ +/* global + * requestJson + */ /* * AnkiConnect diff --git a/ext/bg/js/audio-uri-builder.js b/ext/bg/js/audio-uri-builder.js index 15cea995..499c3441 100644 --- a/ext/bg/js/audio-uri-builder.js +++ b/ext/bg/js/audio-uri-builder.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global jpIsStringEntirelyKana*/ +/* global + * jpIsStringEntirelyKana + */ class AudioUriBuilder { constructor() { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 349fb4eb..978c5a4a 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -16,13 +16,32 @@ * along with this program. If not, see . */ -/*global optionsSave, utilIsolate -conditionsTestValue, profileConditionsDescriptor -handlebarsRenderDynamic -requestText, requestJson, optionsLoad -dictConfigured, dictTermsSort, dictEnabledSet -jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -AnkiNoteBuilder, AudioSystem, AudioUriBuilder, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ +/* global + * AnkiConnect + * AnkiNoteBuilder + * AnkiNull + * AudioSystem + * AudioUriBuilder + * BackendApiForwarder + * ClipboardMonitor + * JsonSchema + * Mecab + * Translator + * conditionsTestValue + * dictConfigured + * dictEnabledSet + * dictTermsSort + * handlebarsRenderDynamic + * jpConvertReading + * jpDistributeFuriganaInflected + * jpKatakanaToHiragana + * optionsLoad + * optionsSave + * profileConditionsDescriptor + * requestJson + * requestText + * utilIsolate + */ class Backend { constructor() { diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index a6d73c79..9a881f57 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global jpIsStringPartiallyJapanese*/ +/* global + * jpIsStringPartiallyJapanese + */ class ClipboardMonitor extends EventDispatcher { constructor({getClipboard}) { diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index 1095c7e0..c3e74656 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -16,7 +16,11 @@ * along with this program. If not, see . */ -/*global apiCommandExec, apiGetEnvironmentInfo, apiOptionsGet*/ +/* global + * apiCommandExec + * apiGetEnvironmentInfo + * apiOptionsGet + */ function showExtensionInfo() { const node = document.getElementById('extension-info'); diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 558f3ceb..08a2a39f 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -16,7 +16,12 @@ * along with this program. If not, see . */ -/*global dictFieldSplit, requestJson, JsonSchema, JSZip*/ +/* global + * JSZip + * JsonSchema + * dictFieldSplit + * requestJson + */ class Database { constructor() { diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js index 3ee4e7fa..e3ce6bd0 100644 --- a/ext/bg/js/handlebars.js +++ b/ext/bg/js/handlebars.js @@ -16,7 +16,11 @@ * along with this program. If not, see . */ -/*global jpIsCodePointKanji, jpDistributeFurigana, Handlebars*/ +/* global + * Handlebars + * jpDistributeFurigana + * jpIsCodePointKanji + */ function handlebarsEscape(text) { return Handlebars.Utils.escapeExpression(text); diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js index fc69dbba..3b37754d 100644 --- a/ext/bg/js/japanese.js +++ b/ext/bg/js/japanese.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global wanakana*/ +/* global + * wanakana + */ const JP_HALFWIDTH_KATAKANA_MAPPING = new Map([ ['ヲ', 'ヲヺ-'], diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 879b4a59..bd0bbe0e 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global utilStringHashCode*/ +/* global + * utilStringHashCode + */ /* * Generic options functions diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js index 453a0b79..a470e873 100644 --- a/ext/bg/js/search-frontend.js +++ b/ext/bg/js/search-frontend.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global apiOptionsGet*/ +/* global + * apiOptionsGet + */ async function searchFrontendSetup() { await yomichan.prepare(); diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js index 1ab23a82..664858a4 100644 --- a/ext/bg/js/search-query-parser-generator.js +++ b/ext/bg/js/search-query-parser-generator.js @@ -16,7 +16,10 @@ * along with this program. If not, see . */ -/*global apiGetQueryParserTemplatesHtml, TemplateHandler*/ +/* global + * TemplateHandler + * apiGetQueryParserTemplatesHtml + */ class QueryParserGenerator { constructor() { diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index c64d0fea..06316ce2 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -16,7 +16,15 @@ * along with this program. If not, see . */ -/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, TextScanner, QueryParserGenerator, docSentenceExtract*/ +/* global + * QueryParserGenerator + * TextScanner + * apiOptionsSet + * apiTermsFind + * apiTextParse + * apiTextParseMecab + * docSentenceExtract + */ class QueryParser extends TextScanner { constructor(search) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 5881f6f8..e2bdff73 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -16,7 +16,14 @@ * along with this program. If not, see . */ -/*global apiOptionsSet, apiTermsFind, apiClipboardGet, Display, QueryParser, ClipboardMonitor*/ +/* global + * ClipboardMonitor + * Display + * QueryParser + * apiClipboardGet + * apiOptionsSet + * apiTermsFind + */ class DisplaySearch extends Display { constructor() { diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index b1665048..c5222d30 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -16,10 +16,18 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, settingsSaveOptions -ankiGetFieldMarkers, ankiGetFieldMarkersHtml -apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates, apiTemplateRender -AnkiNoteBuilder*/ +/* global + * AnkiNoteBuilder + * ankiGetFieldMarkers + * ankiGetFieldMarkersHtml + * apiGetDefaultAnkiFieldTemplates + * apiOptionsGet + * apiTemplateRender + * apiTermsFind + * getOptionsContext + * getOptionsMutable + * settingsSaveOptions + */ function onAnkiFieldTemplatesReset(e) { e.preventDefault(); diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index 782691ab..b706cd1b 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -16,9 +16,16 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, settingsSaveOptions -utilBackgroundIsolate, utilAnkiGetDeckNames, utilAnkiGetModelNames, utilAnkiGetModelFieldNames -onFormOptionsChanged*/ +/* global + * getOptionsContext + * getOptionsMutable + * onFormOptionsChanged + * settingsSaveOptions + * utilAnkiGetDeckNames + * utilAnkiGetModelFieldNames + * utilAnkiGetModelNames + * utilBackgroundIsolate + */ // Private diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index c825be6b..38dd6349 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -16,8 +16,14 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, settingsSaveOptions, apiAudioGetUri -AudioSystem, AudioSourceUI*/ +/* global + * AudioSourceUI + * AudioSystem + * apiAudioGetUri + * getOptionsContext + * getOptionsMutable + * settingsSaveOptions + */ let audioSourceUI = null; let audioSystem = null; diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index daa08c61..21417dfb 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -16,9 +16,17 @@ * along with this program. If not, see . */ -/*global apiOptionsGetFull, apiGetEnvironmentInfo, apiGetDefaultAnkiFieldTemplates -utilBackend, utilIsolate, utilBackgroundIsolate, utilReadFileArrayBuffer -optionsGetDefault, optionsUpdateVersion*/ +/* global + * apiGetDefaultAnkiFieldTemplates + * apiGetEnvironmentInfo + * apiOptionsGetFull + * optionsGetDefault + * optionsUpdateVersion + * utilBackend + * utilBackgroundIsolate + * utilIsolate + * utilReadFileArrayBuffer + */ // Exporting diff --git a/ext/bg/js/settings/conditions-ui.js b/ext/bg/js/settings/conditions-ui.js index 4ca86b07..9d61d25e 100644 --- a/ext/bg/js/settings/conditions-ui.js +++ b/ext/bg/js/settings/conditions-ui.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global conditionsNormalizeOptionValue*/ +/* global + * conditionsNormalizeOptionValue + */ class ConditionsUI { static instantiateTemplate(templateSelector) { diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index b9551073..5e59cc3d 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -16,11 +16,23 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, getOptionsFullMutable, settingsSaveOptions, apiOptionsGetFull, apiOptionsGet -utilBackgroundIsolate, utilDatabaseDeleteDictionary, utilDatabaseGetDictionaryInfo, utilDatabaseGetDictionaryCounts -utilDatabasePurge, utilDatabaseImport -storageUpdateStats, storageEstimate -PageExitPrevention*/ +/* global + * PageExitPrevention + * apiOptionsGet + * apiOptionsGetFull + * getOptionsContext + * getOptionsFullMutable + * getOptionsMutable + * settingsSaveOptions + * storageEstimate + * storageUpdateStats + * utilBackgroundIsolate + * utilDatabaseDeleteDictionary + * utilDatabaseGetDictionaryCounts + * utilDatabaseGetDictionaryInfo + * utilDatabaseImport + * utilDatabasePurge + */ let dictionaryUI = null; diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 1bf1444c..ebc443df 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -16,13 +16,26 @@ * along with this program. If not, see . */ -/*global getOptionsContext, apiOptionsSave -utilBackend, utilIsolate, utilBackgroundIsolate -ankiErrorShown, ankiFieldsToDict -ankiTemplatesUpdateValue, onAnkiOptionsChanged, onDictionaryOptionsChanged -appearanceInitialize, audioSettingsInitialize, profileOptionsSetup, dictSettingsInitialize -ankiInitialize, ankiTemplatesInitialize, storageInfoInitialize, backupInitialize -*/ +/* global + * ankiErrorShown + * ankiFieldsToDict + * ankiInitialize + * ankiTemplatesInitialize + * ankiTemplatesUpdateValue + * apiOptionsSave + * appearanceInitialize + * audioSettingsInitialize + * backupInitialize + * dictSettingsInitialize + * getOptionsContext + * onAnkiOptionsChanged + * onDictionaryOptionsChanged + * profileOptionsSetup + * storageInfoInitialize + * utilBackend + * utilBackgroundIsolate + * utilIsolate + */ function getOptionsMutable(optionsContext) { return utilBackend().getOptions( diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 1ceac177..6a149841 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -16,7 +16,13 @@ * along with this program. If not, see . */ -/*global apiOptionsGet, Popup, PopupProxyHost, Frontend, TextSourceRange*/ +/* global + * Frontend + * Popup + * PopupProxyHost + * TextSourceRange + * apiOptionsGet + */ class SettingsPopupPreview { constructor() { diff --git a/ext/bg/js/settings/profiles.js b/ext/bg/js/settings/profiles.js index f946a33a..b35b6309 100644 --- a/ext/bg/js/settings/profiles.js +++ b/ext/bg/js/settings/profiles.js @@ -16,9 +16,17 @@ * along with this program. If not, see . */ -/*global getOptionsMutable, getOptionsFullMutable, settingsSaveOptions, apiOptionsGetFull -utilBackgroundIsolate, formWrite -conditionsClearCaches, ConditionsUI, profileConditionsDescriptor*/ +/* global + * ConditionsUI + * apiOptionsGetFull + * conditionsClearCaches + * formWrite + * getOptionsFullMutable + * getOptionsMutable + * profileConditionsDescriptor + * settingsSaveOptions + * utilBackgroundIsolate + */ let currentProfileIndex = 0; let profileConditionsContainer = null; diff --git a/ext/bg/js/settings/storage.js b/ext/bg/js/settings/storage.js index 8978414e..ae305e22 100644 --- a/ext/bg/js/settings/storage.js +++ b/ext/bg/js/settings/storage.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global apiGetEnvironmentInfo*/ +/* global + * apiGetEnvironmentInfo + */ function storageBytesToLabeledString(size) { const base = 1000; diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index c01a7124..25da9bf0 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -16,12 +16,28 @@ * along with this program. If not, see . */ -/*global requestJson -dictTermsMergeBySequence, dictTagBuildSource, dictTermsMergeByGloss, dictTermsSort, dictTagsSort -dictEnabledSet, dictTermsGroup, dictTermsCompressTags, dictTermsUndupe, dictTagSanitize -jpDistributeFurigana, jpConvertHalfWidthKanaToFullWidth, jpConvertNumericTofullWidth -jpConvertAlphabeticToKana, jpHiraganaToKatakana, jpKatakanaToHiragana, jpIsCodePointJapanese -Database, Deinflector*/ +/* global + * Database + * Deinflector + * dictEnabledSet + * dictTagBuildSource + * dictTagSanitize + * dictTagsSort + * dictTermsCompressTags + * dictTermsGroup + * dictTermsMergeByGloss + * dictTermsMergeBySequence + * dictTermsSort + * dictTermsUndupe + * jpConvertAlphabeticToKana + * jpConvertHalfWidthKanaToFullWidth + * jpConvertNumericTofullWidth + * jpDistributeFurigana + * jpHiraganaToKatakana + * jpIsCodePointJapanese + * jpKatakanaToHiragana + * requestJson + */ class Translator { constructor() { diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index 35861475..490f61bb 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -16,7 +16,11 @@ * along with this program. If not, see . */ -/*global TextSourceElement, TextSourceRange, DOM*/ +/* global + * DOM + * TextSourceElement + * TextSourceRange + */ const REGEX_TRANSPARENT_COLOR = /rgba\s*\([^)]*,\s*0(?:\.0+)?\s*\)/; diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index bc459d23..393c2719 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -16,7 +16,12 @@ * along with this program. If not, see . */ -/*global popupNestedInitialize, apiForward, apiGetMessageToken, Display*/ +/* global + * Display + * apiForward + * apiGetMessageToken + * popupNestedInitialize + */ class DisplayFloat extends Display { constructor() { diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js index e674724e..8424b21d 100644 --- a/ext/fg/js/frontend-initialize.js +++ b/ext/fg/js/frontend-initialize.js @@ -16,7 +16,11 @@ * along with this program. If not, see . */ -/*global PopupProxyHost, PopupProxy, Frontend*/ +/* global + * Frontend + * PopupProxy + * PopupProxyHost + */ async function main() { await yomichan.prepare(); diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 929ab56a..768b9326 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -16,7 +16,14 @@ * along with this program. If not, see . */ -/*global apiGetZoom, apiOptionsGet, apiTermsFind, apiKanjiFind, docSentenceExtract, TextScanner*/ +/* global + * TextScanner + * apiGetZoom + * apiKanjiFind + * apiOptionsGet + * apiTermsFind + * docSentenceExtract + */ class Frontend extends TextScanner { constructor(popup, ignoreNodes) { diff --git a/ext/fg/js/popup-nested.js b/ext/fg/js/popup-nested.js index 3e5f5b80..06f8fc4b 100644 --- a/ext/fg/js/popup-nested.js +++ b/ext/fg/js/popup-nested.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global apiOptionsGet*/ +/* global + * apiOptionsGet + */ let popupNestedInitialized = false; diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 49123ee1..793d3949 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -16,7 +16,11 @@ * along with this program. If not, see . */ -/*global apiFrameInformationGet, FrontendApiReceiver, Popup*/ +/* global + * FrontendApiReceiver + * Popup + * apiFrameInformationGet + */ class PopupProxyHost { constructor() { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 093cdd2e..f7cef214 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -/*global FrontendApiSender*/ +/* global + * FrontendApiSender + */ class PopupProxy { constructor(id, depth, parentId, parentFrameId, url) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index bc40a8c4..d752812e 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -16,7 +16,10 @@ * along with this program. If not, see . */ -/*global apiInjectStylesheet, apiGetMessageToken*/ +/* global + * apiGetMessageToken + * apiInjectStylesheet + */ class Popup { constructor(id, depth, frameIdPromise) { diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index 470e2a15..49afc44b 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -16,7 +16,10 @@ * along with this program. If not, see . */ -/*global apiGetDisplayTemplatesHtml, TemplateHandler*/ +/* global + * TemplateHandler + * apiGetDisplayTemplatesHtml + */ class DisplayGenerator { constructor() { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index a220c1f7..515e28a7 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -16,10 +16,24 @@ * along with this program. If not, see . */ -/*global docRangeFromPoint, docSentenceExtract -apiKanjiFind, apiTermsFind, apiNoteView, apiOptionsGet, apiDefinitionsAddable, apiDefinitionAdd -apiScreenshotGet, apiForward, apiAudioGetUri -AudioSystem, DisplayGenerator, WindowScroll, DisplayContext, DOM*/ +/* global + * AudioSystem + * DOM + * DisplayContext + * DisplayGenerator + * WindowScroll + * apiAudioGetUri + * apiDefinitionAdd + * apiDefinitionsAddable + * apiForward + * apiKanjiFind + * apiNoteView + * apiOptionsGet + * apiScreenshotGet + * apiTermsFind + * docRangeFromPoint + * docSentenceExtract + */ class Display { constructor(spinner, container) { diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index ff0eac8b..a08e09fb 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -16,7 +16,11 @@ * along with this program. If not, see . */ -/*global docRangeFromPoint, TextSourceRange, DOM*/ +/* global + * DOM + * TextSourceRange + * docRangeFromPoint + */ class TextScanner { constructor(node, ignoreNodes, ignoreElements, ignorePoints) { -- cgit v1.2.3 From adbdca6a8bbb68ea046fe55b070cddb501ce40aa Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 10 Mar 2020 22:38:06 -0400 Subject: Add to test-lint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74d89dda..eb449ea9 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "scripts": { "test": "npm run test-lint && npm run test-code", - "test-lint": "eslint .", + "test-lint": "eslint . && node ./test/lint/global-declarations.js", "test-code": "node ./test/test-schema.js && node ./test/test-dictionary.js && node ./test/test-database.js && node ./test/test-document.js" }, "repository": { -- cgit v1.2.3 From 98afe7adae80c6bc9de0c4b996e6f6cb0a5df49d Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sat, 14 Mar 2020 13:12:08 -0700 Subject: Bump version --- ext/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/manifest.json b/ext/manifest.json index 09e89729..3cea8647 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Yomichan", - "version": "20.2.24.0", + "version": "20.3.14.0", "description": "Japanese dictionary with Anki integration", "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, -- cgit v1.2.3