表示する用語
+ チェックしたカテゴリの用語を強調表示し、カーソルを合わせると説明を表示します。
+ +設定を読み込み中…
+ +カテゴリを無効にしても、お気に入りは残ります。
+お気に入り
+0件
+お気に入りはまだありません。
+diff --git a/README.md b/README.md index 9acecd0..9d08c02 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ npm run build -w @techword/browser `chrome://extensions` → デベロッパーモード ON → 「パッケージ化されていない拡張機能を読み込む」 で `packages/browser/dist` を選択。任意のページで用語にホバーします。 +ホバー内の `☆` を押すと用語をお気に入りに追加でき、もう一度押すと解除できます。 +Chrome のツールバーにある TechTerm のアイコンを押すとサイドパネルが開き、 +お気に入りの確認、個別削除、全削除ができます。お気に入りは Chrome の同期ストレージに保存されます。 + ## 辞書を増やす `packages/core/src/data/terms.json` に追記します。 @@ -73,3 +77,16 @@ VS Code 側は設定 `techword.userTerms` からも同じ形式で追加・上 (最大 4 トークン、長い一致から順に)。 - **漢字・ひらがなは切り出さない**。形態素解析なしでは境界が決まらないため、 日本語の別名はカタカナか英字で `match` に書く運用です。 + +### 表示する用語の設定 + +拡張機能アイコンからサイドパネルを開き、「表示する用語」でカテゴリを選択できます。 +初期状態では全カテゴリが有効です。チェックを外すと、そのカテゴリのハイライトと +ホバー説明が開いているページでも無効になります。お気に入りは削除されません。 +設定はChromeの同期ストレージに保存されます。 + +通常のページ更新では、追加・変更されたテキストだけを再検索します。 +削除したテキストのハイライトは除去し、初回表示とカテゴリ設定の変更時だけページ全体を検索します。 + +`npm run build` のあとに `npm test` を実行すると、辞書に加えて、差分更新・カテゴリ設定・ +サイドパネルの保存失敗時の動作も検証します。 diff --git a/package-lock.json b/package-lock.json index 9e02975..b559b6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "TechTerm", - "version": "0.1.1", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "TechTerm", - "version": "0.1.1", + "version": "0.2.1", "workspaces": [ "packages/core", "packages/vscode", @@ -19,6 +19,135 @@ "typescript": "^5.9.3" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -452,6 +581,41 @@ "resolved": "packages/core", "link": true }, + "node_modules/@types/chrome": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.2.9.tgz", + "integrity": "sha512-6rXrDPkkWv4D2Q23HqT+iED4voODU5CpOSc2VFgKyecSFItuEsIBvz9CY6jUQ3dd+Q7zo1bnCE9pKRMtkYut5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", @@ -482,6 +646,16 @@ "node": ">=6.5" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/archiver": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/archiver/-/archiver-8.0.0.tgz", @@ -740,6 +914,72 @@ "node": ">=18" } }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", @@ -818,6 +1058,60 @@ "dev": true, "license": "MIT" }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -846,6 +1140,13 @@ "dev": true, "license": "ISC" }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-stream": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", @@ -866,6 +1167,46 @@ "dev": true, "license": "MIT" }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -912,6 +1253,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", @@ -928,6 +1276,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -938,6 +1293,26 @@ "node": ">=0.10.0" } }, + "node_modules/nwsapi": { + "version": "2.2.27", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.27.tgz", + "integrity": "sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -955,6 +1330,16 @@ "dev": true, "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -988,6 +1373,13 @@ "url": "https://github.com/sponsors/yqnn" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1009,6 +1401,26 @@ ], "license": "MIT" }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/streamx": { "version": "2.28.1", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", @@ -1031,6 +1443,13 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/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, + "license": "MIT" + }, "node_modules/tar-stream": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.1.tgz", @@ -1068,6 +1487,52 @@ "b4a": "^1.6.4" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -1096,6 +1561,106 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/zip-stream": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-7.0.5.tgz", @@ -1113,18 +1678,22 @@ }, "packages/browser": { "name": "@techword/browser", - "version": "0.1.1", + "version": "0.2.1", "dependencies": { "@techword/core": "*" + }, + "devDependencies": { + "@types/chrome": "^0.2.9", + "jsdom": "^26.1.0" } }, "packages/core": { "name": "@techword/core", - "version": "0.1.1" + "version": "0.2.1" }, "packages/vscode": { "name": "techword-vscode", - "version": "0.1.1", + "version": "0.2.1", "dependencies": { "@techword/core": "*" }, diff --git a/package.json b/package.json index 8d95e00..5625ccd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "npm run build -w @techword/core && npm run build -w techword-vscode && npm run build -w @techword/browser", "watch:core": "npm run watch -w @techword/core", - "test": "npm test -w @techword/core" + "test": "npm test -w @techword/core && npm test -w @techword/browser" }, "devDependencies": { "@types/node": "^22.20.1", diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index d450d81..a003eb5 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -2,7 +2,18 @@ "manifest_version": 3, "name": "TechTerm", "version": "0.2.1", + "minimum_chrome_version": "114", "description": "プログラミング用語にホバーすると、意味を簡潔に表示します。", + "permissions": ["storage", "sidePanel"], + "action": { + "default_title": "TechTermのお気に入りを開く" + }, + "background": { + "service_worker": "background.js" + }, + "side_panel": { + "default_path": "sidepanel.html" + }, "content_scripts": [ { "matches": [ diff --git a/packages/browser/package.json b/packages/browser/package.json index 562184e..10c1347 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -4,10 +4,15 @@ "private": true, "typescript": "^6.0.3", "scripts": { - "build": "esbuild src/content.ts --bundle --outfile=dist/content.js --format=iife --target=chrome110 && cp manifest.json dist/manifest.json", - "watch": "esbuild src/content.ts --bundle --outfile=dist/content.js --format=iife --target=chrome110 --watch" + "build": "tsc -p tsconfig.json --noEmit && esbuild src/content.ts src/background.ts src/sidepanel.ts --bundle --outdir=dist --format=iife --target=chrome114 && cp manifest.json sidepanel.html sidepanel.css dist/", + "watch": "mkdir -p dist && cp manifest.json sidepanel.html sidepanel.css dist/ && esbuild src/content.ts src/background.ts src/sidepanel.ts --bundle --outdir=dist --format=iife --target=chrome114 --watch", + "test": "node --test tests/*.test.cjs" }, "dependencies": { "@techword/core": "*" + }, + "devDependencies": { + "@types/chrome": "^0.2.9", + "jsdom": "^26.1.0" } } diff --git a/packages/browser/sidepanel.css b/packages/browser/sidepanel.css new file mode 100644 index 0000000..bb59fa5 --- /dev/null +++ b/packages/browser/sidepanel.css @@ -0,0 +1,147 @@ +:root { + color-scheme: light dark; + font-family: -apple-system, BlinkMacSystemFont, "Hiragino Sans", "Noto Sans JP", sans-serif; + background: Canvas; + color: CanvasText; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + +main { + padding: 18px 16px 28px; +} + +header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 18px; +} + +h1, +h2, +p { + margin: 0; +} + +h1 { + font-size: 20px; + line-height: 1.3; +} + +.count { + margin-top: 3px; + color: GrayText; + font-size: 12px; +} + +button { + border: 1px solid color-mix(in srgb, CanvasText 24%, transparent); + border-radius: 7px; + background: ButtonFace; + color: ButtonText; + cursor: pointer; + font: inherit; +} + +button:hover:not(:disabled) { + background: color-mix(in srgb, ButtonFace 80%, CanvasText 20%); +} + +button:focus-visible, +a:focus-visible { + outline: 2px solid Highlight; + outline-offset: 2px; +} + +button:disabled { + cursor: default; + opacity: 0.5; +} + +.clear-favorites { + flex: none; + padding: 7px 10px; + font-size: 12px; +} + +.favorites { + display: grid; + gap: 10px; + margin: 0; + padding: 0; + list-style: none; +} + +.favorite-item { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px; + border: 1px solid color-mix(in srgb, CanvasText 18%, transparent); + border-radius: 9px; +} + +.favorite-body { + flex: 1; + min-width: 0; +} + +.favorite-body h2 { + font-size: 15px; + line-height: 1.4; +} + +.favorite-body p { + margin-top: 4px; + color: color-mix(in srgb, CanvasText 76%, transparent); + font-size: 13px; + line-height: 1.6; +} + +.favorite-body a { + display: inline-block; + margin-top: 6px; + color: LinkText; + font-size: 12px; +} + +.remove-favorite { + flex: none; + padding: 6px 8px; + color: #c62828; + font-size: 12px; +} + +.empty { + padding: 30px 8px; + color: GrayText; + text-align: center; + font-size: 13px; +} + +.error { + margin-bottom: 12px; + color: #c62828; + font-size: 13px; +} + +[hidden] { display: none !important; } +.term-settings { margin-bottom: 24px; padding-bottom: 20px; border-bottom: 1px solid color-mix(in srgb, CanvasText 18%, transparent); } +.term-settings summary { padding: 6px 0; cursor: pointer; } +.term-settings summary:focus-visible { outline: 2px solid Highlight; outline-offset: 4px; border-radius: 4px; } +.term-settings h2 { display: inline; font-size: 18px; } +.settings-description, .settings-note, .settings-status { margin-top: 8px; font-size: 12px; line-height: 1.6; color: GrayText; } +.term-categories { display: grid; gap: 4px; margin-top: 12px; } +.category-row { display: flex; align-items: center; gap: 10px; padding: 9px 6px; font-size: 13px; cursor: pointer; } +.category-row:hover { background: color-mix(in srgb, CanvasText 6%, transparent); border-radius: 6px; } +.category-row input { width: 17px; height: 17px; margin: 0; flex: none; accent-color: #4272d7; } +.category-row input:focus-visible { outline: 2px solid Highlight; outline-offset: 3px; } +.category-count { margin-left: auto; color: GrayText; white-space: nowrap; } diff --git a/packages/browser/sidepanel.html b/packages/browser/sidepanel.html new file mode 100644 index 0000000..0473d00 --- /dev/null +++ b/packages/browser/sidepanel.html @@ -0,0 +1,35 @@ + + +
+ + +チェックしたカテゴリの用語を強調表示し、カーソルを合わせると説明を表示します。
+ +設定を読み込み中…
+ +カテゴリを無効にしても、お気に入りは残ります。
+0件
+お気に入りはまだありません。
+JavaScript
') { + const d = new JSDOM(html, { runScripts: 'outside-only', pretendToBeVisual: true }); + const observers = []; + const NativeObserver = d.window.MutationObserver; + d.window.MutationObserver = class extends NativeObserver { + constructor(callback) { super(callback); observers.push(this); } + }; + const close = d.window.close.bind(d.window); + d.window.close = () => { observers.forEach(observer => observer.disconnect()); close(); }; + d.window.Highlight = class extends Set {}; + d.window.CSS = { highlights: new Map() }; + return d; +} +const settle = async (w) => { await new Promise(resolve => w.requestAnimationFrame(() => w.requestAnimationFrame(resolve))); }; +function storage(w, initial = {}, options = {}) { + const data = { ...initial }; + const listeners = new Set(); + const emit = (changes, area = 'sync') => listeners.forEach(fn => fn(changes, area)); + w.chrome = { storage: { onChanged: { addListener: fn => listeners.add(fn), removeListener: fn => listeners.delete(fn) }, sync: { + get: async () => { + const snapshot = { ...data }; + if (options.beforeRead) await options.beforeRead(emit); + return snapshot; + }, + set: async (values) => { + if (options.failWrite) throw Error('write failed'); + const changes = Object.fromEntries(Object.entries(values).map(([k, v]) => [k, { oldValue: data[k], newValue: v }])); + Object.assign(data, values); emit(changes); + }, + } } }; + return { data, emit }; +} + +test('1,000段落のうち変更・追加されたテキストだけを検索し、削除Rangeを除去する', async () => { + const d = dom('JavaScript
'.repeat(1000) + 'React
Vue
'; + w.document.querySelector('main').append(wrapper); + wrapper.firstChild.firstChild.data = 'Nuxt'; + await settle(w); + assert.deepEqual(calls, ['Nuxt', 'Vue']); + calls.length = 0; + wrapper.remove(); + await settle(w); + assert.equal(highlight.size, 1000); + assert.equal(calls.length, 0); + h.disconnect(); + } finally { w.close(); } +}); + +test('ノード移動・body差し替え・編集領域の切り替えでも古いRangeを残さない', async () => { + const d = dom('React
Git
'; + w.document.body.replaceWith(body); await settle(w); + assert.equal(ranges.size, 1); assert.equal([...ranges][0].toString(), 'Git'); + h.disconnect(); + } finally { w.close(); } +}); + +test('設定の初回読込中に受信した変更が古い値で上書きされない', async () => { + const d = dom(); const w = d.window; + try { + storage(w, {}, { beforeRead: async emit => emit({ 'termCategory.javascript': { newValue: false } }) }); + w.eval(settingsCode); + let received; + const stop = w.TestModule.observeCategorySettings(value => { received = value; }, assert.fail); + await settle(w); + assert.equal(received.javascript, false); + assert.equal(received.basics, true); + await w.TestModule.setCategoryEnabled('basics', false); + assert.equal(received.javascript, false); assert.equal(received.basics, false); + stop(); + } finally { w.close(); } +}); + +test('カテゴリOFFで表示済みの説明とハイライトが消え、ONで戻る。お気に入りは保持', async () => { + const d = dom('JavaScript
'); const w = d.window; + try { + const { data } = storage(w, { favoriteTermIds: ['javascript'] }); + const node = w.document.querySelector('p').firstChild; + w.document.caretRangeFromPoint = () => { const r = w.document.createRange(); r.setStart(node, 2); return r; }; + w.Range.prototype.getBoundingClientRect = () => ({ left: 10, bottom: 20, top: 5 }); + w.eval(contentCode); await settle(w); + const hover = async () => { w.document.dispatchEvent(new w.MouseEvent('mousemove', { clientX: 20, clientY: 10 })); await new Promise(r => w.setTimeout(r, 280)); }; + const host = w.document.querySelector('[data-techterm-ui]'); + assert.equal(w.CSS.highlights.get('tw-highlight').size, 1); + await hover(); assert.equal(host.style.display, 'block'); + await w.chrome.storage.sync.set({ 'termCategory.javascript': false }); + assert.equal(host.style.display, 'none'); + assert.equal(w.CSS.highlights.get('tw-highlight').size, 0); + await hover(); assert.equal(host.style.display, 'none'); + await w.chrome.storage.sync.set({ 'termCategory.javascript': true }); + assert.equal(w.CSS.highlights.get('tw-highlight').size, 1); + await hover(); assert.equal(host.style.display, 'block'); + assert.deepEqual(data.favoriteTermIds, ['javascript']); + } finally { w.close(); } +}); + +test('サイドパネルに7カテゴリを表示し、保存と失敗時の復元を行う', async () => { + const d = dom(fs.readFileSync(path.join(root, 'sidepanel.html'), 'utf8')); const w = d.window; + try { + const options = {}; const { data, emit } = storage(w, { 'termCategory.javascript': false }, options); + w.eval(sidepanelCode); await settle(w); + const inputs = [...w.document.querySelectorAll('#term-categories input')]; + assert.equal(inputs.length, 7); assert.equal(inputs[1].checked, false); + inputs[0].click(); await settle(w); + assert.equal(data['termCategory.basics'], false); + assert.equal(data['termCategory.javascript'], false); + const errors = []; w.console.error = (...args) => errors.push(args); + options.failWrite = true; inputs[0].click(); await settle(w); + assert.equal(errors.length, 1); + assert.equal(inputs[0].checked, false); assert.equal(inputs[0].disabled, false); + assert.equal(w.document.querySelector('#settings-error').hidden, false); + emit({ 'termCategory.javascript': { newValue: true } }); + assert.equal(inputs[1].checked, true); + } finally { w.close(); } +}); + +test('全カテゴリOFFの保存状態で起動すると、後から追加した用語も表示しない', async () => { + const d = dom('JavaScript Git API
'); const w = d.window; + try { + const ids = ['basics', 'javascript', 'frameworks', 'html-css', 'development', 'network-data', 'design-performance']; + storage(w, Object.fromEntries(ids.map(id => ['termCategory.' + id, false]))); + w.eval(contentCode); await settle(w); + assert.equal(w.CSS.highlights.get('tw-highlight').size, 0); + const p = w.document.createElement('p'); p.textContent = 'React'; w.document.body.append(p); + await settle(w); + assert.equal(w.CSS.highlights.get('tw-highlight').size, 0); + await w.chrome.storage.sync.set({ 'termCategory.frameworks': true }); + assert.equal(w.CSS.highlights.get('tw-highlight').size, 1); + } finally { w.close(); } +}); diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json new file mode 100644 index 0000000..1290cee --- /dev/null +++ b/packages/browser/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["chrome"], + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/core/src/categories.ts b/packages/core/src/categories.ts new file mode 100644 index 0000000..3d50928 --- /dev/null +++ b/packages/core/src/categories.ts @@ -0,0 +1,12 @@ +/** 表示設定で使う安定したカテゴリIDと表示名。 */ +export const termCategories = [ + { id: 'basics', label: 'プログラミング基礎' }, + { id: 'javascript', label: 'JavaScript・TypeScript' }, + { id: 'frameworks', label: 'React・Vue' }, + { id: 'html-css', label: 'HTML・CSS' }, + { id: 'development', label: 'Git・開発ツール' }, + { id: 'network-data', label: '通信・データベース' }, + { id: 'design-performance', label: '設計・性能' }, +] as const; + +export type TermCategory = (typeof termCategories)[number]['id']; diff --git a/packages/core/src/data/terms.json b/packages/core/src/data/terms.json index 86c7ba5..ed57fdd 100644 --- a/packages/core/src/data/terms.json +++ b/packages/core/src/data/terms.json @@ -1,6 +1,7 @@ [ { "id": "closure", + "category": "javascript", "term": "closure", "match": ["クロージャ", "クロージャー"], "short": "自分が作られた場所の変数を覚えている関数。", @@ -10,6 +11,7 @@ }, { "id": "idempotent", + "category": "design-performance", "term": "idempotent", "match": ["冪等", "べき等", "idempotency"], "short": "何回実行しても結果が同じになる性質。", @@ -17,6 +19,7 @@ }, { "id": "race-condition", + "category": "design-performance", "term": "race condition", "match": ["競合状態", "レースコンディション"], "short": "処理の実行順序次第で結果が変わってしまうバグ。", @@ -24,6 +27,7 @@ }, { "id": "memoization", + "category": "design-performance", "term": "memoization", "match": ["メモ化", "memoize", "memo"], "short": "一度計算した結果を覚えておいて使い回すこと。", @@ -31,6 +35,7 @@ }, { "id": "gc", + "category": "design-performance", "term": "garbage collection", "match": ["GC", "ガベージコレクション", "ガベコレ"], "short": "使われなくなったメモリを自動で回収する仕組み。", @@ -38,6 +43,7 @@ }, { "id": "immutable", + "category": "design-performance", "term": "immutable", "match": ["イミュータブル", "不変", "immutability"], "short": "作った後に中身を書き換えられないデータ。", @@ -45,6 +51,7 @@ }, { "id": "dependency-injection", + "category": "design-performance", "term": "dependency injection", "match": ["DI", "依存性注入"], "short": "使う部品を自分で作らず、外から渡してもらう設計。", @@ -52,6 +59,7 @@ }, { "id": "middleware", + "category": "network-data", "term": "middleware", "match": ["ミドルウェア"], "short": "リクエストと本処理の間に挟まる共通処理。", @@ -59,6 +67,7 @@ }, { "id": "polyfill", + "category": "javascript", "term": "polyfill", "match": ["ポリフィル"], "short": "古い環境に新しい機能を後付けするコード。", @@ -66,6 +75,7 @@ }, { "id": "hoisting", + "category": "javascript", "term": "hoisting", "match": ["巻き上げ", "ホイスティング"], "short": "宣言がスコープの先頭に持ち上げられる挙動。", @@ -74,6 +84,7 @@ }, { "id": "use-state", + "category": "frameworks", "term": "useState", "match": ["ユーズステート"], "short": "React で「変わる値」を持つためのフック。", @@ -84,6 +95,7 @@ }, { "id": "use-effect", + "category": "frameworks", "term": "useEffect", "short": "React で描画のあとに副作用を走らせるフック。", "detail": "通信・購読・DOM 操作など、描画そのものではない処理を置く。第 2 引数の依存配列に入れた値が変わったときだけ再実行される。", @@ -92,6 +104,7 @@ }, { "id": "promise", + "category": "javascript", "term": "Promise", "match": ["プロミス"], "short": "「あとで結果が入る箱」を表すオブジェクト。", @@ -100,6 +113,7 @@ }, { "id": "generic", + "category": "javascript", "term": "generics", "match": ["ジェネリクス", "generic", "型引数"], "short": "型を後から差し込めるようにする仕組み。", @@ -107,6 +121,7 @@ }, { "id": "type-guard", + "category": "javascript", "term": "type guard", "match": ["型ガード"], "short": "実行時のチェックで型を絞り込む書き方。", @@ -115,6 +130,7 @@ }, { "id": "orm", + "category": "network-data", "term": "ORM", "match": ["オーアールエム", "object relational mapping"], "short": "DB のテーブルをコードのオブジェクトとして扱う道具。", @@ -122,6 +138,7 @@ }, { "id": "n-plus-one", + "category": "network-data", "term": "N+1 problem", "match": ["N+1", "エヌプラスワン"], "short": "一覧を出すたびに件数ぶん追加クエリが飛ぶ性能問題。", @@ -129,6 +146,7 @@ }, { "id": "idempotency-key", + "category": "design-performance", "term": "idempotency key", "match": ["冪等キー"], "short": "同じリクエストの二重実行を防ぐための識別子。", @@ -136,6 +154,7 @@ }, { "id": "cors", + "category": "network-data", "term": "CORS", "match": ["オリジン間リソース共有", "cross origin resource sharing"], "short": "別ドメインへのブラウザからの通信を許可する仕組み。", @@ -143,6 +162,7 @@ }, { "id": "jwt", + "category": "network-data", "term": "JWT", "match": ["json web token", "ジョット"], "short": "署名つきで中身を自分で検証できるトークン。", @@ -150,6 +170,7 @@ }, { "id": "migration", + "category": "network-data", "term": "migration", "match": ["マイグレーション", "マイグレ"], "short": "DB のスキーマ変更を手順として記録したファイル。", @@ -157,6 +178,7 @@ }, { "id": "debounce", + "category": "design-performance", "term": "debounce", "match": ["デバウンス"], "short": "連続した呼び出しを、止まってから 1 回にまとめる。", @@ -164,6 +186,7 @@ }, { "id": "throttle", + "category": "design-performance", "term": "throttle", "match": ["スロットル", "スロットリング", "throttling"], "short": "呼び出しの頻度を一定間隔までに制限する。", @@ -171,6 +194,7 @@ }, { "id": "exponential-backoff", + "category": "design-performance", "term": "exponential backoff", "match": ["指数バックオフ", "backoff"], "short": "リトライ間隔を 1秒, 2秒, 4秒…と伸ばしていく方式。", @@ -178,6 +202,7 @@ }, { "id": "single-source-of-truth", + "category": "design-performance", "term": "single source of truth", "match": ["SSOT", "信頼できる唯一の情報源"], "short": "同じ情報の正本を 1 箇所だけに持つ設計方針。", @@ -185,6 +210,7 @@ }, { "id": "feature-flag", + "category": "design-performance", "term": "feature flag", "match": ["フィーチャーフラグ", "feature toggle", "機能フラグ"], "short": "コードを出したまま機能のオン/オフを切り替える仕組み。", @@ -192,6 +218,7 @@ }, { "id": "cache", + "category": "design-performance", "term": "cache", "match": ["キャッシュ", "caching"], "short": "一度取得・計算したデータを保存し、次回から素早く使う仕組み。", @@ -199,6 +226,7 @@ }, { "id": "api", + "category": "network-data", "term": "API", "match": ["エーピーアイ", "application programming interface"], "short": "ソフトウェア同士が機能やデータをやり取りするための窓口。", @@ -206,6 +234,7 @@ }, { "id": "refactoring", + "category": "design-performance", "term": "refactoring", "match": ["リファクタリング", "リファクタ"], "short": "動作を変えずに、コードを読みやすく整理すること。", @@ -213,6 +242,7 @@ }, { "id": "object", + "category": "basics", "term": "object", "match": ["オブジェクト"], "short": "複数のデータや処理をひとまとまりにしたもの。", @@ -222,6 +252,7 @@ }, { "id": "class", + "category": "basics", "term": "class", "match": ["クラス"], "short": "同じ種類のオブジェクトを作るための設計図。", @@ -231,6 +262,7 @@ }, { "id": "instance", + "category": "basics", "term": "instance", "match": ["インスタンス"], "short": "クラスなどの設計をもとに実際に作られたオブジェクト。", @@ -239,6 +271,7 @@ }, { "id": "key", + "category": "basics", "term": "key", "match": ["キー", "property name", "プロパティ名"], "short": "オブジェクトなどから値を取り出すための名前。", @@ -247,6 +280,7 @@ }, { "id": "value", + "category": "basics", "term": "value", "match": ["バリュー", "値"], "short": "変数やキーに対応して保存されているデータ。", @@ -255,6 +289,7 @@ }, { "id": "path", + "category": "development", "term": "path", "match": ["パス", "ファイルパス"], "short": "ファイルやディレクトリの場所を表す文字列。", @@ -263,6 +298,7 @@ }, { "id": "directory", + "category": "development", "term": "directory", "match": ["ディレクトリ", "folder", "フォルダ"], "short": "ファイルや別のディレクトリをまとめて置く場所。", @@ -271,6 +307,7 @@ }, { "id": "root-directory", + "category": "development", "term": "root directory", "match": ["ルートディレクトリ", "root folder", "ルートフォルダ"], "short": "ディレクトリ階層のいちばん上にある場所。", @@ -279,6 +316,7 @@ }, { "id": "current-directory", + "category": "development", "term": "current directory", "match": ["カレントディレクトリ", "working directory", "作業ディレクトリ"], "short": "プログラムやコマンドが現在の基準にしているディレクトリ。", @@ -287,6 +325,7 @@ }, { "id": "javascript", + "category": "javascript", "term": "JavaScript", "match": ["ジャバスクリプト", "JS"], "short": "Web ページなどに動きや処理を加えるプログラミング言語。", @@ -296,6 +335,7 @@ }, { "id": "typescript", + "category": "javascript", "term": "TypeScript", "match": ["タイプスクリプト", "TS"], "short": "JavaScript に型の仕組みを加えたプログラミング言語。", @@ -305,6 +345,7 @@ }, { "id": "nodejs", + "category": "javascript", "term": "Node.js", "match": ["Node", "NodeJS", "ノード", "ノードジェイエス"], "short": "ブラウザの外で JavaScript を実行するための環境。", @@ -314,6 +355,7 @@ }, { "id": "vue", + "category": "frameworks", "term": "Vue.js", "match": ["Vue", "VueJS", "ビュー", "ビュージェイエス"], "short": "Web の画面を部品に分けて作るための JavaScript フレームワーク。", @@ -323,6 +365,7 @@ }, { "id": "nextjs", + "category": "frameworks", "term": "Next.js", "match": ["Next", "NextJS", "ネクスト", "ネクストジェイエス"], "short": "React を使った Web アプリ開発のためのフレームワーク。", @@ -332,6 +375,7 @@ }, { "id": "react", + "category": "frameworks", "term": "React", "match": ["React.js", "ReactJS", "リアクト"], "short": "Web の画面をコンポーネントとして作る JavaScript ライブラリ。", @@ -341,6 +385,7 @@ }, { "id": "python", + "category": "basics", "term": "Python", "match": ["パイソン"], "short": "読みやすく簡潔な文法を持つプログラミング言語。", @@ -350,6 +395,7 @@ }, { "id": "ruby", + "category": "basics", "term": "Ruby", "match": ["ルビー"], "short": "人が読み書きしやすいことを重視したプログラミング言語。", @@ -359,6 +405,7 @@ }, { "id": "html", + "category": "html-css", "term": "HTML", "match": ["エイチティーエムエル", "HyperText Markup Language"], "short": "Web ページの内容と構造を記述するためのマークアップ言語。", @@ -368,6 +415,7 @@ }, { "id": "css", + "category": "html-css", "term": "CSS", "match": ["シーエスエス", "Cascading Style Sheets"], "short": "Web ページの見た目やレイアウトを指定するための言語。", @@ -377,6 +425,7 @@ }, { "id": "vscode", + "category": "development", "term": "Visual Studio Code", "match": ["VS Code", "VSCode", "ブイエスコード"], "short": "プログラムの作成や編集に使うソースコードエディタ。", @@ -386,6 +435,7 @@ }, { "id": "nuxt", + "category": "frameworks", "term": "Nuxt", "match": ["Nuxt.js", "NuxtJS", "ナクスト"], "short": "Vue を使った Web アプリ開発のためのフレームワーク。", @@ -395,6 +445,7 @@ }, { "id": "terminal", + "category": "development", "term": "terminal", "match": ["ターミナル", "command line", "コマンドライン"], "short": "文字でコマンドを入力してコンピュータを操作する画面。", @@ -403,6 +454,7 @@ }, { "id": "github", + "category": "development", "term": "GitHub", "match": ["ギットハブ"], "short": "Git のリポジトリを保存・共有できるWebサービス。", @@ -411,6 +463,7 @@ }, { "id": "git", + "category": "development", "term": "Git", "match": ["ギット", "version control system", "バージョン管理システム"], "short": "ファイルの変更履歴を記録・管理するためのツール。", @@ -420,6 +473,7 @@ }, { "id": "commit", + "category": "development", "term": "commit", "match": ["コミット", "git commit"], "short": "Git でファイルの変更をひとまとまりとして記録すること。", @@ -429,6 +483,7 @@ }, { "id": "pull-request", + "category": "development", "term": "pull request", "match": ["プルリクエスト", "PR", "プルリク"], "short": "ブランチの変更を取り込み、レビューしてもらうための依頼。", @@ -437,6 +492,7 @@ }, { "id": "merge", + "category": "development", "term": "merge", "match": ["マージ", "git merge"], "short": "別のブランチにある変更履歴をひとつに統合すること。", @@ -446,6 +502,7 @@ }, { "id": "git-pull", + "category": "development", "term": "git pull", "match": ["pull", "プル"], "short": "リモートリポジトリの変更を取得し、現在のブランチへ反映する操作。", @@ -455,6 +512,7 @@ }, { "id": "git-push", + "category": "development", "term": "git push", "match": ["push", "プッシュ"], "short": "ローカルのコミットをリモートリポジトリへ送る操作。", @@ -464,6 +522,7 @@ }, { "id": "git-clone", + "category": "development", "term": "git clone", "match": ["clone", "クローン"], "short": "リモートリポジトリを履歴ごと手元へ複製する操作。", @@ -473,6 +532,7 @@ }, { "id": "git-checkout", + "category": "development", "term": "git checkout", "match": ["checkout", "チェックアウト"], "short": "ブランチを切り替えたり、ファイルを指定した状態へ戻したりする操作。", @@ -482,6 +542,7 @@ }, { "id": "git-fetch", + "category": "development", "term": "git fetch", "match": ["fetch", "フェッチ"], "short": "リモートリポジトリの最新情報を手元へ取得する操作。", @@ -491,6 +552,7 @@ }, { "id": "branch", + "category": "development", "term": "branch", "match": ["ブランチ", "git branch"], "short": "Git の変更履歴を枝分かれさせて作業する仕組み。", @@ -500,6 +562,7 @@ }, { "id": "repository", + "category": "development", "term": "repository", "match": ["リポジトリ", "repo", "レポジトリ"], "short": "Git がファイルと変更履歴をまとめて管理する場所。", @@ -509,6 +572,7 @@ }, { "id": "git-init", + "category": "development", "term": "git init", "match": ["init", "イニット", "初期化"], "short": "現在のディレクトリを新しい Git リポジトリにする操作。", @@ -518,6 +582,7 @@ }, { "id": "staging-area", + "category": "development", "term": "staging area", "match": ["stage", "staging", "ステージ", "ステージング", "index", "インデックス"], "short": "次のコミットに含める変更を一時的に置く場所。", @@ -527,6 +592,7 @@ }, { "id": "html-id", + "category": "html-css", "term": "id attribute", "match": ["id", "ID", "アイディー", "id属性"], "short": "HTML 要素にページ内で固有の名前を付ける属性。", @@ -537,6 +603,7 @@ }, { "id": "html-tag", + "category": "html-css", "term": "HTML tag", "match": ["tag", "タグ", "HTMLタグ"], "short": "HTML で要素の種類や範囲を示すための記号。", @@ -547,6 +614,7 @@ }, { "id": "css-margin", + "category": "html-css", "term": "margin", "match": ["マージン", "外側の余白"], "short": "CSS で要素の境界線より外側に作る余白。", @@ -557,6 +625,7 @@ }, { "id": "css-border", + "category": "html-css", "term": "border", "match": ["ボーダー", "境界線"], "short": "CSS で要素の周囲に表示する境界線。", @@ -567,6 +636,7 @@ }, { "id": "css-padding", + "category": "html-css", "term": "padding", "match": ["パディング", "内側の余白"], "short": "CSS で要素の内容と境界線の間に作る余白。", @@ -577,6 +647,7 @@ }, { "id": "css-height", + "category": "html-css", "term": "height", "match": ["ハイト", "高さ"], "short": "CSS で要素の高さを指定するプロパティ。", @@ -587,6 +658,7 @@ }, { "id": "css-width", + "category": "html-css", "term": "width", "match": ["ウィズ", "幅"], "short": "CSS で要素の横幅を指定するプロパティ。", @@ -597,6 +669,7 @@ }, { "id": "box-model", + "category": "html-css", "term": "box model", "match": ["ボックスモデル", "CSS box model"], "short": "HTML 要素を四角い領域として扱う CSS の考え方。", @@ -607,6 +680,7 @@ }, { "id": "responsive-web-design", + "category": "html-css", "term": "responsive web design", "match": ["レスポンシブウェブデザイン", "responsive design", "レスポンシブデザイン", "RWD"], "short": "画面の大きさに応じてレイアウトを調整する設計手法。", @@ -616,6 +690,7 @@ }, { "id": "npm", + "category": "development", "term": "npm", "match": ["エヌピーエム"], "short": "Node.js のパッケージを管理するためのツール。", @@ -625,6 +700,7 @@ }, { "id": "ubuntu", + "category": "development", "term": "Ubuntu", "match": ["ウブンツ"], "short": "使いやすさを重視して作られた Linux ディストリビューション。", @@ -634,10 +710,839 @@ }, { "id": "component", + "category": "frameworks", "term": "component", "match": ["コンポーネント", "UI component", "UIコンポーネント"], "short": "画面を構成する再利用可能な部品。", "detail": "ボタン、入力欄、ヘッダーなどを独立した単位として作り、組み合わせて画面を構築する。見た目や処理をまとめることで保守しやすくなる。", "example": "" + }, + { + "id": "variable", + "category": "basics", + "term": "variable", + "match": [ + "変数", + "バリアブル" + ], + "short": "値に名前を付けて、プログラムの中で参照するためのもの。", + "detail": "言語や宣言方法によって、あとから別の値を代入できるかどうかが異なる。", + "example": "let score = 10;" + }, + { + "id": "constant", + "category": "basics", + "term": "constant", + "match": [ + "定数" + ], + "short": "変更しない値や、再代入できない名前。", + "detail": "JavaScriptのconstは再代入を禁止する。オブジェクトや配列の中身まで変更できなくなるわけではない。", + "example": "const limit = 100;" + }, + { + "id": "function", + "category": "basics", + "term": "function", + "match": [ + "関数", + "ファンクション" + ], + "short": "ひとまとまりの処理に名前などを付け、繰り返し使えるようにしたもの。", + "detail": "引数で値を受け取り、戻り値として結果を返すことができる。", + "example": "function double(n) { return n * 2; }" + }, + { + "id": "argument", + "category": "basics", + "term": "argument", + "match": [ + "引数", + "アーギュメント" + ], + "short": "関数を呼び出すときに渡す値。", + "detail": "関数を定義するときの受け取り口は仮引数(parameter)、呼び出すときの値は実引数(argument)と区別することもある。", + "example": "Math.max(3, 7);" + }, + { + "id": "parameter", + "category": "basics", + "term": "parameter", + "match": [ + "仮引数", + "パラメータ", + "パラメーター" + ], + "short": "関数の定義で、呼び出し時の値を受け取るために付ける名前。", + "detail": "一般には設定値や処理に与える条件を指す場合もある。", + "example": "function greet(name) { return name; }" + }, + { + "id": "return-value", + "category": "basics", + "term": "return value", + "match": [ + "戻り値", + "返り値" + ], + "short": "関数が処理の結果として呼び出し元に返す値。", + "detail": "JavaScriptではreturnで指定する。値を返さず終了した関数の戻り値はundefinedになる。", + "example": "const result = Math.max(3, 7);" + }, + { + "id": "array", + "category": "basics", + "term": "array", + "match": [ + "配列", + "アレイ" + ], + "short": "複数の値を順番に並べてまとめたもの。", + "detail": "各要素を位置を示す番号で取り出す。JavaScriptでは先頭の番号は0。", + "example": "const colors = ['red', 'blue'];" + }, + { + "id": "string", + "category": "basics", + "term": "string", + "match": [ + "文字列", + "ストリング" + ], + "short": "文字や記号を並べたデータ。", + "detail": "JavaScriptでは引用符などで囲んで書く。数字でも引用符で囲むと数値とは異なる文字列になる。", + "example": "const message = 'Hello';" + }, + { + "id": "boolean", + "category": "basics", + "term": "boolean", + "match": [ + "真偽値", + "ブーリアン" + ], + "short": "trueかfalseのどちらかを表すデータ型。", + "detail": "条件の成立・不成立や、機能が有効かどうかなどを表す。", + "example": "const isReady = true;" + }, + { + "id": "conditional", + "category": "basics", + "term": "conditional statement", + "match": [ + "条件分岐" + ], + "short": "条件に応じて実行する処理を変えること。", + "detail": "ifやswitchなどを使い、入力や状態によって処理を選ぶ。", + "example": "if (score >= 60) { console.log('合格'); }" + }, + { + "id": "loop", + "category": "basics", + "term": "loop", + "match": [ + "ループ", + "繰り返し処理" + ], + "short": "同じ処理を繰り返し実行する仕組み。", + "detail": "forやwhileなどを使う。終了条件が満たされないと無限ループになることがある。", + "example": "for (let i = 0; i < 3; i++) { console.log(i); }" + }, + { + "id": "scope", + "category": "basics", + "term": "scope", + "match": [ + "スコープ", + "有効範囲" + ], + "short": "変数などの名前を参照できる範囲。", + "detail": "JavaScriptでは関数やブロックが範囲を作る。letやconstで宣言した名前は、そのブロック内で参照できる。" + }, + { + "id": "null", + "category": "basics", + "term": "null", + "match": [ + "ヌル" + ], + "short": "値が存在しないことを意図的に表す値。", + "detail": "JavaScriptでは、まだ選択されていない項目などを明示するときに使う。undefinedとは別の値。", + "example": "const selectedUser = null;" + }, + { + "id": "undefined", + "category": "basics", + "term": "undefined", + "match": [ + "アンディファインド" + ], + "short": "JavaScriptで値が未設定であることなどを表す値。", + "detail": "未代入の変数、存在しないプロパティ、値を返さない関数の戻り値などに現れる。", + "example": "let result;" + }, + { + "id": "exception", + "category": "basics", + "term": "exception", + "match": [ + "例外", + "エクセプション" + ], + "short": "通常の処理を続けられない状況を知らせる仕組み。", + "detail": "JavaScriptではthrowで発生させ、tryとcatchで処理できる。" + }, + { + "id": "recursion", + "category": "basics", + "term": "recursion", + "match": [ + "再帰", + "リカージョン" + ], + "short": "関数が自分自身を呼び出して問題を解く方法。", + "detail": "小さな問題に分けて処理する。呼び出しを止める条件が必要。" + }, + { + "id": "operator", + "category": "basics", + "term": "operator", + "match": [ + "演算子" + ], + "short": "値の計算や比較などの操作を表す記号。", + "detail": "足し算の+、比較の===、論理演算の&&などがある。", + "example": "const total = 2 + 3;" + }, + { + "id": "data-type", + "category": "basics", + "term": "data type", + "match": [ + "データ型" + ], + "short": "値の種類や、その値にできる操作を表す分類。", + "detail": "数値、文字列、真偽値などがある。言語によって型の扱いや検査の方法が異なる。" + }, + { + "id": "callback", + "category": "javascript", + "term": "callback", + "match": [ + "コールバック", + "コールバック関数" + ], + "short": "別の処理に渡し、その処理から呼び出してもらう関数。", + "detail": "イベント処理や配列操作などに使う。コールバックが必ず非同期で呼ばれるとは限らない。", + "example": "[1, 2].map(n => n * 2);" + }, + { + "id": "asynchronous", + "category": "javascript", + "term": "asynchronous", + "match": [ + "非同期処理", + "非同期", + "async processing" + ], + "short": "処理の完了を待つ間に、ほかの処理を進められる仕組み。", + "detail": "通信やタイマーなどで使う。同時に複数のCPUで実行する並列処理とは同じ意味ではない。" + }, + { + "id": "async-await", + "category": "javascript", + "term": "async/await", + "match": [ + "async await", + "アシンクアウェイト" + ], + "short": "Promiseを使う非同期処理を、順番に読む形で書く構文。", + "detail": "async関数はPromiseを返す。awaitはその処理を一時停止して結果を待つが、JavaScript全体を停止するわけではない。", + "example": "const response = await fetch('/api/users');" + }, + { + "id": "arrow-function", + "category": "javascript", + "term": "arrow function", + "match": [ + "アロー関数" + ], + "short": "矢印の記号を使って関数を書く構文。", + "detail": "通常の関数とはthisの扱いが異なり、外側のthisを引き継ぐ。", + "example": "const double = n => n * 2;" + }, + { + "id": "destructuring", + "category": "javascript", + "term": "destructuring", + "match": [ + "分割代入" + ], + "short": "配列やオブジェクトから値を取り出し、変数にまとめて代入する構文。", + "detail": "必要な要素やプロパティを短く取り出せる。", + "example": "const { name, age } = user;" + }, + { + "id": "spread-syntax", + "category": "javascript", + "term": "spread syntax", + "match": [ + "スプレッド構文" + ], + "short": "配列の要素やオブジェクトのプロパティなどを展開する構文。", + "detail": "コピーや結合に使えるが、入れ子のオブジェクトまで複製する深いコピーではない。", + "example": "const copy = [...items];" + }, + { + "id": "optional-chaining", + "category": "javascript", + "term": "optional chaining", + "match": [ + "オプショナルチェーン", + "オプショナルチェイニング" + ], + "short": "途中の値がnullやundefinedでもエラーにせずアクセスを止める構文。", + "detail": "その場合の結果はundefinedになる。存在するか分からないプロパティを参照するときに使う。", + "example": "const name = user?.profile?.name;" + }, + { + "id": "type-inference", + "category": "javascript", + "term": "type inference", + "match": [ + "型推論" + ], + "short": "明示的に書かれていない型を、値や使い方から推定する仕組み。", + "detail": "TypeScriptでは初期値などから型が決まるため、すべての変数に型注釈を書く必要はない。", + "example": "const count = 3;" + }, + { + "id": "union-type", + "category": "javascript", + "term": "union type", + "match": [ + "ユニオン型", + "共用体型" + ], + "short": "複数の型のいずれかを受け付ける型。", + "detail": "TypeScriptでは縦棒で型を並べる。使うときは型を絞り込む必要がある場合がある。" + }, + { + "id": "event-loop", + "category": "javascript", + "term": "event loop", + "match": [ + "イベントループ" + ], + "short": "イベントや非同期処理のコールバックを実行する順序を管理する仕組み。", + "detail": "JavaScriptの実行中に長い同期処理を行うと、ほかのイベント処理や画面更新が遅れる。" + }, + { + "id": "props", + "category": "frameworks", + "term": "props", + "match": [ + "プロップス" + ], + "short": "親コンポーネントから子コンポーネントに渡す入力。", + "detail": "Reactでは子側で直接書き換えず、渡された値を使って表示や動作を決める。", + "example": "