From e3880e45311d28e77d9398a2be86d649ef5a7a83 Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:47:26 +0900 Subject: [PATCH 01/53] =?UTF-8?q?Chrome=20Web=20Store=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E5=85=AC=E9=96=8B=E3=82=92=E8=A8=98=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 4e939b1..f4b2483 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,7 @@ -# Web Extension for NITech Moodle 4.0 +# NITech Moodle Extension (40a) +Web Extension for NITech Moodle 4.0 + +[Chrome Web Storeで公開中](https://chromewebstore.google.com/detail/nitech-moodle-extension-4/gghacnecolaclhlihmlhffgkmeojehff) [![release](https://img.shields.io/github/v/release/nitech-create/nitech-moodle-extension-40a?include_prereleases)](https://github.com/nitech-create/nitech-moodle-extension-40a/releases/latest) @@ -8,8 +11,6 @@ 名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 -Web Extension for Moodle 4.0 of NITech. - ### 主な機能 - ダッシュボード ( トップページ ) に講義へのショートカットアクセスを追加 @@ -34,7 +35,7 @@ Web Extension for Moodle 4.0 of NITech. ### Chrome Web Store からインストール -準備中です +[Chrome Web Storeで公開中](https://chromewebstore.google.com/detail/nitech-moodle-extension-4/gghacnecolaclhlihmlhffgkmeojehff) ### GitHub からインストール From f531d309b8ece29d1dc194cd69eb78dbb9ada8a2 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 21 Oct 2024 21:45:36 +0900 Subject: [PATCH 02/53] fix: replace import assertion from "assert" to "with" --- esbuild.common.ts | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/esbuild.common.ts b/esbuild.common.ts index 88229ae..7c28c33 100644 --- a/esbuild.common.ts +++ b/esbuild.common.ts @@ -2,8 +2,8 @@ import * as esbuild from 'esbuild'; import { posix } from 'posix'; import JSON5 from 'json5'; import type ManifestType from './src/manifestType.ts'; -import denoConfig from './deno.json' assert { type: 'json' }; -import importmap from './import_map.json' assert { type: 'json' }; +import denoConfig from './deno.json' with { type: 'json' }; +import importmap from './import_map.json' with { type: 'json' }; import sassPlugin from 'esbuild-plugin-sass'; import { esbuildCachePlugin } from 'esbuild-cache-plugin'; @@ -16,25 +16,33 @@ const destPath = 'dist'; const cachePath = 'cache'; const manifest = JSON5.parse( - Deno.readTextFileSync(posix.resolve(srcPath, 'manifest.json5')) + Deno.readTextFileSync(posix.resolve(srcPath, 'manifest.json5')), ) as ManifestType; -const contentScripts = Array.from(new Set( - manifest['content_scripts'] - .flatMap((entry) => entry['js'] ?? []) - .map((path) => posix.resolve(srcPath, path.replace(/\.js$/, '.ts'))) -)); +const contentScripts = Array.from( + new Set( + manifest['content_scripts'] + .flatMap((entry) => entry['js'] ?? []) + .map((path) => posix.resolve(srcPath, path.replace(/\.js$/, '.ts'))), + ), +); -const contentStyleSheets = Array.from(new Set( - manifest['content_scripts'] - .flatMap((entry) => entry['css'] ?? []) - .map((path) => posix.resolve(srcPath, path.replace(/\.css$/, '.scss'))) -)); +const contentStyleSheets = Array.from( + new Set( + manifest['content_scripts'] + .flatMap((entry) => entry['css'] ?? []) + .map((path) => posix.resolve(srcPath, path.replace(/\.css$/, '.scss'))), + ), +); const optionsResources = [ manifest['options_ui']['page'], - ...(manifest['options_ui']['js'] ?? []).map((path) => path.replace(/\.js$/, '.ts')), - ...(manifest['options_ui']['css'] ?? []).map((path) => path.replace(/\.css$/, '.scss')), + ...(manifest['options_ui']['js'] ?? []).map((path) => + path.replace(/\.js$/, '.ts') + ), + ...(manifest['options_ui']['css'] ?? []).map((path) => + path.replace(/\.css$/, '.scss') + ), ].map((path) => posix.resolve(srcPath, path)); // Reflect.deleteProperty と違って Typescript の型チェックが効く @@ -71,13 +79,14 @@ const config: Partial = { baseOutDir: destPath, files: [ { from: 'imgs/*', to: 'imgs/[name][ext]' }, - ] + ], }), resultPlugin(), ], logOverride: { 'unsupported-jsx-comment': 'silent', }, -} +}; + +export default config; -export default config; \ No newline at end of file From 3fb1fb2541786b3390096b6e13f9ac5840aa42b8 Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:42:17 +0900 Subject: [PATCH 03/53] Create privacy_policies.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 拡張機能の申請に必要であるため、プライバシーポリシーを追加。 --- privacy_policies.md | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 privacy_policies.md diff --git a/privacy_policies.md b/privacy_policies.md new file mode 100644 index 0000000..fd8431c --- /dev/null +++ b/privacy_policies.md @@ -0,0 +1,67 @@ +# Privacy policy for Chrome extensions +## Chrome拡張のプライバシーポリシー + +本プライバシーポリシーは、nitechCreate(以下、「当開発者」)が開発したGoogle Chromeの拡張機能(Extension)(以下、「拡張機能」とします。)の利用において、利用者の個人情報もしくはそれに準ずる情報を取り扱う際に、当開発者が遵守する方針を示したものです。 + +### 基本方針 +当開発者は、個人情報の重要性を認識し、個人情報を保護することが社会的責務であると考え、個人情報に関する法令を遵守し、拡張機能で取扱う個人情報の取得、利用、管理を適正に行います。 + +### 適用範囲 +本プライバシーポリシーは、当開発者が開発した拡張機能においてのみ適用されます。 + +### 個人情報の取得と利用目的 +当開発者は、個人情報を収集する機能を要した拡張機能を公開しません。 +**ただし、一部個人情報を利用者のブラウザのLocalStorageに保存します。** + +#### 取得方法 +特定のWebサイトへアクセスした際に授業などの情報を取得する。 + +#### 利用目的 +##### 利便性の向上 +拡張機能では、機能の内容により、下記の情報を **「ブラウザのLocalStorage」へ保存します**。 +- 拡張機能で用いる授業等の情報 + +これにより、拡張機能の次回利用時などに授業等の情報が保存されているため、欠落があっても表示されて利便性が向上します。 +**個人情報は当開発者に送信されず拡張機能利用者のブラウザのLocalStorageに保存されます**。 + +##### 保存期間について +拡張機能内でデータの扱いとして、LocalStorageを使用します。 +LocalStorageには保存期間が存在しないためデータの保存期間は拡張機能のアンインストール時までとします。 + +#### 個人情報の取り扱いの同意について +当開発者が開発を行った拡張機能では、拡張機能のインストールを行う前に、当プライバシーポリシーをご一読頂くようにお願いします。 +インストールをされた時点で、当プライバシーポリシーに同意されたとみなします。 + +#### Cookieによる個人情報の取得 +拡張機能では、Cookieを利用することがあります。 +Cookie(クッキー)とは、ウェブサイトを利用したときに、ブラウザとサーバーとの間で送受信した利用履歴や入力内容などを、訪問者のコンピュータにファイルとして保存しておく仕組みです。 + +##### 利用目的について +拡張機能の利用者の利便性を向上するために活用します。 +ログイン処理や画面遷移を拡張機能から行う際に、Cookieを活用することでユーザーの手間を省いてデータの処理が可能となります。 +なお、拡張機能ではプライバシー保護のため、拡張機能の目的とする情報以外のCookieを送信しません。 + +##### 保存期間について +Cookieの保存期間は利用者のブラウザーにて設定されているデフォルトの期間保存されます。 + +### 個人情報の管理 +当開発者は、拡張機能内における個人情報の管理について、以下を徹底します。 + +#### 情報の正確性の確保 +利用者が入力したデータにおいて、常に正しい情報を保持します。 + +#### 安全管理措置 +拡張機能において、情報の漏洩、滅失を防止するために拡張機能内において利用目的外のサーバーへの情報送信を行いません。 + +#### 個人情報の第三者への提供について +当開発者の開発する拡張機能は、利用者から提供いただいた個人情報を、訪問者本人の同意を得ることなく第三者に提供することはありません。 +また、今後第三者提供を行うことになった場合には、提供する情報と提供目的などを提示し、訪問者から同意を得た場合のみ第三者提供を行います。 + +#### 問い合わせ先 +拡張機能、又は個人情報の取扱いに関しては、下記の連絡先よりお問い合わせください。 + +X: https://x.com/nitechCreate (DMやリプライ) +お問い合わせフォーム: https://forms.gle/obR3yYBi3q5jH3KW8 + +## 策定日 +2024年10月22日 策定 From 8b8a4b6ff345f617cb063a1e6a4a16331c5bb20d Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Wed, 23 Oct 2024 00:07:10 +0900 Subject: [PATCH 04/53] ci: upgrade Deno version in GitHub Actions --- .github/workflows/lint-fmt-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-fmt-test.yml b/.github/workflows/lint-fmt-test.yml index 82cdfa4..b06de7c 100644 --- a/.github/workflows/lint-fmt-test.yml +++ b/.github/workflows/lint-fmt-test.yml @@ -14,9 +14,9 @@ jobs: - uses: actions/checkout@v3 - name: Setup Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: Lint run: deno lint From b20f3c43dc926de9b34059ba9d1db9fa8540e8af Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 21 Oct 2024 21:52:48 +0900 Subject: [PATCH 05/53] chore: merge esbuild option files --- build.ts | 14 ++++++-------- esbuild.common.ts => buildOptions.ts | 6 +++--- esbuild.dev.ts | 9 --------- esbuild.prod.ts | 10 ---------- 4 files changed, 9 insertions(+), 30 deletions(-) rename esbuild.common.ts => buildOptions.ts (95%) delete mode 100644 esbuild.dev.ts delete mode 100644 esbuild.prod.ts diff --git a/build.ts b/build.ts index faba26c..fde79c2 100644 --- a/build.ts +++ b/build.ts @@ -1,22 +1,20 @@ import * as esbuild from 'esbuild'; -import { Command } from "cliffy"; -import { readLines } from "std/io/mod.ts"; -import devConfig from './esbuild.dev.ts'; -import prodConfig from './esbuild.prod.ts'; +import { Command } from 'cliffy'; +import { readLines } from 'std/io/mod.ts'; +import config from './buildOptions.ts'; const { options, args } = await new Command() .option('-d, --dev', 'development mode') .option('-w, --watch', 'watch mode (development only)') .parse(Deno.args); -const config = options.dev ? devConfig : prodConfig; -const ctx = await esbuild.context(config); +const ctx = await esbuild.context(config(options.dev === true)); -if(options.dev && options.watch) { +if (options.dev && options.watch) { await ctx.watch(); console.log('Watching...'); - for await(const _ of readLines(Deno.stdin)) { + for await (const _ of readLines(Deno.stdin)) { // manually rebuild await ctx.rebuild().catch(() => {}); } diff --git a/esbuild.common.ts b/buildOptions.ts similarity index 95% rename from esbuild.common.ts rename to buildOptions.ts index 7c28c33..d0dddc1 100644 --- a/esbuild.common.ts +++ b/buildOptions.ts @@ -49,7 +49,7 @@ const optionsResources = [ delete manifest.options_ui.js; delete manifest.options_ui.css; -const config: Partial = { +const config: Partial = (dev = false) => ({ entryPoints: [ ...contentScripts, ...contentStyleSheets, @@ -86,7 +86,7 @@ const config: Partial = { logOverride: { 'unsupported-jsx-comment': 'silent', }, -}; + sourcemap: dev ? 'inline' : 'linked', +}); export default config; - diff --git a/esbuild.dev.ts b/esbuild.dev.ts deleted file mode 100644 index 42e02f8..0000000 --- a/esbuild.dev.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as esbuild from 'esbuild'; -import commonConfig from './esbuild.common.ts'; - -const config: esbuild.BuildOptions = { - ...commonConfig, - sourcemap: 'inline', -} - -export default config; diff --git a/esbuild.prod.ts b/esbuild.prod.ts deleted file mode 100644 index fccb751..0000000 --- a/esbuild.prod.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as esbuild from 'esbuild'; -import commonConfig from './esbuild.common.ts'; - -const config: esbuild.BuildOptions = { - ...commonConfig, - minify: true, - sourcemap: 'linked', -} - -export default config; From 5b7731c73417181ed15421c5b3a4ab5e8526323d Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 21 Oct 2024 22:01:37 +0900 Subject: [PATCH 06/53] chore: migrate stds to jsr.io --- build.ts | 17 ++--------- buildOptions.ts | 13 +++++---- deno.json | 3 +- deno.lock | 57 ++++++++++++++----------------------- import_map.json | 6 ++-- plugins/objectExportJSON.ts | 17 ++++++----- 6 files changed, 48 insertions(+), 65 deletions(-) diff --git a/build.ts b/build.ts index fde79c2..103b21c 100644 --- a/build.ts +++ b/build.ts @@ -1,6 +1,5 @@ import * as esbuild from 'esbuild'; import { Command } from 'cliffy'; -import { readLines } from 'std/io/mod.ts'; import config from './buildOptions.ts'; const { options, args } = await new Command() @@ -10,17 +9,7 @@ const { options, args } = await new Command() const ctx = await esbuild.context(config(options.dev === true)); -if (options.dev && options.watch) { - await ctx.watch(); - console.log('Watching...'); +// TODO: implement watch mode +await ctx.rebuild(); - for await (const _ of readLines(Deno.stdin)) { - // manually rebuild - await ctx.rebuild().catch(() => {}); - } -} else { - // just build - await ctx.rebuild(); -} - -esbuild.stop(); +await esbuild.stop(); diff --git a/buildOptions.ts b/buildOptions.ts index d0dddc1..4f6e6d4 100644 --- a/buildOptions.ts +++ b/buildOptions.ts @@ -1,5 +1,6 @@ +import * as path from '@std/path'; + import * as esbuild from 'esbuild'; -import { posix } from 'posix'; import JSON5 from 'json5'; import type ManifestType from './src/manifestType.ts'; import denoConfig from './deno.json' with { type: 'json' }; @@ -16,14 +17,14 @@ const destPath = 'dist'; const cachePath = 'cache'; const manifest = JSON5.parse( - Deno.readTextFileSync(posix.resolve(srcPath, 'manifest.json5')), + Deno.readTextFileSync(path.resolve(srcPath, 'manifest.json5')), ) as ManifestType; const contentScripts = Array.from( new Set( manifest['content_scripts'] .flatMap((entry) => entry['js'] ?? []) - .map((path) => posix.resolve(srcPath, path.replace(/\.js$/, '.ts'))), + .map((file) => path.resolve(srcPath, file.replace(/\.js$/, '.ts'))), ), ); @@ -31,7 +32,7 @@ const contentStyleSheets = Array.from( new Set( manifest['content_scripts'] .flatMap((entry) => entry['css'] ?? []) - .map((path) => posix.resolve(srcPath, path.replace(/\.css$/, '.scss'))), + .map((file) => path.resolve(srcPath, file.replace(/\.css$/, '.scss'))), ), ); @@ -43,7 +44,7 @@ const optionsResources = [ ...(manifest['options_ui']['css'] ?? []).map((path) => path.replace(/\.css$/, '.scss') ), -].map((path) => posix.resolve(srcPath, path)); +].map((file) => path.resolve(srcPath, file)); // Reflect.deleteProperty と違って Typescript の型チェックが効く delete manifest.options_ui.js; @@ -70,7 +71,7 @@ const config: Partial = (dev = false) => ({ }), objectExportJSONPlugin({ targets: [ - { value: manifest, filename: posix.resolve(destPath, 'manifest.json') }, + { value: manifest, filename: path.resolve(destPath, 'manifest.json') }, ], }), sassPlugin(), diff --git a/deno.json b/deno.json index 2a36a7e..c271a1a 100644 --- a/deno.json +++ b/deno.json @@ -37,6 +37,7 @@ "useTabs": false } }, + "import_map": "import_map.json", "tasks": { "build": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net --importmap import_map.json ./build.ts", "dev": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net --importmap import_map.json ./build.ts --dev", @@ -47,4 +48,4 @@ "check-version-increase": "bash ./scripts/checkVersionIncrease.sh", "setup-vscode": "deno run --allow-run --allow-read --allow-write scripts/setup-vscode.ts" } -} \ No newline at end of file +} diff --git a/deno.lock b/deno.lock index 26e2035..715f2e5 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,26 @@ { - "version": "2", + "version": "4", + "specifiers": { + "jsr:@std/fs@^1.0.4": "1.0.4", + "jsr:@std/path@^1.0.6": "1.0.6", + "npm:json5@2.2.3": "2.2.3" + }, + "jsr": { + "@std/fs@1.0.4": { + "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", + "dependencies": [ + "jsr:@std/path" + ] + }, + "@std/path@1.0.6": { + "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" + } + }, + "npm": { + "json5@2.2.3": { + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + } + }, "remote": { "https://deno.land/std@0.131.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", "https://deno.land/std@0.131.0/_util/os.ts": "49b92edea1e82ba295ec946de8ffd956ed123e2948d9bd1d3e901b04e4307617", @@ -55,9 +76,6 @@ "https://deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", "https://deno.land/std@0.184.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", "https://deno.land/std@0.184.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.184.0/bytes/bytes_list.ts": "31d664f4d42fa922066405d0e421c56da89d751886ee77bbe25a88bf0310c9d0", - "https://deno.land/std@0.184.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2", - "https://deno.land/std@0.184.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", "https://deno.land/std@0.184.0/fs/_util.ts": "579038bebc3bd35c43a6a7766f7d91fbacdf44bc03468e9d3134297bb99ed4f9", "https://deno.land/std@0.184.0/fs/copy.ts": "14214efd94fc3aa6db1e4af2b4b9578e50f7362b7f3725d5a14ad259a5df26c8", "https://deno.land/std@0.184.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", @@ -71,23 +89,6 @@ "https://deno.land/std@0.184.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", "https://deno.land/std@0.184.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", "https://deno.land/std@0.184.0/fs/walk.ts": "920be35a7376db6c0b5b1caf1486fb962925e38c9825f90367f8f26b5e5d0897", - "https://deno.land/std@0.184.0/io/buf_reader.ts": "abeb92b18426f11d72b112518293a96aef2e6e55f80b84235e8971ac910affb5", - "https://deno.land/std@0.184.0/io/buf_writer.ts": "48c33c8f00b61dcbc7958706741cec8e59810bd307bc6a326cbd474fe8346dfd", - "https://deno.land/std@0.184.0/io/buffer.ts": "17f4410eaaa60a8a85733e8891349a619eadfbbe42e2f319283ce2b8f29723ab", - "https://deno.land/std@0.184.0/io/copy_n.ts": "0cc7ce07c75130f6fc18621ec1911c36e147eb9570664fee0ea12b1988167590", - "https://deno.land/std@0.184.0/io/limited_reader.ts": "6c9a216f8eef39c1ee2a6b37a29372c8fc63455b2eeb91f06d9646f8f759fc8b", - "https://deno.land/std@0.184.0/io/mod.ts": "2665bcccc1fd6e8627cca167c3e92aaecbd9897556b6f69e6d258070ef63fd9b", - "https://deno.land/std@0.184.0/io/multi_reader.ts": "9c2a0a31686c44b277e16da1d97b4686a986edcee48409b84be25eedbc39b271", - "https://deno.land/std@0.184.0/io/read_delim.ts": "c02b93cc546ae8caad8682ae270863e7ace6daec24c1eddd6faabc95a9d876a3", - "https://deno.land/std@0.184.0/io/read_int.ts": "7cb8bcdfaf1107586c3bacc583d11c64c060196cb070bb13ae8c2061404f911f", - "https://deno.land/std@0.184.0/io/read_lines.ts": "c526c12a20a9386dc910d500f9cdea43cba974e853397790bd146817a7eef8cc", - "https://deno.land/std@0.184.0/io/read_long.ts": "f0aaa420e3da1261c5d33c5e729f09922f3d9fa49f046258d4ff7a00d800c71e", - "https://deno.land/std@0.184.0/io/read_range.ts": "28152daf32e43dd9f7d41d8466852b0d18ad766cd5c4334c91fef6e1b3a74eb5", - "https://deno.land/std@0.184.0/io/read_short.ts": "805cb329574b850b84bf14a92c052c59b5977a492cd780c41df8ad40826c1a20", - "https://deno.land/std@0.184.0/io/read_string_delim.ts": "5dc9f53bdf78e7d4ee1e56b9b60352238ab236a71c3e3b2a713c3d78472a53ce", - "https://deno.land/std@0.184.0/io/slice_long_to_bytes.ts": "48d9bace92684e880e46aa4a2520fc3867f9d7ce212055f76ecc11b22f9644b7", - "https://deno.land/std@0.184.0/io/string_reader.ts": "da0f68251b3d5b5112485dfd4d1b1936135c9b4d921182a7edaf47f74c25cc8f", - "https://deno.land/std@0.184.0/io/string_writer.ts": "8a03c5858c24965a54c6538bed15f32a7c72f5704a12bda56f83a40e28e5433e", "https://deno.land/std@0.184.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", "https://deno.land/std@0.184.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", "https://deno.land/std@0.184.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", @@ -97,7 +98,6 @@ "https://deno.land/std@0.184.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", "https://deno.land/std@0.184.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", "https://deno.land/std@0.184.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", - "https://deno.land/std@0.184.0/types.d.ts": "dbaeb2c4d7c526db9828fc8df89d8aecf53b9ced72e0c4568f97ddd8cda616a4", "https://deno.land/std@0.97.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", "https://deno.land/std@0.97.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7", "https://deno.land/std@0.97.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e", @@ -107,7 +107,6 @@ "https://deno.land/std@0.97.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00", "https://deno.land/std@0.97.0/hash/_wasm/hash.ts": "cb6ad1ab429f8ac9d6eae48f3286e08236d662e1a2e5cfd681ba1c0f17375895", "https://deno.land/std@0.97.0/hash/_wasm/wasm.js": "94b1b997ae6fb4e6d2156bcea8f79cfcd1e512a91252b08800a92071e5e84e1a", - "https://deno.land/std@0.97.0/hash/hasher.ts": "57a9ec05dd48a9eceed319ac53463d9873490feea3832d58679df6eec51c176b", "https://deno.land/std@0.97.0/hash/mod.ts": "5d032bd34186cda2f8d17fc122d621430953a6030d4b3f11172004715e3e2441", "https://deno.land/std@0.97.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", "https://deno.land/std@0.97.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", @@ -215,7 +214,6 @@ "https://deno.land/x/denosass@1.0.6/src/mod.ts": "d2b63172f98238f77831995a5d6c8a06af5252ad8fbe7b9ec40b60eae86f2164", "https://deno.land/x/denosass@1.0.6/src/types/module.types.ts": "7a5027482ded1d2967fbe690ef8f928446c5de8811c3333f9b09ae6e8122f9ba", "https://deno.land/x/denosass@1.0.6/src/wasm/grass.deno.js": "a72432ce8d6b8f9c31e31c71415fdca03fe36aa22417e414bc81e2e21a8a687b", - "https://deno.land/x/esbuild@v0.17.18/mod.d.ts": "dc279a3a46f084484453e617c0cabcd5b8bd1920c0e562e4ea02dfc828c8f968", "https://deno.land/x/esbuild@v0.17.18/mod.js": "84b5044def8a2e94770b79d295a1dd74f5ee453514c5b4f33571e22e1c882898", "https://deno.land/x/esbuild_cache_plugin@v0.3.1/deps.ts": "379625c0bb9767b2229cbe3c316a249916f4611a9520fcf1f91069384b654923", "https://deno.land/x/esbuild_cache_plugin@v0.3.1/mod.ts": "cbcfaca866ca4ba8c729633239c3da811ac4c799d8411aea04dcd2e4c323bdb3", @@ -225,16 +223,5 @@ "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/mod.ts": "fb11c4ee4102510bfc58811cd3567f11df746b009b0bacc7b1110f79409a38a3", "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/deps.ts": "c0397e0f91fbef8fd389829fa905af5d9ca53e14ae0d5da6cbdf2cd498ee05ab", "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/mod.ts": "a83c499a6801370d493d2572b059fd43b46921c30a2827b1122f13d9e6212c5d" - }, - "npm": { - "specifiers": { - "json5@2.2.3": "json5@2.2.3" - }, - "packages": { - "json5@2.2.3": { - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dependencies": {} - } - } } } diff --git a/import_map.json b/import_map.json index 57b185b..82f779c 100644 --- a/import_map.json +++ b/import_map.json @@ -1,5 +1,8 @@ { "imports": { + "@std/fs": "jsr:@std/fs@^1.0.4", + "@std/path": "jsr:@std/path@^1.0.6", + "cliffy": "https://deno.land/x/cliffy@v0.25.7/mod.ts", "esbuild": "https://deno.land/x/esbuild@v0.17.18/mod.js", "esbuild-cache-plugin": "https://deno.land/x/esbuild_cache_plugin@v0.3.1/mod.ts", @@ -15,7 +18,6 @@ "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", "preact/jsx-dev-runtime": "https://esm.sh/preact@10.13.2/jsx-dev-runtime", "preact/types": "https://raw.githubusercontent.com/preactjs/preact/10.13.2/src/index.d.ts", - "std/": "https://deno.land/std@0.184.0/", "webextension-polyfill": "https://esm.sh/webextension-polyfill@0.10.0" } -} \ No newline at end of file +} diff --git a/plugins/objectExportJSON.ts b/plugins/objectExportJSON.ts index 3965e44..7d9272e 100644 --- a/plugins/objectExportJSON.ts +++ b/plugins/objectExportJSON.ts @@ -1,9 +1,10 @@ +import * as fs from '@std/fs'; +import * as path from '@std/path'; + import * as esbuild from 'esbuild'; -import * as posix from 'posix'; -import * as fs from 'std/fs/mod.ts'; interface Option { - targets: { value: any, filename: string }[] + targets: { value: any; filename: string }[]; } const objectExportJSONPlugin = (option: Option): esbuild.Plugin => ({ @@ -12,16 +13,18 @@ const objectExportJSONPlugin = (option: Option): esbuild.Plugin => ({ build.onStart(async () => { const writePromises: Promise[] = []; - for(const target of option.targets) { + for (const target of option.targets) { const content = JSON.stringify(target.value); - writePromises.push(fs.ensureDir(posix.dirname(target.filename)) - .then(() => Deno.writeTextFile(target.filename, content))); + writePromises.push( + fs.ensureDir(path.dirname(target.filename)) + .then(() => Deno.writeTextFile(target.filename, content)), + ); } await Promise.all(writePromises); }); - } + }, }); export default objectExportJSONPlugin; From 57b7897c0e7b276b7a16acbd0efddb7d5834da52 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Wed, 23 Oct 2024 23:45:21 +0900 Subject: [PATCH 07/53] build: re-create build script --- build.ts | 15 -- build/build.ts | 88 +++++++++++ build/manifest.ts | 103 ++++++++++++ build/options/copy.ts | 18 +++ build/options/javascript.ts | 27 ++++ build/options/stylesheet.ts | 23 +++ build/plugins/logBuildResult.ts | 44 ++++++ buildOptions.ts | 93 ----------- deno.json | 30 +++- deno.lock | 271 +++++++++++++++++++++++++++++++- import_map.json | 23 --- src/manifest.json5 | 69 -------- src/manifest.jsonc | 74 +++++++++ src/manifestType.ts | 35 ----- 14 files changed, 670 insertions(+), 243 deletions(-) delete mode 100644 build.ts create mode 100644 build/build.ts create mode 100644 build/manifest.ts create mode 100644 build/options/copy.ts create mode 100644 build/options/javascript.ts create mode 100644 build/options/stylesheet.ts create mode 100644 build/plugins/logBuildResult.ts delete mode 100644 buildOptions.ts delete mode 100644 import_map.json delete mode 100644 src/manifest.json5 create mode 100644 src/manifest.jsonc delete mode 100644 src/manifestType.ts diff --git a/build.ts b/build.ts deleted file mode 100644 index 103b21c..0000000 --- a/build.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as esbuild from 'esbuild'; -import { Command } from 'cliffy'; -import config from './buildOptions.ts'; - -const { options, args } = await new Command() - .option('-d, --dev', 'development mode') - .option('-w, --watch', 'watch mode (development only)') - .parse(Deno.args); - -const ctx = await esbuild.context(config(options.dev === true)); - -// TODO: implement watch mode -await ctx.rebuild(); - -await esbuild.stop(); diff --git a/build/build.ts b/build/build.ts new file mode 100644 index 0000000..078116a --- /dev/null +++ b/build/build.ts @@ -0,0 +1,88 @@ +import * as esbuild from 'esbuild'; +import * as fs from '@std/fs'; +import * as JSONC from '@std/jsonc'; +import * as path from '@std/path'; + +import { Manifest } from './manifest.ts'; +import { buildOptions as jsBuildOptions } from './options/javascript.ts'; +import { buildOptions as cssBuildOptions } from './options/stylesheet.ts'; +import { buildOptions as copyBuildOptions } from './options/copy.ts'; + +const dev = true; + +const srcPath = './src'; +const destPath = './dist'; + +const denoConfigFilePath = path.resolve('deno.json'); +const manifestFilePath = path.resolve(srcPath, 'manifest.jsonc'); + +const denoConfig = JSONC.parse(await Deno.readTextFile(denoConfigFilePath)); +const manifest = Manifest.parse(await Deno.readTextFile(manifestFilePath)); + +const removeExtension = function (name: string) { + return name.slice(0, -path.extname(name).length); +}; + +const jsEntryPointsRaw = manifest.getScripts().map((filename) => ({ + in: filename, + out: `${removeExtension(filename)}.js`, +})); +const cssEntryPointsRaw = manifest.getStylesheets().map((filename) => ({ + in: filename, + out: `${removeExtension(filename)}.css`, +})); +const jsEntryPoints = jsEntryPointsRaw.map((entry) => ({ + in: path.resolve(srcPath, entry.in), + out: path.resolve(destPath, removeExtension(entry.out)), +})); +const cssEntryPoints = cssEntryPointsRaw.map((entry) => ({ + in: path.resolve(srcPath, entry.in), + out: path.resolve(destPath, removeExtension(entry.out)), +})); +const copyEntryPoints = manifest.getFilesToCopy().map((filename) => ({ + in: path.resolve(srcPath, filename), + out: path.resolve(destPath, removeExtension(filename)), +})); + +const resourceReplacementMap = new Map(); +for (const entry of jsEntryPointsRaw) { + resourceReplacementMap.set(entry.in, entry.out); +} +for (const entry of cssEntryPointsRaw) { + resourceReplacementMap.set(entry.in, entry.out); +} +const newManifest = manifest.resourceFileReplaced(resourceReplacementMap); + +const buildOptions = [ + jsBuildOptions({ + entryPoints: jsEntryPoints, + srcPath, + destPath, + dev, + jsxFactory: denoConfig.jsxFactory, + jsxFragmentFactory: denoConfig.jsxFragmentFactory, + }), + cssBuildOptions({ + entryPoints: cssEntryPoints, + srcPath, + destPath, + dev, + }), + copyBuildOptions({ + entryPoints: copyEntryPoints, + srcPath, + destPath, + loaderExts: ['.html'], + }), +]; + +await fs.ensureDir(destPath); +await Promise.all([ + ...buildOptions.map((buildOptions) => { + return esbuild.build(buildOptions); + }), + Deno.writeTextFile( + path.resolve(destPath, 'manifest.json'), + newManifest.stringify(), + ), +]); diff --git a/build/manifest.ts b/build/manifest.ts new file mode 100644 index 0000000..9c1f6a3 --- /dev/null +++ b/build/manifest.ts @@ -0,0 +1,103 @@ +import * as JSONC from '@std/jsonc'; + +export type ContentScript = { + matches: string[]; + js?: string[]; + css?: string[]; + run_at: 'document_start' | 'document_end' | 'document_idle'; + match_about_blank: boolean; + match_origin_as_fallback: boolean; +}; + +export type OptionsUi = { + page: string; + open_in_tab: boolean; + browser_style: boolean; +}; + +export type WebExtensionManifest = { + manifest_version: 3; + name: string; + version: string; + + description: string; + + author: string; + content_scripts: ContentScript[]; + content_security_policy: { + extension_pages?: string; + sandbox?: string; + }; + options_ui: OptionsUi; + + permissions: string[]; + host_permissions: string[]; +}; + +export type ExtendedWebExtensionManifest = WebExtensionManifest & { + options_ui: OptionsUi & { + js?: string[]; + css?: string[]; + }; +}; + +export class Manifest { + private manifest: ExtendedWebExtensionManifest; + + private constructor(manifest: Manifest['manifest']) { + this.manifest = manifest; + } + + static parse(text: string): Manifest { + return new Manifest(JSONC.parse(text) as Manifest['manifest']); + } + + stringify(): string { + return JSON.stringify({ + ...this.manifest, + options_ui: { + ...this.manifest.options_ui, + js: undefined, + css: undefined, + }, + }); + } + + getScripts(): string[] { + return [ + ...this.manifest.content_scripts.flatMap((entry) => entry.js ?? []), + ...this.manifest.options_ui.js ?? [], + ]; + } + + getStylesheets(): string[] { + return [ + ...this.manifest.content_scripts.flatMap((entry) => entry.css ?? []), + ...this.manifest.options_ui.css ?? [], + ]; + } + + getFilesToCopy(): string[] { + return [this.manifest.options_ui.page]; + } + + resourceFileReplaced(map: Map): Manifest { + const manifest = structuredClone(this.manifest); + const newManifest: ExtendedWebExtensionManifest = { + ...manifest, + content_scripts: manifest.content_scripts.map((contentScript) => ({ + ...contentScript, + js: contentScript.js?.map((js) => map.get(js) ?? js), + css: contentScript.css?.map((css) => map.get(css) ?? css), + })), + options_ui: { + ...manifest.options_ui, + page: map.get(manifest.options_ui.page) ?? manifest.options_ui.page, + js: manifest.options_ui.js?.map((js) => map.get(js) ?? js), + css: manifest.options_ui.css?.map((css) => map.get(css) ?? css), + }, + }; + + return new Manifest(newManifest); + } +} diff --git a/build/options/copy.ts b/build/options/copy.ts new file mode 100644 index 0000000..866fcb3 --- /dev/null +++ b/build/options/copy.ts @@ -0,0 +1,18 @@ +import * as esbuild from 'esbuild'; + +type BuildOptionsOptions = { + entryPoints: esbuild.BuildOptions['entryPoints']; + srcPath: string; + destPath: string; + loaderExts: string[]; +}; + +export const buildOptions = ( + options: BuildOptionsOptions, +): esbuild.BuildOptions => ({ + entryPoints: options.entryPoints, + outdir: options.destPath, + loader: Object.fromEntries( + options.loaderExts.map((ext) => [ext, 'copy' as const]), + ), +}); diff --git a/build/options/javascript.ts b/build/options/javascript.ts new file mode 100644 index 0000000..53b4e1c --- /dev/null +++ b/build/options/javascript.ts @@ -0,0 +1,27 @@ +import * as esbuild from 'esbuild'; +import { denoPlugins } from 'esbuild-deno-loader'; + +type BuildOptionsOptions = { + entryPoints: esbuild.BuildOptions['entryPoints']; + srcPath: string; + destPath: string; + dev: boolean; + jsxFactory?: string; + jsxFragmentFactory?: string; +}; + +export const buildOptions = ( + options: BuildOptionsOptions, +): esbuild.BuildOptions => ({ + entryPoints: options.entryPoints, + outdir: options.destPath, + platform: 'browser', + bundle: true, + sourcemap: options.dev ? 'linked' : false, + minify: !options.dev, + jsxFactory: options.jsxFactory, + jsxFragment: options.jsxFragmentFactory, + plugins: [ + ...denoPlugins(), + ], +}); diff --git a/build/options/stylesheet.ts b/build/options/stylesheet.ts new file mode 100644 index 0000000..dd7b374 --- /dev/null +++ b/build/options/stylesheet.ts @@ -0,0 +1,23 @@ +import * as esbuild from 'esbuild'; +import { sassPlugin } from '@tsukina-7mochi/esbuild-plugin-sass'; + +type BuildOptionsOptions = { + entryPoints: esbuild.BuildOptions['entryPoints']; + srcPath: string; + destPath: string; + dev: boolean; +}; + +export const buildOptions = ( + options: BuildOptionsOptions, +): esbuild.BuildOptions => ({ + entryPoints: options.entryPoints, + outdir: options.destPath, + platform: 'browser', + bundle: true, + sourcemap: options.dev ? 'linked' : false, + minify: !options.dev, + plugins: [ + sassPlugin(), + ], +}); diff --git a/build/plugins/logBuildResult.ts b/build/plugins/logBuildResult.ts new file mode 100644 index 0000000..6d971e7 --- /dev/null +++ b/build/plugins/logBuildResult.ts @@ -0,0 +1,44 @@ +import type * as esbuild from 'esbuild'; + +const printLog = function ( + numErrors: number, + numWarnings: number, + startTime: Date, + endTime: Date, +) { + const buildTime = endTime.getTime() - startTime.getTime(); + + let message = `${endTime.toLocaleTimeString('en-GB')}: `; + if (numErrors > 0 && numWarnings > 0) { + message += + `Build failed with ${numErrors} errors and ${numWarnings} warnings`; + } else if (numErrors > 0) { + message += `Build failed with ${numErrors} errors`; + } else if (numWarnings > 0) { + message += `Build failed with ${numWarnings} warnings`; + } else { + message += `Build succeeded`; + } + + message += ` in ${buildTime} ms`; +}; + +export const logBuildResultPlugin = (): esbuild.Plugin => ({ + name: 'log-and-output-filename', + setup(build) { + // assign to avoid type error + let startTime = new Date(); + + build.onStart(() => { + startTime = new Date(); + }); + + build.onEnd((result) => { + const numErrors = result.errors.length; + const numWarnings = result.warnings.length; + const endTime = new Date(); + + printLog(numErrors, numWarnings, startTime, endTime); + }); + }, +}); diff --git a/buildOptions.ts b/buildOptions.ts deleted file mode 100644 index 4f6e6d4..0000000 --- a/buildOptions.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as path from '@std/path'; - -import * as esbuild from 'esbuild'; -import JSON5 from 'json5'; -import type ManifestType from './src/manifestType.ts'; -import denoConfig from './deno.json' with { type: 'json' }; -import importmap from './import_map.json' with { type: 'json' }; - -import sassPlugin from 'esbuild-plugin-sass'; -import { esbuildCachePlugin } from 'esbuild-cache-plugin'; -import copyPlugin from 'esbuild-plugin-copy'; -import resultPlugin from 'esbuild-plugin-result'; -import objectExportJSONPlugin from './plugins/objectExportJSON.ts'; - -const srcPath = 'src'; -const destPath = 'dist'; -const cachePath = 'cache'; - -const manifest = JSON5.parse( - Deno.readTextFileSync(path.resolve(srcPath, 'manifest.json5')), -) as ManifestType; - -const contentScripts = Array.from( - new Set( - manifest['content_scripts'] - .flatMap((entry) => entry['js'] ?? []) - .map((file) => path.resolve(srcPath, file.replace(/\.js$/, '.ts'))), - ), -); - -const contentStyleSheets = Array.from( - new Set( - manifest['content_scripts'] - .flatMap((entry) => entry['css'] ?? []) - .map((file) => path.resolve(srcPath, file.replace(/\.css$/, '.scss'))), - ), -); - -const optionsResources = [ - manifest['options_ui']['page'], - ...(manifest['options_ui']['js'] ?? []).map((path) => - path.replace(/\.js$/, '.ts') - ), - ...(manifest['options_ui']['css'] ?? []).map((path) => - path.replace(/\.css$/, '.scss') - ), -].map((file) => path.resolve(srcPath, file)); - -// Reflect.deleteProperty と違って Typescript の型チェックが効く -delete manifest.options_ui.js; -delete manifest.options_ui.css; - -const config: Partial = (dev = false) => ({ - entryPoints: [ - ...contentScripts, - ...contentStyleSheets, - ...optionsResources, - ], - bundle: true, - outdir: destPath, - platform: 'browser', - loader: { - '.html': 'copy', - }, - jsxFactory: denoConfig.compilerOptions.jsxFactory, - jsxFragment: denoConfig.compilerOptions.jsxFragmentFactory, - plugins: [ - esbuildCachePlugin({ - directory: cachePath, - importmap, - }), - objectExportJSONPlugin({ - targets: [ - { value: manifest, filename: path.resolve(destPath, 'manifest.json') }, - ], - }), - sassPlugin(), - copyPlugin({ - baseDir: srcPath, - baseOutDir: destPath, - files: [ - { from: 'imgs/*', to: 'imgs/[name][ext]' }, - ], - }), - resultPlugin(), - ], - logOverride: { - 'unsupported-jsx-comment': 'silent', - }, - sourcemap: dev ? 'inline' : 'linked', -}); - -export default config; diff --git a/deno.json b/deno.json index c271a1a..4062bbb 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "allowJs": true, "strict": true, "strictNullChecks": true, "noImplicitThis": true, @@ -37,15 +36,36 @@ "useTabs": false } }, - "import_map": "import_map.json", "tasks": { - "build": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net --importmap import_map.json ./build.ts", - "dev": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net --importmap import_map.json ./build.ts --dev", - "watch": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net --importmap import_map.json ./build.ts --dev --watch", + "build": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build/build.ts", + "dev": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build.ts --dev", + "watch": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build.ts --dev --watch", "clean": "rm -rf dist/ && rm -rf cache/", "cache-all": "bash ./scripts/cache-all.sh", "check-version": "bash ./scripts/checkVersion.sh", "check-version-increase": "bash ./scripts/checkVersionIncrease.sh", "setup-vscode": "deno run --allow-run --allow-read --allow-write scripts/setup-vscode.ts" + }, + "imports": { + "@std/fs": "jsr:@std/fs@^1.0.4", + "@std/jsonc": "jsr:@std/jsonc@^1.0.1", + "@std/path": "jsr:@std/path@^1.0.6", + "@tsukina-7mochi/esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", + "cliffy": "https://deno.land/x/cliffy@v0.25.7/mod.ts", + "esbuild": "npm:esbuild@0.24.0", + "esbuild-cache-plugin": "https://deno.land/x/esbuild_cache_plugin@v0.3.1/mod.ts", + "esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.0", + "esbuild-plugin-copy": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-copy-deno/v1.0.8/mod.ts", + "esbuild-plugin-result": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/mod.ts", + "esbuild-plugin-sass": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/mod.ts", + "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", + "posix": "https://deno.land/std@0.184.0/path/mod.ts", + "preact": "https://esm.sh/preact@10.13.2", + "preact/compat": "https://esm.sh/preact@10.13.2/compat", + "preact/hooks": "https://esm.sh/preact@10.13.2/hooks", + "preact/jsx-dev-runtime": "https://esm.sh/preact@10.13.2/jsx-dev-runtime", + "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", + "preact/types": "https://raw.githubusercontent.com/preactjs/preact/10.13.2/src/index.d.ts", + "webextension-polyfill": "https://esm.sh/webextension-polyfill@0.10.0" } } diff --git a/deno.lock b/deno.lock index 715f2e5..492d50a 100644 --- a/deno.lock +++ b/deno.lock @@ -1,24 +1,279 @@ { "version": "4", "specifiers": { + "jsr:@luca/esbuild-deno-loader@0.11": "0.11.0", + "jsr:@std/bytes@^1.0.2": "1.0.2", + "jsr:@std/encoding@^1.0.5": "1.0.5", "jsr:@std/fs@^1.0.4": "1.0.4", + "jsr:@std/jsonc@^1.0.1": "1.0.1", "jsr:@std/path@^1.0.6": "1.0.6", - "npm:json5@2.2.3": "2.2.3" + "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1": "0.1.1", + "npm:esbuild@0.24.0": "0.24.0", + "npm:sass@^1.80.3": "1.80.3" }, "jsr": { + "@luca/esbuild-deno-loader@0.11.0": { + "integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c", + "dependencies": [ + "jsr:@std/bytes", + "jsr:@std/encoding", + "jsr:@std/path" + ] + }, + "@std/bytes@1.0.2": { + "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" + }, + "@std/encoding@1.0.5": { + "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" + }, "@std/fs@1.0.4": { "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", "dependencies": [ "jsr:@std/path" ] }, + "@std/jsonc@1.0.1": { + "integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda" + }, "@std/path@1.0.6": { "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" + }, + "@tsukina-7mochi/esbuild-plugin-sass@0.1.1": { + "integrity": "5bbc148b4bbc9ca5425be4d2c6e37257d0bf1dc9ce946f9e3be3bae7b445056e", + "dependencies": [ + "jsr:@std/path", + "npm:sass" + ] } }, "npm": { - "json5@2.2.3": { - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + "@esbuild/aix-ppc64@0.24.0": { + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==" + }, + "@esbuild/android-arm64@0.24.0": { + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==" + }, + "@esbuild/android-arm@0.24.0": { + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==" + }, + "@esbuild/android-x64@0.24.0": { + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==" + }, + "@esbuild/darwin-arm64@0.24.0": { + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==" + }, + "@esbuild/darwin-x64@0.24.0": { + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==" + }, + "@esbuild/freebsd-arm64@0.24.0": { + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==" + }, + "@esbuild/freebsd-x64@0.24.0": { + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==" + }, + "@esbuild/linux-arm64@0.24.0": { + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==" + }, + "@esbuild/linux-arm@0.24.0": { + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==" + }, + "@esbuild/linux-ia32@0.24.0": { + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==" + }, + "@esbuild/linux-loong64@0.24.0": { + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==" + }, + "@esbuild/linux-mips64el@0.24.0": { + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==" + }, + "@esbuild/linux-ppc64@0.24.0": { + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==" + }, + "@esbuild/linux-riscv64@0.24.0": { + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==" + }, + "@esbuild/linux-s390x@0.24.0": { + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==" + }, + "@esbuild/linux-x64@0.24.0": { + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==" + }, + "@esbuild/netbsd-x64@0.24.0": { + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==" + }, + "@esbuild/openbsd-arm64@0.24.0": { + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==" + }, + "@esbuild/openbsd-x64@0.24.0": { + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==" + }, + "@esbuild/sunos-x64@0.24.0": { + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==" + }, + "@esbuild/win32-arm64@0.24.0": { + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==" + }, + "@esbuild/win32-ia32@0.24.0": { + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==" + }, + "@esbuild/win32-x64@0.24.0": { + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==" + }, + "@parcel/watcher-android-arm64@2.4.1": { + "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==" + }, + "@parcel/watcher-darwin-arm64@2.4.1": { + "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==" + }, + "@parcel/watcher-darwin-x64@2.4.1": { + "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==" + }, + "@parcel/watcher-freebsd-x64@2.4.1": { + "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==" + }, + "@parcel/watcher-linux-arm-glibc@2.4.1": { + "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==" + }, + "@parcel/watcher-linux-arm64-glibc@2.4.1": { + "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==" + }, + "@parcel/watcher-linux-arm64-musl@2.4.1": { + "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==" + }, + "@parcel/watcher-linux-x64-glibc@2.4.1": { + "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==" + }, + "@parcel/watcher-linux-x64-musl@2.4.1": { + "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==" + }, + "@parcel/watcher-win32-arm64@2.4.1": { + "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==" + }, + "@parcel/watcher-win32-ia32@2.4.1": { + "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==" + }, + "@parcel/watcher-win32-x64@2.4.1": { + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==" + }, + "@parcel/watcher@2.4.1": { + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "dependencies": [ + "@parcel/watcher-android-arm64", + "@parcel/watcher-darwin-arm64", + "@parcel/watcher-darwin-x64", + "@parcel/watcher-freebsd-x64", + "@parcel/watcher-linux-arm-glibc", + "@parcel/watcher-linux-arm64-glibc", + "@parcel/watcher-linux-arm64-musl", + "@parcel/watcher-linux-x64-glibc", + "@parcel/watcher-linux-x64-musl", + "@parcel/watcher-win32-arm64", + "@parcel/watcher-win32-ia32", + "@parcel/watcher-win32-x64", + "detect-libc", + "is-glob", + "micromatch", + "node-addon-api" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "chokidar@4.0.1": { + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dependencies": [ + "readdirp" + ] + }, + "detect-libc@1.0.3": { + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==" + }, + "esbuild@0.24.0": { + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "dependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "immutable@4.3.7": { + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch" + ] + }, + "node-addon-api@7.1.1": { + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "readdirp@4.0.2": { + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==" + }, + "sass@1.80.3": { + "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==", + "dependencies": [ + "@parcel/watcher", + "chokidar", + "immutable", + "source-map-js" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] } }, "remote": { @@ -223,5 +478,15 @@ "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/mod.ts": "fb11c4ee4102510bfc58811cd3567f11df746b009b0bacc7b1110f79409a38a3", "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/deps.ts": "c0397e0f91fbef8fd389829fa905af5d9ca53e14ae0d5da6cbdf2cd498ee05ab", "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/mod.ts": "a83c499a6801370d493d2572b059fd43b46921c30a2827b1122f13d9e6212c5d" + }, + "workspace": { + "dependencies": [ + "jsr:@luca/esbuild-deno-loader@0.11", + "jsr:@std/fs@^1.0.4", + "jsr:@std/jsonc@^1.0.1", + "jsr:@std/path@^1.0.6", + "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1", + "npm:esbuild@0.24.0" + ] } } diff --git a/import_map.json b/import_map.json deleted file mode 100644 index 82f779c..0000000 --- a/import_map.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "imports": { - "@std/fs": "jsr:@std/fs@^1.0.4", - "@std/path": "jsr:@std/path@^1.0.6", - - "cliffy": "https://deno.land/x/cliffy@v0.25.7/mod.ts", - "esbuild": "https://deno.land/x/esbuild@v0.17.18/mod.js", - "esbuild-cache-plugin": "https://deno.land/x/esbuild_cache_plugin@v0.3.1/mod.ts", - "esbuild-plugin-copy": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-copy-deno/v1.0.8/mod.ts", - "esbuild-plugin-result": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/mod.ts", - "esbuild-plugin-sass": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/mod.ts", - "json5": "npm:json5@2.2.3", - "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", - "posix": "https://deno.land/std@0.184.0/path/mod.ts", - "preact": "https://esm.sh/preact@10.13.2", - "preact/compat": "https://esm.sh/preact@10.13.2/compat", - "preact/hooks": "https://esm.sh/preact@10.13.2/hooks", - "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", - "preact/jsx-dev-runtime": "https://esm.sh/preact@10.13.2/jsx-dev-runtime", - "preact/types": "https://raw.githubusercontent.com/preactjs/preact/10.13.2/src/index.d.ts", - "webextension-polyfill": "https://esm.sh/webextension-polyfill@0.10.0" - } -} diff --git a/src/manifest.json5 b/src/manifest.json5 deleted file mode 100644 index 5b55884..0000000 --- a/src/manifest.json5 +++ /dev/null @@ -1,69 +0,0 @@ -{ - // Required - manifest_version: 3, - name: "NITech Moodle Extension (40a)", - homepage_url: "https://github.com/nitech-create/nitech-moodle-extension-40a", - version: "0.9.6", - - // Recommended - // action: {}, - // default_locale: "ja", - description: "名古屋工業大学の Moodle (4.0) を使いやすくするChrome拡張機能です。情報基盤センターとは無関係で非公式なものであり、また問題が起きても責任は取れません。Web Extension for Moodle 4.0 of NITech.", - // icons: {}, - - // Optional - author: "nitech Create", - // background: {} - content_scripts: [ - { - // all pages - matches: ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - js: ["content_scripts/allPages/allPages.js"], - }, - { - // dashboard - matches: [ - "https://cms7.ict.nitech.ac.jp/moodle40a/my/", - "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php", - ], - js: ["content_scripts/dashboard/dashboard.js"], - css: ["content_scripts/dashboard/dashboard.css"], - }, - { - // video page - matches: [ - "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", - "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*", - ], - js: ["content_scripts/scorm/scorm.js"], - }, - { - // scorm content page - matches: [ - "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*", - ], - all_frames: true, - css: ["content_scripts/scorm/scorm.css"], - }, - ], - options_ui: { - page: "options/options.html", - open_in_tab: true, - browser_style: true, - // 本当は存在しないフィールド, ビルドのために追加 - js: ["options/options.js"], - css: ["options/options.css"], - }, - content_security_policy: { - extension_pages: "script-src 'self'; object-src 'self';", - }, - permissions: ["storage"], - host_permissions: ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - web_accessible_resources: [ - // source maps - { - resources: ["content_scripts/*/*.map"], - matches: [""], - }, - ], -} diff --git a/src/manifest.jsonc b/src/manifest.jsonc new file mode 100644 index 0000000..b10a089 --- /dev/null +++ b/src/manifest.jsonc @@ -0,0 +1,74 @@ +{ + // Required + "manifest_version": 3, + "name": "NITech Moodle Extension (40a)", + "homepage_url": "https://github.com/nitech-create/nitech-moodle-extension-40a", + "version": "0.9.6", + + // Recommended + // "action": {}, + // "default_locale": "ja", + "description": "名古屋工業大学の Moodle (4.0) を使いやすくするChrome拡張機能です。情報基盤センターとは無関係で非公式なものであり、また問題が起きても責任は取れません。Web Extension for Moodle 4.0 of NITech.", + // "icons": {}, + + // Optional + "author": "nitech Create", + + "content_scripts": [ + { + // all pages + "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], + "js": ["./content_scripts/allPages/allPages.ts"], + }, + { + // dashboard + "matches": [ + "https://cms7.ict.nitech.ac.jp/moodle40a/my/", + "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php", + ], + "js": ["./content_scripts/dashboard/dashboard.ts"], + "css": ["./content_scripts/dashboard/dashboard.scss"], + }, + { + // video page + "matches": [ + "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", + "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*", + ], + "js": ["./content_scripts/scorm/scorm.ts"], + }, + { + // scorm content page + "matches": [ + "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*", + ], + "all_frames": true, + "css": ["./content_scripts/scorm/scorm.scss"], + }, + ], + + "options_ui": { + "page": "./options/options.html", + "open_in_tab": true, + "browser_style": true, + // this field is not exist in the manifest file definition + // /added for the build process + "js": ["./options/options.ts"], + "css": ["./options/options.scss"], + }, + + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self';", + }, + + "permissions": ["storage"], + "host_permissions": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], + + "web_accessible_resources": [ + // source maps + { + "resources": ["content_scripts/*/*.map"], + "matches": [""], + }, + ], +} diff --git a/src/manifestType.ts b/src/manifestType.ts deleted file mode 100644 index 6c60ae5..0000000 --- a/src/manifestType.ts +++ /dev/null @@ -1,35 +0,0 @@ -type RunAt = 'document_start' | 'document_end' | 'document_idle'; - -interface ContentScript { - matches: string[]; - js?: string[]; - css?: string[]; - run_at: RunAt; - match_about_blank: boolean; - match_origin_as_fallback: boolean; -} - -export default interface ManifestType { - manifest_version: 3; - name: string; - version: string; - - description: string; - - author: string; - content_scripts: ContentScript[]; - content_security_policy: { - extension_pages?: string; - sandbox?: string; - }; - options_ui: { - page: string; - open_in_tab: boolean; - browser_style: boolean; - js?: string[]; - css?: string[]; - }; - - permissions: string[]; - host_permissions: string[]; -} From 06875dc333c80276d01d32ed517948faf139bc0f Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 00:05:12 +0900 Subject: [PATCH 08/53] build: implement dev and watch mode --- build/build.ts | 58 +++++++++++++++++++++++++++++++++++++++----------- deno.json | 8 +++---- deno.lock | 8 +++++++ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/build/build.ts b/build/build.ts index 078116a..bb88e2d 100644 --- a/build/build.ts +++ b/build/build.ts @@ -2,13 +2,15 @@ import * as esbuild from 'esbuild'; import * as fs from '@std/fs'; import * as JSONC from '@std/jsonc'; import * as path from '@std/path'; +import { TextLineStream } from '@std/streams'; import { Manifest } from './manifest.ts'; import { buildOptions as jsBuildOptions } from './options/javascript.ts'; import { buildOptions as cssBuildOptions } from './options/stylesheet.ts'; import { buildOptions as copyBuildOptions } from './options/copy.ts'; -const dev = true; +const watchMode = Deno.env.has('WATCH'); +const devMode = watchMode || Deno.env.has('DEV'); const srcPath = './src'; const destPath = './dist'; @@ -58,7 +60,7 @@ const buildOptions = [ entryPoints: jsEntryPoints, srcPath, destPath, - dev, + dev: devMode, jsxFactory: denoConfig.jsxFactory, jsxFragmentFactory: denoConfig.jsxFragmentFactory, }), @@ -66,7 +68,7 @@ const buildOptions = [ entryPoints: cssEntryPoints, srcPath, destPath, - dev, + dev: devMode, }), copyBuildOptions({ entryPoints: copyEntryPoints, @@ -75,14 +77,46 @@ const buildOptions = [ loaderExts: ['.html'], }), ]; +const buildContexts = await Promise.all( + buildOptions.map((buildOptions) => esbuild.context(buildOptions)), +); +console.log('Preparing to build...'); await fs.ensureDir(destPath); -await Promise.all([ - ...buildOptions.map((buildOptions) => { - return esbuild.build(buildOptions); - }), - Deno.writeTextFile( - path.resolve(destPath, 'manifest.json'), - newManifest.stringify(), - ), -]); +await Deno.writeTextFile( + path.resolve(destPath, 'manifest.json'), + newManifest.stringify(), +); + +console.log('Building...'); +await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); + +if (!watchMode) { + await Promise.all(buildContexts.map((ctx) => ctx.cancel())); + await Promise.all(buildContexts.map((ctx) => ctx.dispose())); + Deno.exit(0); +} + +console.log('Watching...'); +console.log('press "r" to rebuild'); +console.log('press "q" to exit'); + +const encoder = new TextEncoder(); +const stdinLines = Deno.stdin.readable + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new TextLineStream()); +await Deno.stdout.write(encoder.encode('> ')); + +for await (const line_ of stdinLines) { + const line = line_.trim(); + + if (line === 'r' || line === 'reload') { + await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); + } else if (line === 'q' || line === 'exit') { + await Promise.all(buildContexts.map((ctx) => ctx.cancel())); + await Promise.all(buildContexts.map((ctx) => ctx.dispose())); + Deno.exit(0); + } + + await Deno.stdout.write(encoder.encode('> ')); +} diff --git a/deno.json b/deno.json index 4062bbb..91923d5 100644 --- a/deno.json +++ b/deno.json @@ -38,10 +38,9 @@ }, "tasks": { "build": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build/build.ts", - "dev": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build.ts --dev", - "watch": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build.ts --dev --watch", - "clean": "rm -rf dist/ && rm -rf cache/", - "cache-all": "bash ./scripts/cache-all.sh", + "dev": "DEV=1 deno task build", + "watch": "WATCH=1 deno task build", + "clean": "rm -rf ./dist", "check-version": "bash ./scripts/checkVersion.sh", "check-version-increase": "bash ./scripts/checkVersionIncrease.sh", "setup-vscode": "deno run --allow-run --allow-read --allow-write scripts/setup-vscode.ts" @@ -50,6 +49,7 @@ "@std/fs": "jsr:@std/fs@^1.0.4", "@std/jsonc": "jsr:@std/jsonc@^1.0.1", "@std/path": "jsr:@std/path@^1.0.6", + "@std/streams": "jsr:@std/streams@^1.0.7", "@tsukina-7mochi/esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", "cliffy": "https://deno.land/x/cliffy@v0.25.7/mod.ts", "esbuild": "npm:esbuild@0.24.0", diff --git a/deno.lock b/deno.lock index 492d50a..8ee99e9 100644 --- a/deno.lock +++ b/deno.lock @@ -7,6 +7,7 @@ "jsr:@std/fs@^1.0.4": "1.0.4", "jsr:@std/jsonc@^1.0.1": "1.0.1", "jsr:@std/path@^1.0.6": "1.0.6", + "jsr:@std/streams@^1.0.7": "1.0.7", "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1": "0.1.1", "npm:esbuild@0.24.0": "0.24.0", "npm:sass@^1.80.3": "1.80.3" @@ -38,6 +39,12 @@ "@std/path@1.0.6": { "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" }, + "@std/streams@1.0.7": { + "integrity": "1a93917ca0c58c01b2bfb93647189229b1702677f169b6fb61ad6241cd2e499b", + "dependencies": [ + "jsr:@std/bytes" + ] + }, "@tsukina-7mochi/esbuild-plugin-sass@0.1.1": { "integrity": "5bbc148b4bbc9ca5425be4d2c6e37257d0bf1dc9ce946f9e3be3bae7b445056e", "dependencies": [ @@ -485,6 +492,7 @@ "jsr:@std/fs@^1.0.4", "jsr:@std/jsonc@^1.0.1", "jsr:@std/path@^1.0.6", + "jsr:@std/streams@^1.0.7", "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1", "npm:esbuild@0.24.0" ] From 9d8c4eec5aba4f0342f658730bb62630845a286a Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 00:14:04 +0900 Subject: [PATCH 09/53] build: organize package dependencies --- build/options/stylesheet.ts | 2 +- deno.json | 8 +- deno.lock | 203 ------------------------------------ 3 files changed, 2 insertions(+), 211 deletions(-) diff --git a/build/options/stylesheet.ts b/build/options/stylesheet.ts index dd7b374..a13f6ca 100644 --- a/build/options/stylesheet.ts +++ b/build/options/stylesheet.ts @@ -1,5 +1,5 @@ import * as esbuild from 'esbuild'; -import { sassPlugin } from '@tsukina-7mochi/esbuild-plugin-sass'; +import { sassPlugin } from 'esbuild-plugin-sass'; type BuildOptionsOptions = { entryPoints: esbuild.BuildOptions['entryPoints']; diff --git a/deno.json b/deno.json index 91923d5..961c1ce 100644 --- a/deno.json +++ b/deno.json @@ -50,16 +50,10 @@ "@std/jsonc": "jsr:@std/jsonc@^1.0.1", "@std/path": "jsr:@std/path@^1.0.6", "@std/streams": "jsr:@std/streams@^1.0.7", - "@tsukina-7mochi/esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", - "cliffy": "https://deno.land/x/cliffy@v0.25.7/mod.ts", + "esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", "esbuild": "npm:esbuild@0.24.0", - "esbuild-cache-plugin": "https://deno.land/x/esbuild_cache_plugin@v0.3.1/mod.ts", "esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.0", - "esbuild-plugin-copy": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-copy-deno/v1.0.8/mod.ts", - "esbuild-plugin-result": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/mod.ts", - "esbuild-plugin-sass": "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/mod.ts", "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", - "posix": "https://deno.land/std@0.184.0/path/mod.ts", "preact": "https://esm.sh/preact@10.13.2", "preact/compat": "https://esm.sh/preact@10.13.2/compat", "preact/hooks": "https://esm.sh/preact@10.13.2/hooks", diff --git a/deno.lock b/deno.lock index 8ee99e9..0c00538 100644 --- a/deno.lock +++ b/deno.lock @@ -283,209 +283,6 @@ ] } }, - "remote": { - "https://deno.land/std@0.131.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", - "https://deno.land/std@0.131.0/_util/os.ts": "49b92edea1e82ba295ec946de8ffd956ed123e2948d9bd1d3e901b04e4307617", - "https://deno.land/std@0.131.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.131.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.131.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", - "https://deno.land/std@0.131.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.131.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", - "https://deno.land/std@0.131.0/path/mod.ts": "4275129bb766f0e475ecc5246aa35689eeade419d72a48355203f31802640be7", - "https://deno.land/std@0.131.0/path/posix.ts": "663e4a6fe30a145f56aa41a22d95114c4c5582d8b57d2d7c9ed27ad2c47636bb", - "https://deno.land/std@0.131.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.131.0/path/win32.ts": "e7bdf63e8d9982b4d8a01ef5689425c93310ece950e517476e22af10f41a136e", - "https://deno.land/std@0.162.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", - "https://deno.land/std@0.162.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", - "https://deno.land/std@0.162.0/fmt/colors.ts": "9e36a716611dcd2e4865adea9c4bec916b5c60caad4cdcdc630d4974e6bb8bd4", - "https://deno.land/std@0.162.0/fs/_util.ts": "fdc156f897197f261a1c096dcf8ff9267ed0ff42bd5b31f55053a4763a4bae3b", - "https://deno.land/std@0.162.0/fs/copy.ts": "73bdf24f4322648d9bc38ef983b818637ba368351d17aa03644209d3ce3eac31", - "https://deno.land/std@0.162.0/fs/empty_dir.ts": "c15a0aaaf40f8c21cca902aa1e01a789ad0c2fd1b7e2eecf4957053c5dbf707f", - "https://deno.land/std@0.162.0/fs/ensure_dir.ts": "76395fc1c989ca8d2de3aedfa8240eb8f5225cde20f926de957995b063135b80", - "https://deno.land/std@0.162.0/fs/ensure_file.ts": "b8e32ea63aa21221d0219760ba3f741f682d7f7d68d0d24a3ec067c338568152", - "https://deno.land/std@0.162.0/fs/ensure_link.ts": "5cc1c04f18487d7d1edf4c5469705f30b61390ffd24ad7db6df85e7209b32bb2", - "https://deno.land/std@0.162.0/fs/ensure_symlink.ts": "5273557b8c50be69477aa9cb003b54ff2240a336db52a40851c97abce76b96ab", - "https://deno.land/std@0.162.0/fs/eol.ts": "65b1e27320c3eec6fb653b27e20056ee3d015d3e91db388cfefa41616ebc7cb3", - "https://deno.land/std@0.162.0/fs/exists.ts": "6a447912e49eb79cc640adacfbf4b0baf8e17ede6d5bed057062ce33c4fa0d68", - "https://deno.land/std@0.162.0/fs/expand_glob.ts": "d3f62aefc7718d878904d60d91e8e6dbbf86c696d32b6cbbc333637acf7f8571", - "https://deno.land/std@0.162.0/fs/mod.ts": "354a6f972ef4e00c4dd1f1339a8828ef0764c1c23d3c0010af3fcc025d8655b0", - "https://deno.land/std@0.162.0/fs/move.ts": "6d7fa9da60dbc7a32dd7fdbc2ff812b745861213c8e92ba96dace0669b0c378c", - "https://deno.land/std@0.162.0/fs/walk.ts": "d96d4e5b6a3552e8304f28a0fd0b317b812298298449044f8de4932c869388a5", - "https://deno.land/std@0.162.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.162.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.162.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", - "https://deno.land/std@0.162.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.162.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", - "https://deno.land/std@0.162.0/path/mod.ts": "56fec03ad0ebd61b6ab39ddb9b0ddb4c4a5c9f2f4f632e09dd37ec9ebfd722ac", - "https://deno.land/std@0.162.0/path/posix.ts": "6b63de7097e68c8663c84ccedc0fd977656eb134432d818ecd3a4e122638ac24", - "https://deno.land/std@0.162.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.162.0/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d", - "https://deno.land/std@0.162.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", - "https://deno.land/std@0.162.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", - "https://deno.land/std@0.162.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8", - "https://deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", - "https://deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", - "https://deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", - "https://deno.land/std@0.170.0/fmt/colors.ts": "03ad95e543d2808bc43c17a3dd29d25b43d0f16287fe562a0be89bf632454a12", - "https://deno.land/std@0.170.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.170.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.170.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", - "https://deno.land/std@0.170.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.170.0/path/glob.ts": "81cc6c72be002cd546c7a22d1f263f82f63f37fe0035d9726aa96fc8f6e4afa1", - "https://deno.land/std@0.170.0/path/mod.ts": "cf7cec7ac11b7048bb66af8ae03513e66595c279c65cfa12bfc07d9599608b78", - "https://deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", - "https://deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", - "https://deno.land/std@0.184.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", - "https://deno.land/std@0.184.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.184.0/fs/_util.ts": "579038bebc3bd35c43a6a7766f7d91fbacdf44bc03468e9d3134297bb99ed4f9", - "https://deno.land/std@0.184.0/fs/copy.ts": "14214efd94fc3aa6db1e4af2b4b9578e50f7362b7f3725d5a14ad259a5df26c8", - "https://deno.land/std@0.184.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", - "https://deno.land/std@0.184.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", - "https://deno.land/std@0.184.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9", - "https://deno.land/std@0.184.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4", - "https://deno.land/std@0.184.0/fs/ensure_symlink.ts": "5006ab2f458159c56d689b53b1e48d57e05eeb1eaf64e677f7f76a30bc4fdba1", - "https://deno.land/std@0.184.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", - "https://deno.land/std@0.184.0/fs/exists.ts": "29c26bca8584a22876be7cb8844f1b6c8fc35e9af514576b78f5c6884d7ed02d", - "https://deno.land/std@0.184.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", - "https://deno.land/std@0.184.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", - "https://deno.land/std@0.184.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", - "https://deno.land/std@0.184.0/fs/walk.ts": "920be35a7376db6c0b5b1caf1486fb962925e38c9825f90367f8f26b5e5d0897", - "https://deno.land/std@0.184.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.184.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.184.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.184.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.184.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.184.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", - "https://deno.land/std@0.184.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.184.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.184.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", - "https://deno.land/std@0.97.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", - "https://deno.land/std@0.97.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7", - "https://deno.land/std@0.97.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e", - "https://deno.land/std@0.97.0/encoding/hex.ts": "f952e0727bddb3b2fd2e6889d104eacbd62e92091f540ebd6459317a61932d9b", - "https://deno.land/std@0.97.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b", - "https://deno.land/std@0.97.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7", - "https://deno.land/std@0.97.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00", - "https://deno.land/std@0.97.0/hash/_wasm/hash.ts": "cb6ad1ab429f8ac9d6eae48f3286e08236d662e1a2e5cfd681ba1c0f17375895", - "https://deno.land/std@0.97.0/hash/_wasm/wasm.js": "94b1b997ae6fb4e6d2156bcea8f79cfcd1e512a91252b08800a92071e5e84e1a", - "https://deno.land/std@0.97.0/hash/mod.ts": "5d032bd34186cda2f8d17fc122d621430953a6030d4b3f11172004715e3e2441", - "https://deno.land/std@0.97.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", - "https://deno.land/std@0.97.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", - "https://deno.land/std@0.97.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", - "https://deno.land/std@0.97.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a", - "https://deno.land/std@0.97.0/path/glob.ts": "314ad9ff263b895795208cdd4d5e35a44618ca3c6dd155e226fb15d065008652", - "https://deno.land/std@0.97.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", - "https://deno.land/std@0.97.0/path/posix.ts": "f56c3c99feb47f30a40ce9d252ef6f00296fa7c0fcb6dd81211bdb3b8b99ca3b", - "https://deno.land/std@0.97.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", - "https://deno.land/std@0.97.0/path/win32.ts": "77f7b3604e0de40f3a7c698e8a79e7f601dc187035a1c21cb1e596666ce112f8", - "https://deno.land/x/cache@0.2.13/cache.ts": "4005aad54fb9aac9ff02526ffa798032e57f2d7966905fdeb7949263b1c95f2f", - "https://deno.land/x/cache@0.2.13/deps.ts": "6f14e76a1a09f329e3f3830c6e72bd10b53a89a75769d5ea886e5d8603e503e6", - "https://deno.land/x/cache@0.2.13/directories.ts": "ef48531cab3f827252e248596d15cede0de179a2fb15392ae24cf8034519994f", - "https://deno.land/x/cache@0.2.13/file.ts": "5abe7d80c6ac594c98e66eb4262962139f48cd9c49dbe2a77e9608760508a09a", - "https://deno.land/x/cache@0.2.13/file_fetcher.ts": "5c793cc83a5b9377679ec313b2a2321e51bf7ed15380fa82d387f1cdef3b924f", - "https://deno.land/x/cache@0.2.13/helpers.ts": "d1545d6432277b7a0b5ea254d1c51d572b6452a8eadd9faa7ad9c5586a1725c4", - "https://deno.land/x/cache@0.2.13/mod.ts": "3188250d3a013ef6c9eb060e5284cf729083af7944a29e60bb3d8597dd20ebcd", - "https://deno.land/x/cliffy@v0.25.7/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", - "https://deno.land/x/cliffy@v0.25.7/ansi/ansi.ts": "7f43d07d31dd7c24b721bb434c39cbb5132029fa4be3dd8938873065f65e5810", - "https://deno.land/x/cliffy@v0.25.7/ansi/ansi_escapes.ts": "885f61f343223f27b8ec69cc138a54bea30542924eacd0f290cd84edcf691387", - "https://deno.land/x/cliffy@v0.25.7/ansi/chain.ts": "31fb9fcbf72fed9f3eb9b9487270d2042ccd46a612d07dd5271b1a80ae2140a0", - "https://deno.land/x/cliffy@v0.25.7/ansi/colors.ts": "5f71993af5bd1aa0a795b15f41692d556d7c89584a601fed75997df844b832c9", - "https://deno.land/x/cliffy@v0.25.7/ansi/cursor_position.ts": "d537491e31d9c254b208277448eff92ff7f55978c4928dea363df92c0df0813f", - "https://deno.land/x/cliffy@v0.25.7/ansi/deps.ts": "0f35cb7e91868ce81561f6a77426ea8bc55dc15e13f84c7352f211023af79053", - "https://deno.land/x/cliffy@v0.25.7/ansi/mod.ts": "bb4e6588e6704949766205709463c8c33b30fec66c0b1846bc84a3db04a4e075", - "https://deno.land/x/cliffy@v0.25.7/ansi/tty.ts": "8fb064c17ead6cdf00c2d3bc87a9fd17b1167f2daa575c42b516f38bdb604673", - "https://deno.land/x/cliffy@v0.25.7/command/_errors.ts": "a9bd23dc816b32ec96c9b8f3057218241778d8c40333b43341138191450965e5", - "https://deno.land/x/cliffy@v0.25.7/command/_utils.ts": "9ab3d69fabab6c335b881b8a5229cbd5db0c68f630a1c307aff988b6396d9baf", - "https://deno.land/x/cliffy@v0.25.7/command/command.ts": "a2b83c612acd65c69116f70dec872f6da383699b83874b70fcf38cddf790443f", - "https://deno.land/x/cliffy@v0.25.7/command/completions/_bash_completions_generator.ts": "43b4abb543d4dc60233620d51e69d82d3b7c44e274e723681e0dce2a124f69f9", - "https://deno.land/x/cliffy@v0.25.7/command/completions/_fish_completions_generator.ts": "d0289985f5cf0bd288c05273bfa286b24c27feb40822eb7fd9d7fee64e6580e8", - "https://deno.land/x/cliffy@v0.25.7/command/completions/_zsh_completions_generator.ts": "14461eb274954fea4953ee75938821f721da7da607dc49bcc7db1e3f33a207bd", - "https://deno.land/x/cliffy@v0.25.7/command/completions/bash.ts": "053aa2006ec327ccecacb00ba28e5eb836300e5c1bec1b3cfaee9ddcf8189756", - "https://deno.land/x/cliffy@v0.25.7/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v0.25.7/command/completions/fish.ts": "9938beaa6458c6cf9e2eeda46a09e8cd362d4f8c6c9efe87d3cd8ca7477402a5", - "https://deno.land/x/cliffy@v0.25.7/command/completions/mod.ts": "aeef7ec8e319bb157c39a4bab8030c9fe8fa327b4c1e94c9c1025077b45b40c0", - "https://deno.land/x/cliffy@v0.25.7/command/completions/zsh.ts": "8b04ab244a0b582f7927d405e17b38602428eeb347a9968a657e7ea9f40e721a", - "https://deno.land/x/cliffy@v0.25.7/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v0.25.7/command/deps.ts": "275b964ce173770bae65f6b8ebe9d2fd557dc10292cdd1ed3db1735f0d77fa1d", - "https://deno.land/x/cliffy@v0.25.7/command/help/_help_generator.ts": "f7c349cb2ddb737e70dc1f89bcb1943ca9017a53506be0d4138e0aadb9970a49", - "https://deno.land/x/cliffy@v0.25.7/command/help/mod.ts": "09d74d3eb42d21285407cda688074c29595d9c927b69aedf9d05ff3f215820d3", - "https://deno.land/x/cliffy@v0.25.7/command/mod.ts": "d0a32df6b14028e43bb2d41fa87d24bc00f9662a44e5a177b3db02f93e473209", - "https://deno.land/x/cliffy@v0.25.7/command/type.ts": "24e88e3085e1574662b856ccce70d589959648817135d4469fab67b9cce1b364", - "https://deno.land/x/cliffy@v0.25.7/command/types.ts": "ae02eec0ed7a769f7dba2dd5d3a931a61724b3021271b1b565cf189d9adfd4a0", - "https://deno.land/x/cliffy@v0.25.7/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v0.25.7/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v0.25.7/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v0.25.7/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v0.25.7/command/types/enum.ts": "2178345972adf7129a47e5f02856ca3e6852a91442a1c78307dffb8a6a3c6c9f", - "https://deno.land/x/cliffy@v0.25.7/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v0.25.7/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v0.25.7/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v0.25.7/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts": "17e2df3b620905583256684415e6c4a31e8de5c59066eb6d6c9c133919292dc4", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider.ts": "d6fb846043232cbd23c57d257100c7fc92274984d75a5fead0f3e4266dc76ab8", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", - "https://deno.land/x/cliffy@v0.25.7/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v0.25.7/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", - "https://deno.land/x/cliffy@v0.25.7/flags/_validate_flags.ts": "16eb5837986c6f6f7620817820161a78d66ce92d690e3697068726bbef067452", - "https://deno.land/x/cliffy@v0.25.7/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v0.25.7/flags/flags.ts": "68a9dfcacc4983a84c07ba19b66e5e9fccd04389fad215210c60fb414cc62576", - "https://deno.land/x/cliffy@v0.25.7/flags/mod.ts": "b21c2c135cd2437cc16245c5f168a626091631d6d4907ad10db61c96c93bdb25", - "https://deno.land/x/cliffy@v0.25.7/flags/types.ts": "7452ea5296758fb7af89930349ce40d8eb9a43b24b3f5759283e1cb5113075fd", - "https://deno.land/x/cliffy@v0.25.7/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v0.25.7/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v0.25.7/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v0.25.7/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v0.25.7/keycode/key_code.ts": "c4ab0ffd102c2534962b765ded6d8d254631821bf568143d9352c1cdcf7a24be", - "https://deno.land/x/cliffy@v0.25.7/keycode/key_codes.ts": "917f0a2da0dbace08cf29bcfdaaa2257da9fe7e705fff8867d86ed69dfb08cfe", - "https://deno.land/x/cliffy@v0.25.7/keycode/mod.ts": "292d2f295316c6e0da6955042a7b31ab2968ff09f2300541d00f05ed6c2aa2d4", - "https://deno.land/x/cliffy@v0.25.7/mod.ts": "e3515ccf6bd4e4ac89322034e07e2332ed71901e4467ee5bc9d72851893e167b", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_input.ts": "737cff2de02c8ce35250f5dd79c67b5fc176423191a2abd1f471a90dd725659e", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_list.ts": "79b301bf09eb19f0d070d897f613f78d4e9f93100d7e9a26349ef0bfaa7408d2", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_prompt.ts": "8630ce89a66d83e695922df41721cada52900b515385d86def597dea35971bb2", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_suggestions.ts": "2a8b619f91e8f9a270811eff557f10f1343a444a527b5fc22c94de832939920c", - "https://deno.land/x/cliffy@v0.25.7/prompt/_utils.ts": "676cca30762656ed1a9bcb21a7254244278a23ffc591750e98a501644b6d2df3", - "https://deno.land/x/cliffy@v0.25.7/prompt/checkbox.ts": "e5a5a9adbb86835dffa2afbd23c6f7a8fe25a9d166485388ef25aba5dc3fbf9e", - "https://deno.land/x/cliffy@v0.25.7/prompt/confirm.ts": "94c8e55de3bbcd53732804420935c432eab29945497d1c47c357d236a89cb5f6", - "https://deno.land/x/cliffy@v0.25.7/prompt/deps.ts": "4c38ab18e55a792c9a136c1c29b2b6e21ea4820c45de7ef4cf517ce94012c57d", - "https://deno.land/x/cliffy@v0.25.7/prompt/figures.ts": "26af0fbfe21497220e4b887bb550fab997498cde14703b98e78faf370fbb4b94", - "https://deno.land/x/cliffy@v0.25.7/prompt/input.ts": "ee45532e0a30c2463e436e08ae291d79d1c2c40872e17364c96d2b97c279bf4d", - "https://deno.land/x/cliffy@v0.25.7/prompt/list.ts": "6780427ff2a932a48c9b882d173c64802081d6cdce9ff618d66ba6504b6abc50", - "https://deno.land/x/cliffy@v0.25.7/prompt/mod.ts": "195aed14d10d279914eaa28c696dec404d576ca424c097a5bc2b4a7a13b66c89", - "https://deno.land/x/cliffy@v0.25.7/prompt/number.ts": "015305a76b50138234dde4fd50eb886c6c7c0baa1b314caf811484644acdc2cf", - "https://deno.land/x/cliffy@v0.25.7/prompt/prompt.ts": "0e7f6a1d43475ee33fb25f7d50749b2f07fc0bcddd9579f3f9af12d05b4a4412", - "https://deno.land/x/cliffy@v0.25.7/prompt/secret.ts": "58745f5231fb2c44294c4acf2511f8c5bfddfa1e12f259580ff90dedea2703d6", - "https://deno.land/x/cliffy@v0.25.7/prompt/select.ts": "1e982eae85718e4e15a3ee10a5ae2233e532d7977d55888f3a309e8e3982b784", - "https://deno.land/x/cliffy@v0.25.7/prompt/toggle.ts": "842c3754a40732f2e80bcd4670098713e402e64bd930e6cab2b787f7ad4d931a", - "https://deno.land/x/cliffy@v0.25.7/table/border.ts": "2514abae4e4f51eda60a5f8c927ba24efd464a590027e900926b38f68e01253c", - "https://deno.land/x/cliffy@v0.25.7/table/cell.ts": "1d787d8006ac8302020d18ec39f8d7f1113612c20801b973e3839de9c3f8b7b3", - "https://deno.land/x/cliffy@v0.25.7/table/deps.ts": "5b05fa56c1a5e2af34f2103fd199e5f87f0507549963019563eae519271819d2", - "https://deno.land/x/cliffy@v0.25.7/table/layout.ts": "46bf10ae5430cf4fbb92f23d588230e9c6336edbdb154e5c9581290562b169f4", - "https://deno.land/x/cliffy@v0.25.7/table/mod.ts": "e74f69f38810ee6139a71132783765feb94436a6619c07474ada45b465189834", - "https://deno.land/x/cliffy@v0.25.7/table/row.ts": "5f519ba7488d2ef76cbbf50527f10f7957bfd668ce5b9169abbc44ec88302645", - "https://deno.land/x/cliffy@v0.25.7/table/table.ts": "ec204c9d08bb3ff1939c5ac7412a4c9ed7d00925d4fc92aff9bfe07bd269258d", - "https://deno.land/x/cliffy@v0.25.7/table/utils.ts": "187bb7dcbcfb16199a5d906113f584740901dfca1007400cba0df7dcd341bc29", - "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", - "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", - "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", - "https://deno.land/x/denosass@1.0.6/mod.ts": "5e9c142055d658f3acb2b370d0b412c783ed4b27db830f387525fb7f69a7ab3d", - "https://deno.land/x/denosass@1.0.6/src/deps.ts": "cb5fa11799e3def8b593be3b5939d2755a2c7f1f4987f3af1bc4ad90922d3715", - "https://deno.land/x/denosass@1.0.6/src/mod.ts": "d2b63172f98238f77831995a5d6c8a06af5252ad8fbe7b9ec40b60eae86f2164", - "https://deno.land/x/denosass@1.0.6/src/types/module.types.ts": "7a5027482ded1d2967fbe690ef8f928446c5de8811c3333f9b09ae6e8122f9ba", - "https://deno.land/x/denosass@1.0.6/src/wasm/grass.deno.js": "a72432ce8d6b8f9c31e31c71415fdca03fe36aa22417e414bc81e2e21a8a687b", - "https://deno.land/x/esbuild@v0.17.18/mod.js": "84b5044def8a2e94770b79d295a1dd74f5ee453514c5b4f33571e22e1c882898", - "https://deno.land/x/esbuild_cache_plugin@v0.3.1/deps.ts": "379625c0bb9767b2229cbe3c316a249916f4611a9520fcf1f91069384b654923", - "https://deno.land/x/esbuild_cache_plugin@v0.3.1/mod.ts": "cbcfaca866ca4ba8c729633239c3da811ac4c799d8411aea04dcd2e4c323bdb3", - "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-copy-deno/v1.0.8/deps.ts": "168bf97c4e56db75a1075a4ef5549c796465588f98b6326e7661acfe98e58caa", - "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-copy-deno/v1.0.8/mod.ts": "48b2bcb4b6210da03d3dfe1be1a17a677089e3bd5be22d889e8a78891bf6ad68", - "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/deps.ts": "cce852a277e6ec80c9abf9cd02b3c9a895f02925c4100d44d226a8a0b8cb4db0", - "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-result-deno/v1.0.9/mod.ts": "fb11c4ee4102510bfc58811cd3567f11df746b009b0bacc7b1110f79409a38a3", - "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/deps.ts": "c0397e0f91fbef8fd389829fa905af5d9ca53e14ae0d5da6cbdf2cd498ee05ab", - "https://raw.githubusercontent.com/Tsukina-7mochi/esbuild-plugin-sass-deno/v0.2.6/mod.ts": "a83c499a6801370d493d2572b059fd43b46921c30a2827b1122f13d9e6212c5d" - }, "workspace": { "dependencies": [ "jsr:@luca/esbuild-deno-loader@0.11", From ae8b05fb903ce830cacb4bc4c555fbf51262f1e2 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 14:43:09 +0900 Subject: [PATCH 10/53] chore: remove unused esbuild plugins --- plugins/json5.ts | 19 ------------------- plugins/json5Export.ts | 35 ----------------------------------- plugins/objectExportJSON.ts | 30 ------------------------------ 3 files changed, 84 deletions(-) delete mode 100644 plugins/json5.ts delete mode 100644 plugins/json5Export.ts delete mode 100644 plugins/objectExportJSON.ts diff --git a/plugins/json5.ts b/plugins/json5.ts deleted file mode 100644 index c22bb01..0000000 --- a/plugins/json5.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as esbuild from 'esbuild'; -import JSON5 from 'json5'; - -const json5Plugin = (loader?: esbuild.Loader): esbuild.Plugin => ({ - name: 'json5-plugin', - setup(build) { - build.onLoad({ filter: /\.json5$/ }, async (args) => { - const json5Content = await Deno.readTextFile(args.path); - const jsonContent = JSON.stringify(JSON5.parse(json5Content)); - - return { - contents: jsonContent, - loader: loader ?? 'json', - }; - }); - } -}); - -export default json5Plugin; diff --git a/plugins/json5Export.ts b/plugins/json5Export.ts deleted file mode 100644 index dd9e838..0000000 --- a/plugins/json5Export.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as esbuild from 'esbuild'; -import JSON5 from 'json5'; - -interface Options { - filePatterns: RegExp[], -} -const namespace = 'json5-export'; - -const json5ExportPlugin = (options?: Options): esbuild.Plugin => ({ - name: 'json5-export', - setup(build) { - const patterns = options?.filePatterns ?? []; - for(const pattern of patterns) { - build.onResolve({ filter: pattern }, (args) => ({ - path: args.path.replace(/\.json5$/, '.json'), - namespace, - pluginData: { - originalPath: args.path - } - })); - } - - build.onLoad({ filter: /.*/, namespace }, async (args) => { - const json5Content = await Deno.readTextFile(args.pluginData.originalPath); - const jsonContent = JSON.stringify(JSON5.parse(json5Content)); - - return { - contents: jsonContent, - loader: 'copy', - }; - }); - } -}); - -export default json5ExportPlugin; diff --git a/plugins/objectExportJSON.ts b/plugins/objectExportJSON.ts deleted file mode 100644 index 7d9272e..0000000 --- a/plugins/objectExportJSON.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as fs from '@std/fs'; -import * as path from '@std/path'; - -import * as esbuild from 'esbuild'; - -interface Option { - targets: { value: any; filename: string }[]; -} - -const objectExportJSONPlugin = (option: Option): esbuild.Plugin => ({ - name: 'object-export-json-plugin', - setup(build) { - build.onStart(async () => { - const writePromises: Promise[] = []; - - for (const target of option.targets) { - const content = JSON.stringify(target.value); - - writePromises.push( - fs.ensureDir(path.dirname(target.filename)) - .then(() => Deno.writeTextFile(target.filename, content)), - ); - } - - await Promise.all(writePromises); - }); - }, -}); - -export default objectExportJSONPlugin; From b45e2598a7218a11fd3141e716006c35323f086b Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 14:44:02 +0900 Subject: [PATCH 11/53] chore: update deno config file format --- deno.json | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/deno.json b/deno.json index 961c1ce..a035327 100644 --- a/deno.json +++ b/deno.json @@ -1,9 +1,7 @@ { "compilerOptions": { "strict": true, - "strictNullChecks": true, - "noImplicitThis": true, - "noImplicitAny": true, + "noImplicitOverride": true, "noUnusedParameters": true, "lib": [ "DOM", @@ -12,29 +10,22 @@ ], "jsx": "react-jsx", "jsxFactory": "preact.h", - "jsxFragmentFactory": "preact.Fragment" + "jsxFragmentFactory": "preact.Fragment", + "paths": { + "~/*": ["./src/*"] + } }, "lint": { - "files": { - "include": ["src/"], - "exclude": ["dist/"] - }, "rules": { "tags": ["recommended"] } }, "fmt": { - "files": { - "include": ["src/"], - "exclude": ["dist/"] - }, - "options": { - "indentWidth": 2, - "lineWidth": 80, - "proseWrap": "always", - "singleQuote": true, - "useTabs": false - } + "indentWidth": 2, + "lineWidth": 80, + "proseWrap": "always", + "singleQuote": true, + "useTabs": false }, "tasks": { "build": "deno run --allow-run --allow-read --allow-write --allow-env --allow-net ./build/build.ts", @@ -46,13 +37,15 @@ "setup-vscode": "deno run --allow-run --allow-read --allow-write scripts/setup-vscode.ts" }, "imports": { + "~/": "./src/", "@std/fs": "jsr:@std/fs@^1.0.4", "@std/jsonc": "jsr:@std/jsonc@^1.0.1", "@std/path": "jsr:@std/path@^1.0.6", "@std/streams": "jsr:@std/streams@^1.0.7", - "esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", + "@types/webextension-polyfill": "npm:@types/webextension-polyfill@^0.12.1", "esbuild": "npm:esbuild@0.24.0", "esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.0", + "esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", "preact": "https://esm.sh/preact@10.13.2", "preact/compat": "https://esm.sh/preact@10.13.2/compat", @@ -60,6 +53,9 @@ "preact/jsx-dev-runtime": "https://esm.sh/preact@10.13.2/jsx-dev-runtime", "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", "preact/types": "https://raw.githubusercontent.com/preactjs/preact/10.13.2/src/index.d.ts", - "webextension-polyfill": "https://esm.sh/webextension-polyfill@0.10.0" - } + "webextension-polyfill": "npm:webextension-polyfill@^0.12.0" + }, + "exclude": [ + "dist/" + ] } From 12419bb504c6762907266a38577213c0f0733e93 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 18:39:13 +0900 Subject: [PATCH 12/53] style: format manifest.jsonc --- src/manifest.jsonc | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/manifest.jsonc b/src/manifest.jsonc index b10a089..b2f25b2 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -18,33 +18,33 @@ { // all pages "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - "js": ["./content_scripts/allPages/allPages.ts"], + "js": ["./content_scripts/allPages/allPages.ts"] }, { // dashboard "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/my/", - "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php", + "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php" ], "js": ["./content_scripts/dashboard/dashboard.ts"], - "css": ["./content_scripts/dashboard/dashboard.scss"], + "css": ["./content_scripts/dashboard/dashboard.scss"] }, { // video page "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", - "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*", + "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*" ], - "js": ["./content_scripts/scorm/scorm.ts"], + "js": ["./content_scripts/scorm/scorm.ts"] }, { // scorm content page "matches": [ - "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*", + "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*" ], "all_frames": true, - "css": ["./content_scripts/scorm/scorm.scss"], - }, + "css": ["./content_scripts/scorm/scorm.scss"] + } ], "options_ui": { @@ -54,11 +54,11 @@ // this field is not exist in the manifest file definition // /added for the build process "js": ["./options/options.ts"], - "css": ["./options/options.scss"], + "css": ["./options/options.scss"] }, "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self';", + "extension_pages": "script-src 'self'; object-src 'self';" }, "permissions": ["storage"], @@ -68,7 +68,7 @@ // source maps { "resources": ["content_scripts/*/*.map"], - "matches": [""], - }, - ], + "matches": [""] + } + ] } From 7e786116d7ba8fa793961b392f3b7e06716d028f Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 18:39:25 +0900 Subject: [PATCH 13/53] build: enable code splitting --- build/options/javascript.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/options/javascript.ts b/build/options/javascript.ts index 53b4e1c..fc8966d 100644 --- a/build/options/javascript.ts +++ b/build/options/javascript.ts @@ -16,7 +16,9 @@ export const buildOptions = ( entryPoints: options.entryPoints, outdir: options.destPath, platform: 'browser', + format: 'esm', bundle: true, + splitting: true, sourcemap: options.dev ? 'linked' : false, minify: !options.dev, jsxFactory: options.jsxFactory, From 3a0c519e685dd499dc6a70e8d022d56be7d3783f Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 18:40:00 +0900 Subject: [PATCH 14/53] feat: create models of course and preference --- src/common/model/course.ts | 244 ++++++++++++++++++++++++++++++++ src/common/model/preferences.ts | 33 +++++ 2 files changed, 277 insertions(+) create mode 100644 src/common/model/course.ts create mode 100644 src/common/model/preferences.ts diff --git a/src/common/model/course.ts b/src/common/model/course.ts new file mode 100644 index 0000000..8c51d5a --- /dev/null +++ b/src/common/model/course.ts @@ -0,0 +1,244 @@ +// deno-fmt-ignore +type Period = + | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 + | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; +type Semester = 1 | 2 | 3 | 4; +type WeekOfDay = 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'; + +const identifierLikePattern = + /^(?(?\d{4})|\w{2})(?\w)(?\w\d{3})$/; +const periodPattern = /(?([1-9]|1\d|20))-(?([1-9]|1\d|20))限/; + +const weekOfDayIndex = ( + weekOfDay: NonNullable, +): number => { + return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'].indexOf(weekOfDay); +}; + +const findSemester = function (text: string): [Semester, Semester] | null { + if (text.includes('前期')) { + return [1, 2]; + } else if (text.includes('後期')) { + return [3, 4]; + } else if (text.includes('第1クォーター')) { + return [1, 1]; + } else if (text.includes('第2クォーター')) { + return [2, 2]; + } else if (text.includes('第3クォーター')) { + return [3, 3]; + } else if (text.includes('第4クォーター')) { + return [4, 4]; + } + + return null; +}; + +const findWeekOfDay = function (text: string): WeekOfDay | null { + if (text.includes('日曜')) { + return 'sun'; + } else if (text.includes('月曜')) { + return 'mon'; + } else if (text.includes('火曜')) { + return 'tue'; + } else if (text.includes('水曜')) { + return 'wed'; + } else if (text.includes('木曜')) { + return 'thu'; + } else if (text.includes('金曜')) { + return 'fri'; + } else if (text.includes('土曜')) { + return 'sat'; + } + + return null; +}; + +export type CourseJson = { + id: string; + /** course name */ + name: string; + /** course name in moodle */ + fullName: string; + /** course name in moodle */ + systemCourseName?: string; + /** the year the course is offered */ + fullYear?: number; + /** the semester the course is offered */ + semesterStart?: Semester; + /** the semester the course is offered */ + semesterEnd?: Semester; + /** the day of the week the course is offered */ + weekOfDay?: WeekOfDay; + /** the period in timetable the course is offered */ + period?: [Period, Period]; +}; + +/** + * Courses that are registered in moodle that are being offered + * or have been offered in the past + */ +export class Course { + id: string; + /** course name */ + name: string; + /** course name displayed in moodle */ + fullName: string; + /** course name in moodle */ + systemCourseName?: string; + /** the year the course is offered */ + fullYear?: number; + /** the semester the course is offered */ + semesterStart?: Semester; + /** the semester the course is offered */ + semesterEnd?: Semester; + /** the day of the week the course is offered */ + weekOfDay?: WeekOfDay; + /** the period in timetable the course is offered */ + period?: [Period, Period]; + + constructor(init: CourseJson) { + this.id = init.id; + this.name = init.name; + this.fullName = init.fullName; + this.systemCourseName = init.systemCourseName; + this.fullYear = init.fullYear; + this.semesterStart = init.semesterStart; + this.semesterEnd = init.semesterEnd; + this.weekOfDay = init.weekOfDay; + this.period = init.period; + } + + static parse(text: string): Omit { + const cleanText = text.trim().replace(/\s+/g, ' '); + const segments = cleanText.split(' '); + const normalizedText = cleanText.normalize('NFKC').toLowerCase(); + + const name = (() => { + // string "コース名" may be embedded for screen reader + const nameIndex = segments.indexOf('コース名'); + if (nameIndex > 0 && nameIndex + 1 < segments.length) { + return segments.slice(nameIndex + 1).join(' '); + } + return text; + })(); + const fullName = cleanText; + + let systemCourseName: string | undefined = undefined; + let fullYear: number | undefined = undefined; + for (const segment of segments) { + const match = segment.match(identifierLikePattern); + if (match === null) continue; + + const year = match.groups?.year; + const seg1 = match.groups?.seg1; + const seg2 = match.groups?.seg2; + const seg3 = match.groups?.seg3; + + if (year) { + fullYear = parseInt(year); + } + if (seg1 && seg2 && seg3) { + systemCourseName = `${seg1}-${seg2}-${seg3}`; + } + } + + let semesterStart: Semester | undefined = undefined; + let semesterEnd: Semester | undefined = undefined; + const semester = findSemester(normalizedText); + if (semester) { + [semesterStart, semesterEnd] = semester; + } + + const weekOfDay = findWeekOfDay(normalizedText) ?? undefined; + const periodMatch = normalizedText.match(periodPattern); + let period: [Period, Period] | undefined = undefined; + if (periodMatch) { + const start = periodMatch.groups?.start; + const end = periodMatch.groups?.end; + if (start && end) { + period = [parseInt(start), parseInt(end)] as [Period, Period]; + } + } + + return { + name, + fullName, + systemCourseName, + fullYear, + semesterStart, + semesterEnd, + weekOfDay, + period, + }; + } + + static fromJson(json: CourseJson): Course { + return new Course(json); + } + + toJson(): CourseJson { + return { + id: this.id, + name: this.name, + fullName: this.fullName, + fullYear: this.fullYear, + semesterStart: this.semesterStart, + semesterEnd: this.semesterEnd, + weekOfDay: this.weekOfDay, + period: this.period, + }; + } + + // TODO: accept sort options + compare(that: Course): number { + if (this.fullYear && that.fullYear) { + if (this.fullYear !== that.fullYear) { + return this.fullYear - that.fullYear; + } + } else if (this.fullYear) { + return -1; + } else if (that.fullYear) { + return 1; + } + + if (this.semesterStart && that.semesterStart) { + if (this.semesterStart !== that.semesterStart) { + return this.semesterStart - that.semesterStart; + } + } else if (this.semesterStart) { + return -1; + } else if (that.semesterStart) { + return 1; + } + + if (this.weekOfDay && that.weekOfDay) { + const thisWeekOfDay = weekOfDayIndex(this.weekOfDay); + const thatWeekOfDay = weekOfDayIndex(that.weekOfDay); + if (thisWeekOfDay !== thatWeekOfDay) { + return thisWeekOfDay - thatWeekOfDay; + } + } else if (this.weekOfDay) { + return -1; + } else if (that.weekOfDay) { + return 1; + } + + if (this.period && that.period) { + if (this.period[0] !== that.period[0]) { + return this.period[0] - that.period[0]; + } + } else if (this.period) { + return -1; + } else if (that.period) { + return 1; + } + + if (this.name < that.name) { + return -1; + } else if (this.name > that.name) { + return 1; + } + + return 0; + } +} diff --git a/src/common/model/preferences.ts b/src/common/model/preferences.ts new file mode 100644 index 0000000..2b27259 --- /dev/null +++ b/src/common/model/preferences.ts @@ -0,0 +1,33 @@ +export type Preferences = { + // features for all pages + removeForceDownload: { + enabled: boolean; + }; + replaceBreadcrumbCourseName: { + enabled: boolean; + }; + replaceNavigationCourseName: { + enabled: boolean; + }; + + // features for dashboard page + dashboardEventsCountdown: { + enabled: boolean; + }; + dashboardQuickCourseLinks: { + enabled: boolean; + }; + + // video page + scormCollapseToc: { + enabled: boolean; + }; + scormAutoPlay: { + enabled: boolean; + }; + + // login page + loginAutoSubmit: { + enabled: boolean; + }; +}; From f8d00229afb5d294f65d239b1272a30ff756516e Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 18:47:33 +0900 Subject: [PATCH 15/53] feat: implement new storage api --- deno.lock | 4 +- src/common/newStorage/courses/actions.ts | 19 +++++ src/common/newStorage/courses/index.ts | 36 +++++++++ src/common/newStorage/courses/reducer.ts | 21 ++++++ src/common/newStorage/preferences/action.ts | 65 ++++++++++++++++ src/common/newStorage/preferences/index.ts | 56 ++++++++++++++ src/common/newStorage/preferences/reducer.ts | 78 ++++++++++++++++++++ src/common/newStorage/storage.ts | 34 +++++++++ 8 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 src/common/newStorage/courses/actions.ts create mode 100644 src/common/newStorage/courses/index.ts create mode 100644 src/common/newStorage/courses/reducer.ts create mode 100644 src/common/newStorage/preferences/action.ts create mode 100644 src/common/newStorage/preferences/index.ts create mode 100644 src/common/newStorage/preferences/reducer.ts create mode 100644 src/common/newStorage/storage.ts diff --git a/deno.lock b/deno.lock index 0c00538..ade3884 100644 --- a/deno.lock +++ b/deno.lock @@ -291,7 +291,9 @@ "jsr:@std/path@^1.0.6", "jsr:@std/streams@^1.0.7", "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1", - "npm:esbuild@0.24.0" + "npm:@types/webextension-polyfill@~0.12.1", + "npm:esbuild@0.24.0", + "npm:webextension-polyfill@0.12" ] } } diff --git a/src/common/newStorage/courses/actions.ts b/src/common/newStorage/courses/actions.ts new file mode 100644 index 0000000..b38e006 --- /dev/null +++ b/src/common/newStorage/courses/actions.ts @@ -0,0 +1,19 @@ +import type { CourseJson } from '~/common/model/course.ts'; + +export type SaveCoursesAction = { + type: 'saveCourses'; + payload: { + courses: CourseJson[]; + }; +}; + +export type MergeAndSaveCoursesAction = { + type: 'mergeAndSaveCourses'; + payload: { + courses: CourseJson[]; + }; +}; + +export type CoursesAction = + | SaveCoursesAction + | MergeAndSaveCoursesAction; diff --git a/src/common/newStorage/courses/index.ts b/src/common/newStorage/courses/index.ts new file mode 100644 index 0000000..1d1965d --- /dev/null +++ b/src/common/newStorage/courses/index.ts @@ -0,0 +1,36 @@ +import * as storage from '../storage.ts'; +import type { CourseJson } from '~/common/model/course.ts'; +import { coursesReducer } from './reducer.ts'; +import type { CoursesAction } from './actions.ts'; +import { Course } from '~/common/model/course.ts'; + +const storageArea = 'local'; + +const initialCourses: CourseJson[] = []; +let cachedCourses: CourseJson[] | null = null; + +export const getCoursesJson = async function (): Promise { + if (cachedCourses !== null) { + return cachedCourses; + } + + const courses = await storage.get('courses', storageArea); + cachedCourses = courses ?? initialCourses; + + return cachedCourses; +}; + +export const getCourses = async function (): Promise { + return (await getCoursesJson()).map(Course.fromJson); +}; + +export const reduceAndSaveCourses = async function ( + action: CoursesAction, +): Promise { + cachedCourses = coursesReducer(await getCoursesJson(), action); + return storage.set( + 'courses', + cachedCourses, + storageArea, + ); +}; diff --git a/src/common/newStorage/courses/reducer.ts b/src/common/newStorage/courses/reducer.ts new file mode 100644 index 0000000..f112500 --- /dev/null +++ b/src/common/newStorage/courses/reducer.ts @@ -0,0 +1,21 @@ +import type { CourseJson } from '~/common/model/course.ts'; +import type { CoursesAction } from './actions.ts'; + +export const coursesReducer = function ( + courses: CourseJson[], + action: CoursesAction, +): CourseJson[] { + const { payload } = action; + + if (action.type === 'saveCourses') { + return payload.courses; + } else if (action.type === 'mergeAndSaveCourses') { + return [ + ...courses, + ...payload.courses, + ]; + } + + const _: never = action; + throw Error(`Unknown action type: ${(action as CoursesAction).type}`); +}; diff --git a/src/common/newStorage/preferences/action.ts b/src/common/newStorage/preferences/action.ts new file mode 100644 index 0000000..17a9608 --- /dev/null +++ b/src/common/newStorage/preferences/action.ts @@ -0,0 +1,65 @@ +export type PatchRemoveForceDownloadAction = { + type: 'patchRemoveForceDownload'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchReplaceBreadcrumbCourseNameAction = { + type: 'patchReplaceBreadcrumbCourseName'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchReplaceNavigationCourseNameAction = { + type: 'patchReplaceNavigationCourseName'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchDashboardEventsCountdownAction = { + type: 'patchDashboardEventsCountdown'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchDashboardQuickCourseLinksAction = { + type: 'patchDashboardQuickCourseLinks'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchScormCollapseTocAction = { + type: 'patchScormCollapseToc'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchScormAutoPlayAction = { + type: 'patchScormAutoPlay'; + payload: { + enabled?: boolean; + }; +}; + +export type PatchLoginAutoSubmitAction = { + type: 'patchLoginAutoSubmit'; + payload: { + enabled?: boolean; + }; +}; + +export type PreferencesAction = + | PatchRemoveForceDownloadAction + | PatchReplaceBreadcrumbCourseNameAction + | PatchReplaceNavigationCourseNameAction + | PatchDashboardEventsCountdownAction + | PatchDashboardQuickCourseLinksAction + | PatchScormCollapseTocAction + | PatchScormAutoPlayAction + | PatchLoginAutoSubmitAction; diff --git a/src/common/newStorage/preferences/index.ts b/src/common/newStorage/preferences/index.ts new file mode 100644 index 0000000..12d65f4 --- /dev/null +++ b/src/common/newStorage/preferences/index.ts @@ -0,0 +1,56 @@ +import * as storage from '../storage.ts'; +import type { Preferences } from '~/common/model/preferences.ts'; +import { preferencesReducer } from './reducer.ts'; +import type { PreferencesAction } from './action.ts'; + +const storageArea = 'local'; + +export const initialPreferences: Preferences = { + removeForceDownload: { + enabled: true, + }, + replaceBreadcrumbCourseName: { + enabled: true, + }, + replaceNavigationCourseName: { + enabled: true, + }, + dashboardEventsCountdown: { + enabled: true, + }, + dashboardQuickCourseLinks: { + enabled: true, + }, + scormCollapseToc: { + enabled: true, + }, + scormAutoPlay: { + enabled: false, + }, + loginAutoSubmit: { + enabled: true, + }, +}; +let cachedPreferences: Preferences | null = null; + +export const getPreferences = async function (): Promise { + if (cachedPreferences !== null) { + return cachedPreferences; + } + + const preferences = await storage.get('preferences', storageArea); + cachedPreferences = preferences ?? initialPreferences; + + return cachedPreferences; +}; + +export const reduceAndSavePreferences = async function ( + action: PreferencesAction, +): Promise { + cachedPreferences = preferencesReducer(await getPreferences(), action); + return storage.set( + 'preferences', + cachedPreferences, + storageArea, + ); +}; diff --git a/src/common/newStorage/preferences/reducer.ts b/src/common/newStorage/preferences/reducer.ts new file mode 100644 index 0000000..1c17a21 --- /dev/null +++ b/src/common/newStorage/preferences/reducer.ts @@ -0,0 +1,78 @@ +import type { Preferences } from '~/common/model/preferences.ts'; +import type { PreferencesAction } from './action.ts'; + +export const preferencesReducer = function ( + preferences: Preferences, + action: PreferencesAction, +): Preferences { + const { payload } = action; + + if (action.type === 'patchRemoveForceDownload') { + return { + ...preferences, + removeForceDownload: { + ...preferences.removeForceDownload, + ...payload, + }, + }; + } else if (action.type === 'patchReplaceBreadcrumbCourseName') { + return { + ...preferences, + replaceBreadcrumbCourseName: { + ...preferences.replaceBreadcrumbCourseName, + ...payload, + }, + }; + } else if (action.type === 'patchReplaceNavigationCourseName') { + return { + ...preferences, + replaceNavigationCourseName: { + ...preferences.replaceNavigationCourseName, + ...payload, + }, + }; + } else if (action.type === 'patchDashboardEventsCountdown') { + return { + ...preferences, + dashboardEventsCountdown: { + ...preferences.dashboardEventsCountdown, + ...payload, + }, + }; + } else if (action.type === 'patchDashboardQuickCourseLinks') { + return { + ...preferences, + dashboardQuickCourseLinks: { + ...preferences.dashboardQuickCourseLinks, + ...payload, + }, + }; + } else if (action.type === 'patchScormCollapseToc') { + return { + ...preferences, + scormCollapseToc: { + ...preferences.scormCollapseToc, + ...payload, + }, + }; + } else if (action.type === 'patchScormAutoPlay') { + return { + ...preferences, + scormAutoPlay: { + ...preferences.scormAutoPlay, + ...payload, + }, + }; + } else if (action.type === 'patchLoginAutoSubmit') { + return { + ...preferences, + loginAutoSubmit: { + ...preferences.loginAutoSubmit, + ...payload, + }, + }; + } + + const _: never = action; + throw Error(`Unknown action type: ${(action as PreferencesAction).type}`); +}; diff --git a/src/common/newStorage/storage.ts b/src/common/newStorage/storage.ts new file mode 100644 index 0000000..5126e07 --- /dev/null +++ b/src/common/newStorage/storage.ts @@ -0,0 +1,34 @@ +// @deno-types="@types/webextension-polyfill" +import browser from 'webextension-polyfill'; + +import type { CourseJson } from '~/common/model/course.ts'; +import type { Preferences } from '~/common/model/preferences.ts'; + +type Subtract = T extends U ? never : T; + +type StorageSchema = { + courses: CourseJson[]; + preferences: Preferences; + version: number; +}; +type StorageKey = Subtract; + +type StorageAreaName = 'local' | 'sync' | 'managed' | 'session'; + +const storageGet = async function ( + key: K, + storageArea: StorageAreaName, +): Promise { + const data = await browser.storage[storageArea].get(key); + return data[key] as StorageSchema[K]; +}; + +const storageSet = async function ( + key: K, + value: StorageSchema[K], + storageArea: StorageAreaName, +): Promise { + await browser.storage[storageArea].set({ [key]: value, version: 1 }); +}; + +export { storageGet as get, storageSet as set }; From eb4d22c762e6144db5c789c3842ffbd16a6c5299 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 24 Oct 2024 18:55:29 +0900 Subject: [PATCH 16/53] chore: rename directory from content_scripts to contentScripts --- .../allPages/allPages.ts | 0 .../allPages/removeForceDownload.ts | 0 .../allPages/replaceHeaderCourseName.ts | 0 .../allPages/replaceNavigationText.ts | 0 .../common/debugMode.ts | 0 .../common/loadFeature.ts | 0 .../common/types.ts | 0 .../dashboard/dashboard.scss | 0 .../dashboard/dashboard.ts | 0 .../dashboard/eventsCountdown.ts | 0 .../dashboard/eventsCountdown/EventsCountdown.tsx | 0 .../dashboard/quickCourseView.scss | 0 .../dashboard/quickCourseView/CourseItem.tsx | 0 .../dashboard/quickCourseView/ListGroup.tsx | 0 .../dashboard/quickCourseView/QuickCourseView.tsx | 0 .../quickCourseView/QuickCourseViewBody.tsx | 0 .../quickCourseView/QuickCourseViewControl.tsx | 0 .../dashboard/quickCourseView/defs.ts | 0 .../dashboard/renderQuickCourseView.ts | 0 .../dashboard/updateCourseRepository.ts | 0 .../dashboard/waitForPageLoad.ts | 0 .../scorm/collapseToc.ts | 0 .../scorm/scorm.scss | 0 .../scorm/scorm.ts | 0 src/manifest.jsonc | 12 ++++++------ 25 files changed, 6 insertions(+), 6 deletions(-) rename src/{content_scripts => contentScripts}/allPages/allPages.ts (100%) rename src/{content_scripts => contentScripts}/allPages/removeForceDownload.ts (100%) rename src/{content_scripts => contentScripts}/allPages/replaceHeaderCourseName.ts (100%) rename src/{content_scripts => contentScripts}/allPages/replaceNavigationText.ts (100%) rename src/{content_scripts => contentScripts}/common/debugMode.ts (100%) rename src/{content_scripts => contentScripts}/common/loadFeature.ts (100%) rename src/{content_scripts => contentScripts}/common/types.ts (100%) rename src/{content_scripts => contentScripts}/dashboard/dashboard.scss (100%) rename src/{content_scripts => contentScripts}/dashboard/dashboard.ts (100%) rename src/{content_scripts => contentScripts}/dashboard/eventsCountdown.ts (100%) rename src/{content_scripts => contentScripts}/dashboard/eventsCountdown/EventsCountdown.tsx (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView.scss (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView/CourseItem.tsx (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView/ListGroup.tsx (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView/QuickCourseView.tsx (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView/QuickCourseViewBody.tsx (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView/QuickCourseViewControl.tsx (100%) rename src/{content_scripts => contentScripts}/dashboard/quickCourseView/defs.ts (100%) rename src/{content_scripts => contentScripts}/dashboard/renderQuickCourseView.ts (100%) rename src/{content_scripts => contentScripts}/dashboard/updateCourseRepository.ts (100%) rename src/{content_scripts => contentScripts}/dashboard/waitForPageLoad.ts (100%) rename src/{content_scripts => contentScripts}/scorm/collapseToc.ts (100%) rename src/{content_scripts => contentScripts}/scorm/scorm.scss (100%) rename src/{content_scripts => contentScripts}/scorm/scorm.ts (100%) diff --git a/src/content_scripts/allPages/allPages.ts b/src/contentScripts/allPages/allPages.ts similarity index 100% rename from src/content_scripts/allPages/allPages.ts rename to src/contentScripts/allPages/allPages.ts diff --git a/src/content_scripts/allPages/removeForceDownload.ts b/src/contentScripts/allPages/removeForceDownload.ts similarity index 100% rename from src/content_scripts/allPages/removeForceDownload.ts rename to src/contentScripts/allPages/removeForceDownload.ts diff --git a/src/content_scripts/allPages/replaceHeaderCourseName.ts b/src/contentScripts/allPages/replaceHeaderCourseName.ts similarity index 100% rename from src/content_scripts/allPages/replaceHeaderCourseName.ts rename to src/contentScripts/allPages/replaceHeaderCourseName.ts diff --git a/src/content_scripts/allPages/replaceNavigationText.ts b/src/contentScripts/allPages/replaceNavigationText.ts similarity index 100% rename from src/content_scripts/allPages/replaceNavigationText.ts rename to src/contentScripts/allPages/replaceNavigationText.ts diff --git a/src/content_scripts/common/debugMode.ts b/src/contentScripts/common/debugMode.ts similarity index 100% rename from src/content_scripts/common/debugMode.ts rename to src/contentScripts/common/debugMode.ts diff --git a/src/content_scripts/common/loadFeature.ts b/src/contentScripts/common/loadFeature.ts similarity index 100% rename from src/content_scripts/common/loadFeature.ts rename to src/contentScripts/common/loadFeature.ts diff --git a/src/content_scripts/common/types.ts b/src/contentScripts/common/types.ts similarity index 100% rename from src/content_scripts/common/types.ts rename to src/contentScripts/common/types.ts diff --git a/src/content_scripts/dashboard/dashboard.scss b/src/contentScripts/dashboard/dashboard.scss similarity index 100% rename from src/content_scripts/dashboard/dashboard.scss rename to src/contentScripts/dashboard/dashboard.scss diff --git a/src/content_scripts/dashboard/dashboard.ts b/src/contentScripts/dashboard/dashboard.ts similarity index 100% rename from src/content_scripts/dashboard/dashboard.ts rename to src/contentScripts/dashboard/dashboard.ts diff --git a/src/content_scripts/dashboard/eventsCountdown.ts b/src/contentScripts/dashboard/eventsCountdown.ts similarity index 100% rename from src/content_scripts/dashboard/eventsCountdown.ts rename to src/contentScripts/dashboard/eventsCountdown.ts diff --git a/src/content_scripts/dashboard/eventsCountdown/EventsCountdown.tsx b/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx similarity index 100% rename from src/content_scripts/dashboard/eventsCountdown/EventsCountdown.tsx rename to src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx diff --git a/src/content_scripts/dashboard/quickCourseView.scss b/src/contentScripts/dashboard/quickCourseView.scss similarity index 100% rename from src/content_scripts/dashboard/quickCourseView.scss rename to src/contentScripts/dashboard/quickCourseView.scss diff --git a/src/content_scripts/dashboard/quickCourseView/CourseItem.tsx b/src/contentScripts/dashboard/quickCourseView/CourseItem.tsx similarity index 100% rename from src/content_scripts/dashboard/quickCourseView/CourseItem.tsx rename to src/contentScripts/dashboard/quickCourseView/CourseItem.tsx diff --git a/src/content_scripts/dashboard/quickCourseView/ListGroup.tsx b/src/contentScripts/dashboard/quickCourseView/ListGroup.tsx similarity index 100% rename from src/content_scripts/dashboard/quickCourseView/ListGroup.tsx rename to src/contentScripts/dashboard/quickCourseView/ListGroup.tsx diff --git a/src/content_scripts/dashboard/quickCourseView/QuickCourseView.tsx b/src/contentScripts/dashboard/quickCourseView/QuickCourseView.tsx similarity index 100% rename from src/content_scripts/dashboard/quickCourseView/QuickCourseView.tsx rename to src/contentScripts/dashboard/quickCourseView/QuickCourseView.tsx diff --git a/src/content_scripts/dashboard/quickCourseView/QuickCourseViewBody.tsx b/src/contentScripts/dashboard/quickCourseView/QuickCourseViewBody.tsx similarity index 100% rename from src/content_scripts/dashboard/quickCourseView/QuickCourseViewBody.tsx rename to src/contentScripts/dashboard/quickCourseView/QuickCourseViewBody.tsx diff --git a/src/content_scripts/dashboard/quickCourseView/QuickCourseViewControl.tsx b/src/contentScripts/dashboard/quickCourseView/QuickCourseViewControl.tsx similarity index 100% rename from src/content_scripts/dashboard/quickCourseView/QuickCourseViewControl.tsx rename to src/contentScripts/dashboard/quickCourseView/QuickCourseViewControl.tsx diff --git a/src/content_scripts/dashboard/quickCourseView/defs.ts b/src/contentScripts/dashboard/quickCourseView/defs.ts similarity index 100% rename from src/content_scripts/dashboard/quickCourseView/defs.ts rename to src/contentScripts/dashboard/quickCourseView/defs.ts diff --git a/src/content_scripts/dashboard/renderQuickCourseView.ts b/src/contentScripts/dashboard/renderQuickCourseView.ts similarity index 100% rename from src/content_scripts/dashboard/renderQuickCourseView.ts rename to src/contentScripts/dashboard/renderQuickCourseView.ts diff --git a/src/content_scripts/dashboard/updateCourseRepository.ts b/src/contentScripts/dashboard/updateCourseRepository.ts similarity index 100% rename from src/content_scripts/dashboard/updateCourseRepository.ts rename to src/contentScripts/dashboard/updateCourseRepository.ts diff --git a/src/content_scripts/dashboard/waitForPageLoad.ts b/src/contentScripts/dashboard/waitForPageLoad.ts similarity index 100% rename from src/content_scripts/dashboard/waitForPageLoad.ts rename to src/contentScripts/dashboard/waitForPageLoad.ts diff --git a/src/content_scripts/scorm/collapseToc.ts b/src/contentScripts/scorm/collapseToc.ts similarity index 100% rename from src/content_scripts/scorm/collapseToc.ts rename to src/contentScripts/scorm/collapseToc.ts diff --git a/src/content_scripts/scorm/scorm.scss b/src/contentScripts/scorm/scorm.scss similarity index 100% rename from src/content_scripts/scorm/scorm.scss rename to src/contentScripts/scorm/scorm.scss diff --git a/src/content_scripts/scorm/scorm.ts b/src/contentScripts/scorm/scorm.ts similarity index 100% rename from src/content_scripts/scorm/scorm.ts rename to src/contentScripts/scorm/scorm.ts diff --git a/src/manifest.jsonc b/src/manifest.jsonc index b2f25b2..7964b21 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -18,7 +18,7 @@ { // all pages "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - "js": ["./content_scripts/allPages/allPages.ts"] + "js": ["./contentScripts/allPages/allPages.ts"] }, { // dashboard @@ -26,8 +26,8 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/my/", "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php" ], - "js": ["./content_scripts/dashboard/dashboard.ts"], - "css": ["./content_scripts/dashboard/dashboard.scss"] + "js": ["./contentScripts/dashboard/dashboard.ts"], + "css": ["./contentScripts/dashboard/dashboard.scss"] }, { // video page @@ -35,7 +35,7 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*" ], - "js": ["./content_scripts/scorm/scorm.ts"] + "js": ["./contentScripts/scorm/scorm.ts"] }, { // scorm content page @@ -43,7 +43,7 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*" ], "all_frames": true, - "css": ["./content_scripts/scorm/scorm.scss"] + "css": ["./contentScripts/scorm/scorm.scss"] } ], @@ -67,7 +67,7 @@ "web_accessible_resources": [ // source maps { - "resources": ["content_scripts/*/*.map"], + "resources": ["contentScripts/*/*.map"], "matches": [""] } ] From dc21eec8d2a087d62c808f652377b92fe105947b Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 25 Oct 2024 22:09:31 +0900 Subject: [PATCH 17/53] build: add debug switch plugin --- build/build.ts | 2 ++ build/options/javascript.ts | 10 ++++++++++ deno.json | 5 +++-- deno.lock | 28 +++++++++++++++++++--------- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/build/build.ts b/build/build.ts index bb88e2d..000c012 100644 --- a/build/build.ts +++ b/build/build.ts @@ -63,6 +63,8 @@ const buildOptions = [ dev: devMode, jsxFactory: denoConfig.jsxFactory, jsxFragmentFactory: denoConfig.jsxFragmentFactory, + extensionName: manifest.name, + extensionVersion: manifest.version, }), cssBuildOptions({ entryPoints: cssEntryPoints, diff --git a/build/options/javascript.ts b/build/options/javascript.ts index fc8966d..cfa33b2 100644 --- a/build/options/javascript.ts +++ b/build/options/javascript.ts @@ -1,5 +1,6 @@ import * as esbuild from 'esbuild'; import { denoPlugins } from 'esbuild-deno-loader'; +import { debugSwitchPlugin } from 'esbuild-plugin-debug-switch/plugin'; type BuildOptionsOptions = { entryPoints: esbuild.BuildOptions['entryPoints']; @@ -8,6 +9,8 @@ type BuildOptionsOptions = { dev: boolean; jsxFactory?: string; jsxFragmentFactory?: string; + extensionName: string; + extensionVersion: string; }; export const buildOptions = ( @@ -24,6 +27,13 @@ export const buildOptions = ( jsxFactory: options.jsxFactory, jsxFragment: options.jsxFragmentFactory, plugins: [ + debugSwitchPlugin({ + isDebug: options.dev, + env: { + extensionName: options.extensionName, + extensionVersion: options.extensionVersion, + }, + }), ...denoPlugins(), ], }); diff --git a/deno.json b/deno.json index a035327..24a5ed8 100644 --- a/deno.json +++ b/deno.json @@ -37,7 +37,6 @@ "setup-vscode": "deno run --allow-run --allow-read --allow-write scripts/setup-vscode.ts" }, "imports": { - "~/": "./src/", "@std/fs": "jsr:@std/fs@^1.0.4", "@std/jsonc": "jsr:@std/jsonc@^1.0.1", "@std/path": "jsr:@std/path@^1.0.6", @@ -45,6 +44,7 @@ "@types/webextension-polyfill": "npm:@types/webextension-polyfill@^0.12.1", "esbuild": "npm:esbuild@0.24.0", "esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.0", + "esbuild-plugin-debug-switch": "jsr:@tsukina-7mochi/esbuild-plugin-debug-switch@^0.2.0", "esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", "preact": "https://esm.sh/preact@10.13.2", @@ -53,7 +53,8 @@ "preact/jsx-dev-runtime": "https://esm.sh/preact@10.13.2/jsx-dev-runtime", "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", "preact/types": "https://raw.githubusercontent.com/preactjs/preact/10.13.2/src/index.d.ts", - "webextension-polyfill": "npm:webextension-polyfill@^0.12.0" + "webextension-polyfill": "npm:webextension-polyfill@^0.12.0", + "~/": "./src/" }, "exclude": [ "dist/" diff --git a/deno.lock b/deno.lock index ade3884..ad0747b 100644 --- a/deno.lock +++ b/deno.lock @@ -4,11 +4,14 @@ "jsr:@luca/esbuild-deno-loader@0.11": "0.11.0", "jsr:@std/bytes@^1.0.2": "1.0.2", "jsr:@std/encoding@^1.0.5": "1.0.5", - "jsr:@std/fs@^1.0.4": "1.0.4", + "jsr:@std/fs@^1.0.4": "1.0.5", "jsr:@std/jsonc@^1.0.1": "1.0.1", - "jsr:@std/path@^1.0.6": "1.0.6", + "jsr:@std/path@^1.0.6": "1.0.7", + "jsr:@std/path@^1.0.7": "1.0.7", "jsr:@std/streams@^1.0.7": "1.0.7", + "jsr:@tsukina-7mochi/esbuild-plugin-debug-switch@0.2": "0.2.0", "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1": "0.1.1", + "npm:esbuild@0.24": "0.24.0", "npm:esbuild@0.24.0": "0.24.0", "npm:sass@^1.80.3": "1.80.3" }, @@ -18,7 +21,7 @@ "dependencies": [ "jsr:@std/bytes", "jsr:@std/encoding", - "jsr:@std/path" + "jsr:@std/path@^1.0.6" ] }, "@std/bytes@1.0.2": { @@ -27,17 +30,17 @@ "@std/encoding@1.0.5": { "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" }, - "@std/fs@1.0.4": { - "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", + "@std/fs@1.0.5": { + "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e", "dependencies": [ - "jsr:@std/path" + "jsr:@std/path@^1.0.7" ] }, "@std/jsonc@1.0.1": { "integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda" }, - "@std/path@1.0.6": { - "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" + "@std/path@1.0.7": { + "integrity": "76a689e07f0e15dcc6002ec39d0866797e7156629212b28f27179b8a5c3b33a1" }, "@std/streams@1.0.7": { "integrity": "1a93917ca0c58c01b2bfb93647189229b1702677f169b6fb61ad6241cd2e499b", @@ -45,10 +48,16 @@ "jsr:@std/bytes" ] }, + "@tsukina-7mochi/esbuild-plugin-debug-switch@0.2.0": { + "integrity": "2d4dd6d548a48cda926d8fcb5e44420476ca994f057f493b3e53aff945a924ae", + "dependencies": [ + "npm:esbuild@0.24" + ] + }, "@tsukina-7mochi/esbuild-plugin-sass@0.1.1": { "integrity": "5bbc148b4bbc9ca5425be4d2c6e37257d0bf1dc9ce946f9e3be3bae7b445056e", "dependencies": [ - "jsr:@std/path", + "jsr:@std/path@^1.0.6", "npm:sass" ] } @@ -290,6 +299,7 @@ "jsr:@std/jsonc@^1.0.1", "jsr:@std/path@^1.0.6", "jsr:@std/streams@^1.0.7", + "jsr:@tsukina-7mochi/esbuild-plugin-debug-switch@0.2", "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1", "npm:@types/webextension-polyfill@~0.12.1", "npm:esbuild@0.24.0", From 457ceb59259a45b0d181f1f7a3105830809c773b Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 25 Oct 2024 22:16:08 +0900 Subject: [PATCH 18/53] fix: change export format from esm to default --- build/options/javascript.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/build/options/javascript.ts b/build/options/javascript.ts index cfa33b2..1282a65 100644 --- a/build/options/javascript.ts +++ b/build/options/javascript.ts @@ -19,9 +19,7 @@ export const buildOptions = ( entryPoints: options.entryPoints, outdir: options.destPath, platform: 'browser', - format: 'esm', bundle: true, - splitting: true, sourcemap: options.dev ? 'linked' : false, minify: !options.dev, jsxFactory: options.jsxFactory, From 8a8f3373df5ae7f554376b345fe2598caa7c8a12 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 25 Oct 2024 22:17:34 +0900 Subject: [PATCH 19/53] feat: add plugin to output start message --- build/manifest.ts | 12 ++++++++++++ src/contentScripts/allPages/startMessage/index.ts | 8 ++++++++ src/manifest.jsonc | 11 ++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/contentScripts/allPages/startMessage/index.ts diff --git a/build/manifest.ts b/build/manifest.ts index 9c1f6a3..59a2c1e 100644 --- a/build/manifest.ts +++ b/build/manifest.ts @@ -44,6 +44,18 @@ export type ExtendedWebExtensionManifest = WebExtensionManifest & { export class Manifest { private manifest: ExtendedWebExtensionManifest; + get manifest_version(): (typeof this.manifest)['manifest_version'] { + return this.manifest.manifest_version; + } + + get name(): (typeof this.manifest)['name'] { + return this.manifest.name; + } + + get version(): (typeof this.manifest)['version'] { + return this.manifest.version; + } + private constructor(manifest: Manifest['manifest']) { this.manifest = manifest; } diff --git a/src/contentScripts/allPages/startMessage/index.ts b/src/contentScripts/allPages/startMessage/index.ts new file mode 100644 index 0000000..8f7141f --- /dev/null +++ b/src/contentScripts/allPages/startMessage/index.ts @@ -0,0 +1,8 @@ +// Show message when the extension is loaded. + +import { env, isDebug } from 'esbuild-plugin-debug-switch'; + +console.log(`${env.extensionName} ${env.extensionVersion} is loaded.`); +if (isDebug) { + console.log('Debug mode is enabled.'); +} diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 7964b21..18fad21 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -15,13 +15,19 @@ "author": "nitech Create", "content_scripts": [ + ///// all pages ///// + // start message + { + "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], + "js": ["./contentScripts/allPages/startMessage/index.ts"] + }, { // all pages "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], "js": ["./contentScripts/allPages/allPages.ts"] }, + ///// dashboard ///// { - // dashboard "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/my/", "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php" @@ -29,8 +35,8 @@ "js": ["./contentScripts/dashboard/dashboard.ts"], "css": ["./contentScripts/dashboard/dashboard.scss"] }, + ///// scorm page ///// { - // video page "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*" @@ -38,7 +44,6 @@ "js": ["./contentScripts/scorm/scorm.ts"] }, { - // scorm content page "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*" ], From 0ac4025c26d31ed3d9c8f649dbe28074daf43a44 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 25 Oct 2024 23:27:46 +0900 Subject: [PATCH 20/53] feat: migrate removeForceDownload --- .../allPages/removeForceDownload.ts | 22 -------- .../allPages/removeForceDownload/index.ts | 56 +++++++++++++++++++ src/manifest.jsonc | 17 +++--- 3 files changed, 63 insertions(+), 32 deletions(-) delete mode 100644 src/contentScripts/allPages/removeForceDownload.ts create mode 100644 src/contentScripts/allPages/removeForceDownload/index.ts diff --git a/src/contentScripts/allPages/removeForceDownload.ts b/src/contentScripts/allPages/removeForceDownload.ts deleted file mode 100644 index afbb391..0000000 --- a/src/contentScripts/allPages/removeForceDownload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Feature } from '../common/types.ts'; - -/** 強制ダウンロードのリンクをブラウザで開くようにする */ -const removeForceDownload: Feature = { - uniqueName: 'all-pages-remove-force-download', - hostnameFilter: 'cms7.ict.nitech.ac.jp', - pathnameFilter: /^\/moodle40a\//, - loader: async (options) => { - if (!options.enabled) { - return; - } - - // 読み込み待ちのため遅延を入れる - await new Promise((resolve) => setTimeout(resolve, 1000)); - - document.querySelectorAll('a').forEach((link) => { - link.href = link.href.replace('forcedownload=1', ''); - }); - }, -}; - -export default removeForceDownload; diff --git a/src/contentScripts/allPages/removeForceDownload/index.ts b/src/contentScripts/allPages/removeForceDownload/index.ts new file mode 100644 index 0000000..33bb1bd --- /dev/null +++ b/src/contentScripts/allPages/removeForceDownload/index.ts @@ -0,0 +1,56 @@ +import { isDebug } from 'esbuild-plugin-debug-switch'; + +import { getPreferences } from '~/common/newStorage/preferences/index.ts'; + +const parseUrl = function (url: string): URL | null { + try { + return new URL(url); + } catch { + return null; + } +}; + +const forceDownloadRemoved = function (url: URL): URL { + const removed = new URL(url); + removed.searchParams.delete('forcedownload', '1'); + + return removed; +}; + +const debounce = function (callback: () => void, timeout: number): () => void { + let timer: number | undefined = undefined; + return () => { + clearTimeout(timer); + timer = setTimeout(callback, timeout); + }; +}; + +(async () => { + const preferences = await getPreferences(); + if (!preferences.removeForceDownload.enabled) { + return; + } + + if (isDebug) { + console.log('RemoveForceDownload is enabled.'); + } + + const removeForceDownload = () => { + if (isDebug) { + console.log('Removing forcedownload'); + } + + document.querySelectorAll('a').forEach((link) => { + const url = parseUrl(link.href); + if (url === null) return; + + link.href = forceDownloadRemoved(url).toString(); + }); + }; + removeForceDownload(); + + const observer = new MutationObserver( + debounce(removeForceDownload, 500), + ); + observer.observe(document.body, { childList: true }); +})(); diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 18fad21..c286315 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -15,18 +15,15 @@ "author": "nitech Create", "content_scripts": [ - ///// all pages ///// - // start message + // all pages { "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - "js": ["./contentScripts/allPages/startMessage/index.ts"] + "js": [ + "./contentScripts/allPages/startMessage/index.ts", + "./contentScripts/allPages/removeForceDownload/index.ts" + ] }, - { - // all pages - "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - "js": ["./contentScripts/allPages/allPages.ts"] - }, - ///// dashboard ///// + // dashboard { "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/my/", @@ -35,7 +32,7 @@ "js": ["./contentScripts/dashboard/dashboard.ts"], "css": ["./contentScripts/dashboard/dashboard.scss"] }, - ///// scorm page ///// + // scorm page { "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", From dc2337f40f39057408626904efac50007ea42ed8 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Sat, 26 Oct 2024 11:44:55 +0900 Subject: [PATCH 21/53] feat: add function to store courses on dashboard --- .../dashboard/readAndStoreCourses/index.ts | 57 +++++++++++++++++++ src/manifest.jsonc | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/contentScripts/dashboard/readAndStoreCourses/index.ts diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts new file mode 100644 index 0000000..87eca01 --- /dev/null +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -0,0 +1,57 @@ +import { isDebug } from 'esbuild-plugin-debug-switch'; + +import { Course } from '~/common/model/course.ts'; +import { reduceAndSaveCourses } from '~/common/newStorage/courses/index.ts'; + +const debounce = function (callback: () => void, timeout: number): () => void { + let timer: number | undefined = undefined; + return () => { + clearTimeout(timer); + timer = setTimeout(callback, timeout); + }; +}; + +const main = function () { + if (isDebug) { + console.log('Running ReadAndStoreCoureses'); + } + + const readAndStoreCoureses = function () { + if (isDebug) { + console.log('Reading courses'); + } + + const myOverview = document.querySelector('*[data-block="myoverview"]'); + if (!myOverview) return; + + const links = Array.from(myOverview.querySelectorAll( + '*[data-region="courses-view"] ul a.coursename', + )) as HTMLAnchorElement[]; + const coursesJson = links.map((link) => { + try { + const id = new URL(link.href).searchParams.get('id'); + if (!id) return null; + + return Course.fromJson({ + id, + ...Course.parse(link.textContent || ''), + }).toJson(); + } catch { + return null; + } + }).filter((course) => course !== null); + + reduceAndSaveCourses({ + type: 'mergeAndSaveCourses', + payload: { + courses: coursesJson, + }, + }); + }; + readAndStoreCoureses(); + + const observer = new MutationObserver(debounce(readAndStoreCoureses, 500)); + observer.observe(document.body, { subtree: true, childList: true }); +}; + +main(); diff --git a/src/manifest.jsonc b/src/manifest.jsonc index c286315..bb01554 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -29,7 +29,7 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/my/", "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php" ], - "js": ["./contentScripts/dashboard/dashboard.ts"], + "js": ["./contentScripts/dashboard/readAndStoreCourses/index.ts"], "css": ["./contentScripts/dashboard/dashboard.scss"] }, // scorm page From 5ab1ced60df6142fd04f8ee2badb91f6434192a2 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Sat, 26 Oct 2024 11:45:22 +0900 Subject: [PATCH 22/53] fix: fix course reducer not merging couress --- src/common/newStorage/courses/reducer.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/common/newStorage/courses/reducer.ts b/src/common/newStorage/courses/reducer.ts index f112500..818d4d0 100644 --- a/src/common/newStorage/courses/reducer.ts +++ b/src/common/newStorage/courses/reducer.ts @@ -10,10 +10,16 @@ export const coursesReducer = function ( if (action.type === 'saveCourses') { return payload.courses; } else if (action.type === 'mergeAndSaveCourses') { - return [ - ...courses, - ...payload.courses, - ]; + const idCourseMap = new Map(); + + for (const course of courses) { + idCourseMap.set(course.id, course); + } + for (const course of payload.courses) { + idCourseMap.set(course.id, course); + } + + return [...idCourseMap.values()]; } const _: never = action; From 368281eadfba14ef5286b97dcff231a56e22f8bc Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Sat, 26 Oct 2024 11:52:43 +0900 Subject: [PATCH 23/53] refactor: extract debounce function --- src/common/debounceCallback.ts | 15 +++++++++++++++ .../allPages/removeForceDownload/index.ts | 11 ++--------- .../dashboard/readAndStoreCourses/index.ts | 13 ++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 src/common/debounceCallback.ts diff --git a/src/common/debounceCallback.ts b/src/common/debounceCallback.ts new file mode 100644 index 0000000..ad4d700 --- /dev/null +++ b/src/common/debounceCallback.ts @@ -0,0 +1,15 @@ +type Callback = (...args: Args) => void; + +export const debounceCallback = function < + Args extends unknown[], + ThisType = unknown, +>( + callback: Callback, + timeout: number, +): Callback { + let timer: number | undefined = undefined; + return function (this: ThisType, ...args: Args) { + clearTimeout(timer); + timer = setTimeout(() => callback.apply(this, args), timeout); + }; +}; diff --git a/src/contentScripts/allPages/removeForceDownload/index.ts b/src/contentScripts/allPages/removeForceDownload/index.ts index 33bb1bd..ca5f173 100644 --- a/src/contentScripts/allPages/removeForceDownload/index.ts +++ b/src/contentScripts/allPages/removeForceDownload/index.ts @@ -1,5 +1,6 @@ import { isDebug } from 'esbuild-plugin-debug-switch'; +import { debounceCallback } from '~/common/debounceCallback.ts'; import { getPreferences } from '~/common/newStorage/preferences/index.ts'; const parseUrl = function (url: string): URL | null { @@ -17,14 +18,6 @@ const forceDownloadRemoved = function (url: URL): URL { return removed; }; -const debounce = function (callback: () => void, timeout: number): () => void { - let timer: number | undefined = undefined; - return () => { - clearTimeout(timer); - timer = setTimeout(callback, timeout); - }; -}; - (async () => { const preferences = await getPreferences(); if (!preferences.removeForceDownload.enabled) { @@ -50,7 +43,7 @@ const debounce = function (callback: () => void, timeout: number): () => void { removeForceDownload(); const observer = new MutationObserver( - debounce(removeForceDownload, 500), + debounceCallback(removeForceDownload, 500), ); observer.observe(document.body, { childList: true }); })(); diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts index 87eca01..b46ba85 100644 --- a/src/contentScripts/dashboard/readAndStoreCourses/index.ts +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -1,16 +1,9 @@ import { isDebug } from 'esbuild-plugin-debug-switch'; import { Course } from '~/common/model/course.ts'; +import { debounceCallback } from '~/common/debounceCallback.ts'; import { reduceAndSaveCourses } from '~/common/newStorage/courses/index.ts'; -const debounce = function (callback: () => void, timeout: number): () => void { - let timer: number | undefined = undefined; - return () => { - clearTimeout(timer); - timer = setTimeout(callback, timeout); - }; -}; - const main = function () { if (isDebug) { console.log('Running ReadAndStoreCoureses'); @@ -50,7 +43,9 @@ const main = function () { }; readAndStoreCoureses(); - const observer = new MutationObserver(debounce(readAndStoreCoureses, 500)); + const observer = new MutationObserver( + debounceCallback(readAndStoreCoureses, 500), + ); observer.observe(document.body, { subtree: true, childList: true }); }; From 3670af0593a8636f55ebf4fb54d592cf0ff08172 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 13:29:56 +0900 Subject: [PATCH 24/53] feat: add function to replace course name in breadcrumb menu --- src/common/model/course.ts | 5 ++- .../replaceBreadcrumbCourseName/index.ts | 44 +++++++++++++++++++ src/manifest.jsonc | 3 +- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts diff --git a/src/common/model/course.ts b/src/common/model/course.ts index 8c51d5a..8b24e3c 100644 --- a/src/common/model/course.ts +++ b/src/common/model/course.ts @@ -119,7 +119,7 @@ export class Course { if (nameIndex > 0 && nameIndex + 1 < segments.length) { return segments.slice(nameIndex + 1).join(' '); } - return text; + return cleanText; })(); const fullName = cleanText; @@ -138,7 +138,7 @@ export class Course { fullYear = parseInt(year); } if (seg1 && seg2 && seg3) { - systemCourseName = `${seg1}-${seg2}-${seg3}`; + systemCourseName = `${seg1.slice(-2)}-${seg2}-${seg3}`; } } @@ -181,6 +181,7 @@ export class Course { id: this.id, name: this.name, fullName: this.fullName, + systemCourseName: this.systemCourseName, fullYear: this.fullYear, semesterStart: this.semesterStart, semesterEnd: this.semesterEnd, diff --git a/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts b/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts new file mode 100644 index 0000000..bc20c63 --- /dev/null +++ b/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts @@ -0,0 +1,44 @@ +import { isDebug } from 'esbuild-plugin-debug-switch'; + +import { debounceCallback } from '~/common/debounceCallback.ts'; +import { getPreferences } from '~/common/newStorage/preferences/index.ts'; +import { getCourses } from '~/common/newStorage/courses/index.ts'; + +const main = async function () { + const preferences = await getPreferences(); + if (!preferences.replaceBreadcrumbCourseName.enabled) return; + + if (isDebug) { + console.log('ReplaveBreadcrumbCourseName is enabled.'); + } + + const courses = await getCourses(); + const replacementMap = new Map(); + for (const course of courses) { + if (!course.systemCourseName) continue; + replacementMap.set(course.systemCourseName, course.name); + } + + const replaceBreadcrumbCourseName = function () { + const breadcrumb = document.querySelector('#page-header nav ol.breadcrumb'); + if (!breadcrumb) return; + + const links = Array.from( + breadcrumb.querySelectorAll('li a'), + ) as HTMLAnchorElement[]; + for (const link of links) { + const content = link.textContent?.trim(); + if (!content) continue; + + link.textContent = replacementMap.get(content) ?? content; + } + }; + replaceBreadcrumbCourseName(); + + const observer = new MutationObserver( + debounceCallback(replaceBreadcrumbCourseName, 500), + ); + observer.observe(document.body, { childList: true }); +}; + +main(); diff --git a/src/manifest.jsonc b/src/manifest.jsonc index bb01554..834a999 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -20,7 +20,8 @@ "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], "js": [ "./contentScripts/allPages/startMessage/index.ts", - "./contentScripts/allPages/removeForceDownload/index.ts" + "./contentScripts/allPages/removeForceDownload/index.ts", + "./contentScripts/allPages/replaceBreadcrumbCourseName/index.ts" ] }, // dashboard From c019738b2160baa636ee45df3658938932f87e38 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 13:40:05 +0900 Subject: [PATCH 25/53] feat: add function to replace course name in side navigation --- .../replaceNavigationCourseName/index.ts | 45 +++++++++++++++++++ src/manifest.jsonc | 3 +- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/contentScripts/allPages/replaceNavigationCourseName/index.ts diff --git a/src/contentScripts/allPages/replaceNavigationCourseName/index.ts b/src/contentScripts/allPages/replaceNavigationCourseName/index.ts new file mode 100644 index 0000000..3bb7d0e --- /dev/null +++ b/src/contentScripts/allPages/replaceNavigationCourseName/index.ts @@ -0,0 +1,45 @@ +import { isDebug } from 'esbuild-plugin-debug-switch'; + +import { debounceCallback } from '~/common/debounceCallback.ts'; +import { getPreferences } from '~/common/newStorage/preferences/index.ts'; +import { getCourses } from '~/common/newStorage/courses/index.ts'; + +const main = async function () { + const preferences = await getPreferences(); + if (!preferences.replaceNavigationCourseName.enabled) return; + + if (isDebug) { + console.log('ReplaceNavigationCourseName is enabled.'); + } + + const courses = await getCourses(); + const replacementMap = new Map(); + for (const course of courses) { + if (!course.systemCourseName) continue; + replacementMap.set(course.systemCourseName, course.name); + } + + const replaceNavigationCourseName = function () { + const navigation = document.querySelector('*[role="navigation"]'); + if (!navigation) return; + + const links = Array.from( + navigation.querySelectorAll('*[role="treeitem"] a'), + ) as HTMLAnchorElement[]; + + for (const link of links) { + const content = link.textContent?.trim(); + if (!content) continue; + + link.textContent = replacementMap.get(content) ?? content; + } + }; + replaceNavigationCourseName(); + + const observer = new MutationObserver( + debounceCallback(replaceNavigationCourseName, 500), + ); + observer.observe(document.body, { childList: true }); +}; + +main(); diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 834a999..539be89 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -21,7 +21,8 @@ "js": [ "./contentScripts/allPages/startMessage/index.ts", "./contentScripts/allPages/removeForceDownload/index.ts", - "./contentScripts/allPages/replaceBreadcrumbCourseName/index.ts" + "./contentScripts/allPages/replaceBreadcrumbCourseName/index.ts", + "./contentScripts/allPages/replaceNavigationCourseName/index.ts" ] }, // dashboard From 2d2d7c99bb212eb1747b0738ae46967cc19ec51a Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 13:41:20 +0900 Subject: [PATCH 26/53] chore: remove unused files --- src/contentScripts/allPages/allPages.ts | 19 -------- .../allPages/replaceHeaderCourseName.ts | 46 ------------------ .../allPages/replaceNavigationText.ts | 48 ------------------- 3 files changed, 113 deletions(-) delete mode 100644 src/contentScripts/allPages/allPages.ts delete mode 100644 src/contentScripts/allPages/replaceHeaderCourseName.ts delete mode 100644 src/contentScripts/allPages/replaceNavigationText.ts diff --git a/src/contentScripts/allPages/allPages.ts b/src/contentScripts/allPages/allPages.ts deleted file mode 100644 index d025fec..0000000 --- a/src/contentScripts/allPages/allPages.ts +++ /dev/null @@ -1,19 +0,0 @@ -import debugMode from '../common/debugMode.ts'; -import loadFeature from '../common/loadFeature.ts'; -import removeForceDownload from './removeForceDownload.ts'; -import replaceNavigationText from './replaceNavigationText.ts'; -import replaceHeaderCourseName from './replaceHeaderCourseName.ts'; - -globalThis.addEventListener('load', () => { - console.log('Extension loaded.'); - - loadFeature( - [ - removeForceDownload, - replaceNavigationText, - replaceHeaderCourseName, - ], - new URL(location.href), - debugMode, - ); -}); diff --git a/src/contentScripts/allPages/replaceHeaderCourseName.ts b/src/contentScripts/allPages/replaceHeaderCourseName.ts deleted file mode 100644 index dd39a8c..0000000 --- a/src/contentScripts/allPages/replaceHeaderCourseName.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Feature } from '../common/types.ts'; -import { getCourses } from '../../common/storage/course.ts'; - -/** ヘッダーのコース表示名をわかりやすい表示に変更する */ -const replaceHeaderCourseName: Feature = { - uniqueName: 'all-pages-replace-header-course-name', - hostnameFilter: 'cms7.ict.nitech.ac.jp', - pathnameFilter: /^\/moodle40a\//, - loader: async (options) => { - if (!options.enabled) { - return; - } - - const elHeader = document.getElementById('page-header'); - if (!elHeader) { - return; - } - - const courses = await getCourses(); - const courseNameMap = new Map(); - for (const course of courses) { - if (course.type === 'regular-lecture') { - courseNameMap.set( - course.shortName, - `${course.name} ${course.shortName}`, - ); - } else { - courseNameMap.set(course.shortName, course.fullName); - } - } - - const elBreadcrumbLinks = elHeader.querySelectorAll( - 'nav li a', - ); - elBreadcrumbLinks.forEach((elLink) => { - const shortName = elLink.textContent ?? ''; - const courseName = courseNameMap.get(shortName); - - if (typeof courseName === 'string') { - elLink.textContent = courseName; - } - }); - }, -}; - -export default replaceHeaderCourseName; diff --git a/src/contentScripts/allPages/replaceNavigationText.ts b/src/contentScripts/allPages/replaceNavigationText.ts deleted file mode 100644 index 8eabd01..0000000 --- a/src/contentScripts/allPages/replaceNavigationText.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Feature } from '../common/types.ts'; -import { getCourses } from '../../common/storage/course.ts'; - -/** ナビゲーションのコース表示名をわかりやすい表示に変更する */ -const replaceNavigationText: Feature = { - uniqueName: 'all-pages-replace-navigation-texts', - hostnameFilter: 'cms7.ict.nitech.ac.jp', - pathnameFilter: /^\/moodle40a\//, - loader: async (options) => { - if (!options.enabled) { - return; - } - - const elNavigation = document.querySelector('section.block_navigation'); - if (!elNavigation) { - return; - } - - const courses = await getCourses(); - const courseNameMap = new Map(); - for (const course of courses) { - courseNameMap.set(course.shortName, course.name); - } - - const elMyCourse = document.evaluate( - `.//a[contains(text(), "マイコース")]/../../ul`, - elNavigation, - null, - XPathResult.UNORDERED_NODE_ITERATOR_TYPE, - null, - ).iterateNext() as HTMLElement | null; - if (!elMyCourse) { - return; - } - - const elMyCourseItems = elMyCourse.querySelectorAll('li a'); - elMyCourseItems.forEach((elItem) => { - const shortName = elItem.textContent?.trim() ?? ''; - const courseName = courseNameMap.get(shortName); - - if (typeof courseName === 'string') { - elItem.textContent = courseName; - } - }); - }, -}; - -export default replaceNavigationText; From 518aa2e1d5c600d110b2359647dd99f00b3ab41c Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 14:06:04 +0900 Subject: [PATCH 27/53] refactor: replace MutationObserver pattern to helper --- src/common/debounceCallback.ts | 2 +- .../allPages/removeForceDownload/index.ts | 41 +++++----- .../replaceBreadcrumbCourseName/index.ts | 47 ++++++----- .../replaceNavigationCourseName/index.ts | 49 +++++++----- .../common/mutationObserverCallback.ts | 19 +++++ .../dashboard/readAndStoreCourses/index.ts | 77 ++++++++++--------- 6 files changed, 136 insertions(+), 99 deletions(-) create mode 100644 src/contentScripts/common/mutationObserverCallback.ts diff --git a/src/common/debounceCallback.ts b/src/common/debounceCallback.ts index ad4d700..986c2fa 100644 --- a/src/common/debounceCallback.ts +++ b/src/common/debounceCallback.ts @@ -1,4 +1,4 @@ -type Callback = (...args: Args) => void; +export type Callback = (...args: Args) => void; export const debounceCallback = function < Args extends unknown[], diff --git a/src/contentScripts/allPages/removeForceDownload/index.ts b/src/contentScripts/allPages/removeForceDownload/index.ts index ca5f173..d25c8b7 100644 --- a/src/contentScripts/allPages/removeForceDownload/index.ts +++ b/src/contentScripts/allPages/removeForceDownload/index.ts @@ -1,7 +1,7 @@ import { isDebug } from 'esbuild-plugin-debug-switch'; -import { debounceCallback } from '~/common/debounceCallback.ts'; import { getPreferences } from '~/common/newStorage/preferences/index.ts'; +import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; const parseUrl = function (url: string): URL | null { try { @@ -11,6 +11,19 @@ const parseUrl = function (url: string): URL | null { } }; +const removeForceDownload = () => { + if (isDebug) { + console.log('Removing forcedownload'); + } + + document.querySelectorAll('a').forEach((link) => { + const url = parseUrl(link.href); + if (url === null) return; + + link.href = forceDownloadRemoved(url).toString(); + }); +}; + const forceDownloadRemoved = function (url: URL): URL { const removed = new URL(url); removed.searchParams.delete('forcedownload', '1'); @@ -20,30 +33,18 @@ const forceDownloadRemoved = function (url: URL): URL { (async () => { const preferences = await getPreferences(); - if (!preferences.removeForceDownload.enabled) { - return; - } + if (!preferences.removeForceDownload.enabled) return; if (isDebug) { console.log('RemoveForceDownload is enabled.'); } - const removeForceDownload = () => { - if (isDebug) { - console.log('Removing forcedownload'); - } - - document.querySelectorAll('a').forEach((link) => { - const url = parseUrl(link.href); - if (url === null) return; - - link.href = forceDownloadRemoved(url).toString(); - }); - }; removeForceDownload(); - - const observer = new MutationObserver( - debounceCallback(removeForceDownload, 500), + registerMutationObserverCallback( + removeForceDownload, + { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }, ); - observer.observe(document.body, { childList: true }); })(); diff --git a/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts b/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts index bc20c63..8d1f5d8 100644 --- a/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts +++ b/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts @@ -1,8 +1,25 @@ import { isDebug } from 'esbuild-plugin-debug-switch'; -import { debounceCallback } from '~/common/debounceCallback.ts'; import { getPreferences } from '~/common/newStorage/preferences/index.ts'; import { getCourses } from '~/common/newStorage/courses/index.ts'; +import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; + +const replaceBreadcrumbCourseName = function ( + replacementMap: Map, +) { + const breadcrumb = document.querySelector('#page-header nav ol.breadcrumb'); + if (!breadcrumb) return; + + const links = Array.from( + breadcrumb.querySelectorAll('li a'), + ) as HTMLAnchorElement[]; + for (const link of links) { + const content = link.textContent?.trim(); + if (!content) continue; + + link.textContent = replacementMap.get(content) ?? content; + } +}; const main = async function () { const preferences = await getPreferences(); @@ -19,26 +36,16 @@ const main = async function () { replacementMap.set(course.systemCourseName, course.name); } - const replaceBreadcrumbCourseName = function () { - const breadcrumb = document.querySelector('#page-header nav ol.breadcrumb'); - if (!breadcrumb) return; - - const links = Array.from( - breadcrumb.querySelectorAll('li a'), - ) as HTMLAnchorElement[]; - for (const link of links) { - const content = link.textContent?.trim(); - if (!content) continue; - - link.textContent = replacementMap.get(content) ?? content; - } - }; - replaceBreadcrumbCourseName(); - - const observer = new MutationObserver( - debounceCallback(replaceBreadcrumbCourseName, 500), + replaceBreadcrumbCourseName(replacementMap); + registerMutationObserverCallback( + () => { + replaceBreadcrumbCourseName(replacementMap); + }, + { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }, ); - observer.observe(document.body, { childList: true }); }; main(); diff --git a/src/contentScripts/allPages/replaceNavigationCourseName/index.ts b/src/contentScripts/allPages/replaceNavigationCourseName/index.ts index 3bb7d0e..c631184 100644 --- a/src/contentScripts/allPages/replaceNavigationCourseName/index.ts +++ b/src/contentScripts/allPages/replaceNavigationCourseName/index.ts @@ -1,8 +1,26 @@ import { isDebug } from 'esbuild-plugin-debug-switch'; -import { debounceCallback } from '~/common/debounceCallback.ts'; import { getPreferences } from '~/common/newStorage/preferences/index.ts'; import { getCourses } from '~/common/newStorage/courses/index.ts'; +import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; + +const replaceNavigationCourseName = function ( + replacementMap: Map, +) { + const navigation = document.querySelector('*[role="navigation"]'); + if (!navigation) return; + + const links = Array.from( + navigation.querySelectorAll('*[role="treeitem"] a'), + ) as HTMLAnchorElement[]; + + for (const link of links) { + const content = link.textContent?.trim(); + if (!content) continue; + + link.textContent = replacementMap.get(content) ?? content; + } +}; const main = async function () { const preferences = await getPreferences(); @@ -19,27 +37,16 @@ const main = async function () { replacementMap.set(course.systemCourseName, course.name); } - const replaceNavigationCourseName = function () { - const navigation = document.querySelector('*[role="navigation"]'); - if (!navigation) return; - - const links = Array.from( - navigation.querySelectorAll('*[role="treeitem"] a'), - ) as HTMLAnchorElement[]; - - for (const link of links) { - const content = link.textContent?.trim(); - if (!content) continue; - - link.textContent = replacementMap.get(content) ?? content; - } - }; - replaceNavigationCourseName(); - - const observer = new MutationObserver( - debounceCallback(replaceNavigationCourseName, 500), + replaceNavigationCourseName(replacementMap); + registerMutationObserverCallback( + () => { + replaceNavigationCourseName(replacementMap); + }, + { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }, ); - observer.observe(document.body, { childList: true }); }; main(); diff --git a/src/contentScripts/common/mutationObserverCallback.ts b/src/contentScripts/common/mutationObserverCallback.ts new file mode 100644 index 0000000..c3e8f44 --- /dev/null +++ b/src/contentScripts/common/mutationObserverCallback.ts @@ -0,0 +1,19 @@ +import { debounceCallback } from '~/common/debounceCallback.ts'; + +export type RegisterMutationObserverCallbackOptions = { + rootElement: HTMLElement; + observerOptions: MutationObserverInit; + debounceTimeout?: number; +}; + +export const registerMutationObserverCallback = function ( + callback: MutationCallback, + options: RegisterMutationObserverCallbackOptions, +) { + const debounceTimeout = options?.debounceTimeout ?? 500; + + const observer = new MutationObserver( + debounceCallback(callback, debounceTimeout), + ); + observer.observe(options.rootElement, options.observerOptions); +}; diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts index b46ba85..0c1e0ef 100644 --- a/src/contentScripts/dashboard/readAndStoreCourses/index.ts +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -1,52 +1,55 @@ import { isDebug } from 'esbuild-plugin-debug-switch'; import { Course } from '~/common/model/course.ts'; -import { debounceCallback } from '~/common/debounceCallback.ts'; import { reduceAndSaveCourses } from '~/common/newStorage/courses/index.ts'; +import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; -const main = function () { +const readAndStoreCoureses = function () { if (isDebug) { - console.log('Running ReadAndStoreCoureses'); + console.log('Reading courses'); } - const readAndStoreCoureses = function () { - if (isDebug) { - console.log('Reading courses'); + const myOverview = document.querySelector('*[data-block="myoverview"]'); + if (!myOverview) return; + + const links = Array.from(myOverview.querySelectorAll( + '*[data-region="courses-view"] ul a.coursename', + )) as HTMLAnchorElement[]; + const coursesJson = links.map((link) => { + try { + const id = new URL(link.href).searchParams.get('id'); + if (!id) return null; + + return Course.fromJson({ + id, + ...Course.parse(link.textContent || ''), + }).toJson(); + } catch { + return null; } + }).filter((course) => course !== null); + + reduceAndSaveCourses({ + type: 'mergeAndSaveCourses', + payload: { + courses: coursesJson, + }, + }); +}; - const myOverview = document.querySelector('*[data-block="myoverview"]'); - if (!myOverview) return; - - const links = Array.from(myOverview.querySelectorAll( - '*[data-region="courses-view"] ul a.coursename', - )) as HTMLAnchorElement[]; - const coursesJson = links.map((link) => { - try { - const id = new URL(link.href).searchParams.get('id'); - if (!id) return null; - - return Course.fromJson({ - id, - ...Course.parse(link.textContent || ''), - }).toJson(); - } catch { - return null; - } - }).filter((course) => course !== null); - - reduceAndSaveCourses({ - type: 'mergeAndSaveCourses', - payload: { - courses: coursesJson, - }, - }); - }; - readAndStoreCoureses(); +const main = function () { + if (isDebug) { + console.log('Running ReadAndStoreCoureses'); + } - const observer = new MutationObserver( - debounceCallback(readAndStoreCoureses, 500), + readAndStoreCoureses(); + registerMutationObserverCallback( + readAndStoreCoureses, + { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }, ); - observer.observe(document.body, { subtree: true, childList: true }); }; main(); From f28d13a8c011c3103b4333aed81748d29eae1a51 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 15:47:24 +0900 Subject: [PATCH 28/53] feat: add function to automatically collapse Toc in video page --- src/common/newStorage/preferences/index.ts | 2 +- src/common/newStorage/preferences/reducer.ts | 4 +-- .../scorm/autoCollapseToc/index.ts | 33 +++++++++++++++++++ src/manifest.jsonc | 4 ++- 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/contentScripts/scorm/autoCollapseToc/index.ts diff --git a/src/common/newStorage/preferences/index.ts b/src/common/newStorage/preferences/index.ts index 12d65f4..305c977 100644 --- a/src/common/newStorage/preferences/index.ts +++ b/src/common/newStorage/preferences/index.ts @@ -21,7 +21,7 @@ export const initialPreferences: Preferences = { dashboardQuickCourseLinks: { enabled: true, }, - scormCollapseToc: { + scormAutoCollapseToc: { enabled: true, }, scormAutoPlay: { diff --git a/src/common/newStorage/preferences/reducer.ts b/src/common/newStorage/preferences/reducer.ts index 1c17a21..896e6be 100644 --- a/src/common/newStorage/preferences/reducer.ts +++ b/src/common/newStorage/preferences/reducer.ts @@ -50,8 +50,8 @@ export const preferencesReducer = function ( } else if (action.type === 'patchScormCollapseToc') { return { ...preferences, - scormCollapseToc: { - ...preferences.scormCollapseToc, + scormAutoCollapseToc: { + ...preferences.scormAutoCollapseToc, ...payload, }, }; diff --git a/src/contentScripts/scorm/autoCollapseToc/index.ts b/src/contentScripts/scorm/autoCollapseToc/index.ts new file mode 100644 index 0000000..a7b7d22 --- /dev/null +++ b/src/contentScripts/scorm/autoCollapseToc/index.ts @@ -0,0 +1,33 @@ +import { isDebug } from 'esbuild-plugin-debug-switch'; + +import { getPreferences } from '~/common/newStorage/preferences/index.ts'; +import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; + +const collapseToc = function () { + const toc = document.getElementById('scorm_toc'); + const toggleCollapseButton = document.getElementById('scorm_toc_toggle_btn'); + if (!toc || !toggleCollapseButton) return; + + if (toc.classList.contains('disabled')) return; + + // do not toggle toggle class .disabled directly becausemoodle handles other + // elements at the same time when the button is pressed + toggleCollapseButton.click(); +}; + +const main = async function () { + const preferences = await getPreferences(); + if (!preferences.scormAutoCollapseToc.enabled) return; + + if (isDebug) { + console.log('CollapseToc is enabled.'); + } + + collapseToc(); + registerMutationObserverCallback(collapseToc, { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }); +}; + +main(); diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 539be89..9ea24e4 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -40,7 +40,9 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*" ], - "js": ["./contentScripts/scorm/scorm.ts"] + "js": [ + "./contentScripts/scorm/autoCollapseToc/index.ts" + ] }, { "matches": [ From 65d96ecbc4f82715018adc950309d61e12b92361 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 15:52:25 +0900 Subject: [PATCH 29/53] build: fix watch mode does not start --- build/build.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/build.ts b/build/build.ts index 000c012..e25dae1 100644 --- a/build/build.ts +++ b/build/build.ts @@ -99,6 +99,8 @@ if (!watchMode) { Deno.exit(0); } +await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); + console.log('Watching...'); console.log('press "r" to rebuild'); console.log('press "q" to exit'); From 59db7a240c2e3df312c0dad8dcc9d0e61248d34b Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Mon, 28 Oct 2024 19:33:06 +0900 Subject: [PATCH 30/53] chore: organize files --- src/contentScripts/scorm/collapseToc.ts | 29 ------------------- src/contentScripts/scorm/scorm.ts | 13 --------- .../scorm.scss => scormContents/main.scss} | 0 src/manifest.jsonc | 4 ++- 4 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 src/contentScripts/scorm/collapseToc.ts delete mode 100644 src/contentScripts/scorm/scorm.ts rename src/contentScripts/{scorm/scorm.scss => scormContents/main.scss} (100%) diff --git a/src/contentScripts/scorm/collapseToc.ts b/src/contentScripts/scorm/collapseToc.ts deleted file mode 100644 index 26dc870..0000000 --- a/src/contentScripts/scorm/collapseToc.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Feature } from '../common/types.ts'; - -/** TOCを折りたたむ */ -const collapseToc: Feature = { - uniqueName: 'scorm-collapse-toc', - hostnameFilter: 'cms7.ict.nitech.ac.jp', - pathnameFilter: /^\/moodle40a\/mod\/scorm/, - loader: (options) => { - if (!options.enabled) { - return; - } - - const elScormToc = document.getElementById('scorm_toc'); - console.log('EXT: ', elScormToc); - - if (!elScormToc) { - return; - } - if (elScormToc.classList.contains('disabled')) { - // すでに折りたたまれている - return; - } - - const elTocToggleButton = document.getElementById('scorm_toc_toggle_btn'); - elTocToggleButton?.click(); - }, -}; - -export default collapseToc; diff --git a/src/contentScripts/scorm/scorm.ts b/src/contentScripts/scorm/scorm.ts deleted file mode 100644 index b5de336..0000000 --- a/src/contentScripts/scorm/scorm.ts +++ /dev/null @@ -1,13 +0,0 @@ -import debugMode from '../common/debugMode.ts'; -import loadFeature from '../common/loadFeature.ts'; -import collapseToc from './collapseToc.ts'; - -globalThis.addEventListener('load', () => { - loadFeature( - [ - collapseToc, - ], - new URL(location.href), - debugMode, - ); -}); diff --git a/src/contentScripts/scorm/scorm.scss b/src/contentScripts/scormContents/main.scss similarity index 100% rename from src/contentScripts/scorm/scorm.scss rename to src/contentScripts/scormContents/main.scss diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 9ea24e4..b56ba59 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -49,7 +49,9 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*" ], "all_frames": true, - "css": ["./contentScripts/scorm/scorm.scss"] + "css": [ + "./contentScripts/scormContents/main.scss" + ] } ], From 144a8d9be2a4425f5a5dce3d05b85225e2d2d2f4 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Wed, 30 Oct 2024 21:03:04 +0900 Subject: [PATCH 31/53] refactor: rename dashboard.scss -> main.scss --- .../dashboard/{dashboard.scss => main.scss} | 8 +++----- src/manifest.jsonc | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) rename src/contentScripts/dashboard/{dashboard.scss => main.scss} (67%) diff --git a/src/contentScripts/dashboard/dashboard.scss b/src/contentScripts/dashboard/main.scss similarity index 67% rename from src/contentScripts/dashboard/dashboard.scss rename to src/contentScripts/dashboard/main.scss index 5ad0927..81754ff 100644 --- a/src/contentScripts/dashboard/dashboard.scss +++ b/src/contentScripts/dashboard/main.scss @@ -1,6 +1,4 @@ -@import './quickCourseView.scss'; - -// セクション下の謎の空白を修正 +// delete blanks under sections div .block_myoverview .content { min-height: unset; } @@ -8,7 +6,7 @@ div .block_myoverview .paged-content-page-container { min-height: unset; } -// コース概要のリンク要素の大きさを修正 +// enlarge link element in course overview section.block_myoverview ul li div.row { display: flex; margin: 0; @@ -22,7 +20,7 @@ section.block_myoverview ul li div.row { } } -// リスト末尾の下境界線を消す +// delete border line at the end of list section.block ul li:last-child { border-bottom: none; } diff --git a/src/manifest.jsonc b/src/manifest.jsonc index b56ba59..b18eb43 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -32,7 +32,7 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php" ], "js": ["./contentScripts/dashboard/readAndStoreCourses/index.ts"], - "css": ["./contentScripts/dashboard/dashboard.scss"] + "css": ["./contentScripts/dashboard/main.scss"] }, // scorm page { From e1e6331b29e66ff195cc0f777be3e1018ca64383 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Wed, 30 Oct 2024 21:23:26 +0900 Subject: [PATCH 32/53] style: change single quote to double quote --- .github/workflows/check-version-increase.yml | 2 +- .github/workflows/create-tag-on-pr-merge.yml | 2 +- build/build.ts | 48 ++--- build/manifest.ts | 14 +- build/options/copy.ts | 6 +- build/options/javascript.ts | 12 +- build/options/stylesheet.ts | 10 +- build/plugins/logBuildResult.ts | 6 +- deno.json | 2 +- how_to_build.md | 2 +- readme.dev.md | 41 ++-- readme.md | 99 +++++----- scripts/setup-vscode.ts | 36 ++-- src/common/course.ts | 98 +++++----- src/common/model/course.ts | 56 +++--- src/common/newStorage/courses/actions.ts | 6 +- src/common/newStorage/courses/index.ts | 16 +- src/common/newStorage/courses/reducer.ts | 10 +- src/common/newStorage/preferences/action.ts | 16 +- src/common/newStorage/preferences/index.ts | 14 +- src/common/newStorage/preferences/reducer.ts | 20 +- src/common/newStorage/storage.ts | 10 +- src/common/storage/course.ts | 6 +- src/common/storage/options.ts | 8 +- src/common/storage/storage.ts | 14 +- .../allPages/removeForceDownload/index.ts | 14 +- .../replaceBreadcrumbCourseName/index.ts | 14 +- .../replaceNavigationCourseName/index.ts | 10 +- .../allPages/startMessage/index.ts | 4 +- src/contentScripts/common/loadFeature.ts | 14 +- .../common/mutationObserverCallback.ts | 2 +- src/contentScripts/common/types.ts | 2 +- src/contentScripts/dashboard/dashboard.ts | 14 +- .../dashboard/eventsCountdown.ts | 24 +-- .../eventsCountdown/EventsCountdown.tsx | 10 +- .../dashboard/quickCourseView.scss | 5 - .../dashboard/quickCourseView/CourseItem.tsx | 33 ---- .../dashboard/quickCourseView/ListGroup.tsx | 21 --- .../quickCourseView/QuickCourseView.tsx | 178 ------------------ .../quickCourseView/QuickCourseViewBody.tsx | 39 ---- .../QuickCourseViewControl.tsx | 65 ------- .../dashboard/quickCourseView/defs.ts | 8 - .../dashboard/readAndStoreCourses/index.ts | 18 +- .../dashboard/renderQuickCourseView.ts | 38 ---- .../dashboard/updateCourseRepository.ts | 48 ++--- .../dashboard/waitForPageLoad.ts | 8 +- .../scorm/autoCollapseToc/index.ts | 14 +- src/options/app/App.tsx | 14 +- src/options/app/FeatureOptions.tsx | 16 +- src/options/app/ToggleBox.tsx | 8 +- src/options/options.html | 40 ++-- src/options/options.scss | 12 +- src/options/options.ts | 6 +- 53 files changed, 435 insertions(+), 798 deletions(-) delete mode 100644 src/contentScripts/dashboard/quickCourseView.scss delete mode 100644 src/contentScripts/dashboard/quickCourseView/CourseItem.tsx delete mode 100644 src/contentScripts/dashboard/quickCourseView/ListGroup.tsx delete mode 100644 src/contentScripts/dashboard/quickCourseView/QuickCourseView.tsx delete mode 100644 src/contentScripts/dashboard/quickCourseView/QuickCourseViewBody.tsx delete mode 100644 src/contentScripts/dashboard/quickCourseView/QuickCourseViewControl.tsx delete mode 100644 src/contentScripts/dashboard/quickCourseView/defs.ts delete mode 100644 src/contentScripts/dashboard/renderQuickCourseView.ts diff --git a/.github/workflows/check-version-increase.yml b/.github/workflows/check-version-increase.yml index 7203e1b..fa94670 100644 --- a/.github/workflows/check-version-increase.yml +++ b/.github/workflows/check-version-increase.yml @@ -8,7 +8,7 @@ on: branches: - main paths: - - 'src/**' + - "src/**" jobs: lint-and-fmt: diff --git a/.github/workflows/create-tag-on-pr-merge.yml b/.github/workflows/create-tag-on-pr-merge.yml index 5f8c191..d7b400e 100644 --- a/.github/workflows/create-tag-on-pr-merge.yml +++ b/.github/workflows/create-tag-on-pr-merge.yml @@ -7,7 +7,7 @@ on: types: - closed paths: - - 'src/**' + - "src/**" jobs: create-tag: diff --git a/build/build.ts b/build/build.ts index e25dae1..3f061eb 100644 --- a/build/build.ts +++ b/build/build.ts @@ -1,22 +1,22 @@ -import * as esbuild from 'esbuild'; -import * as fs from '@std/fs'; -import * as JSONC from '@std/jsonc'; -import * as path from '@std/path'; -import { TextLineStream } from '@std/streams'; +import * as esbuild from "esbuild"; +import * as fs from "@std/fs"; +import * as JSONC from "@std/jsonc"; +import * as path from "@std/path"; +import { TextLineStream } from "@std/streams"; -import { Manifest } from './manifest.ts'; -import { buildOptions as jsBuildOptions } from './options/javascript.ts'; -import { buildOptions as cssBuildOptions } from './options/stylesheet.ts'; -import { buildOptions as copyBuildOptions } from './options/copy.ts'; +import { Manifest } from "./manifest.ts"; +import { buildOptions as jsBuildOptions } from "./options/javascript.ts"; +import { buildOptions as cssBuildOptions } from "./options/stylesheet.ts"; +import { buildOptions as copyBuildOptions } from "./options/copy.ts"; -const watchMode = Deno.env.has('WATCH'); -const devMode = watchMode || Deno.env.has('DEV'); +const watchMode = Deno.env.has("WATCH"); +const devMode = watchMode || Deno.env.has("DEV"); -const srcPath = './src'; -const destPath = './dist'; +const srcPath = "./src"; +const destPath = "./dist"; -const denoConfigFilePath = path.resolve('deno.json'); -const manifestFilePath = path.resolve(srcPath, 'manifest.jsonc'); +const denoConfigFilePath = path.resolve("deno.json"); +const manifestFilePath = path.resolve(srcPath, "manifest.jsonc"); const denoConfig = JSONC.parse(await Deno.readTextFile(denoConfigFilePath)); const manifest = Manifest.parse(await Deno.readTextFile(manifestFilePath)); @@ -76,21 +76,21 @@ const buildOptions = [ entryPoints: copyEntryPoints, srcPath, destPath, - loaderExts: ['.html'], + loaderExts: [".html"], }), ]; const buildContexts = await Promise.all( buildOptions.map((buildOptions) => esbuild.context(buildOptions)), ); -console.log('Preparing to build...'); +console.log("Preparing to build..."); await fs.ensureDir(destPath); await Deno.writeTextFile( - path.resolve(destPath, 'manifest.json'), + path.resolve(destPath, "manifest.json"), newManifest.stringify(), ); -console.log('Building...'); +console.log("Building..."); await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); if (!watchMode) { @@ -101,7 +101,7 @@ if (!watchMode) { await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); -console.log('Watching...'); +console.log("Watching..."); console.log('press "r" to rebuild'); console.log('press "q" to exit'); @@ -109,18 +109,18 @@ const encoder = new TextEncoder(); const stdinLines = Deno.stdin.readable .pipeThrough(new TextDecoderStream()) .pipeThrough(new TextLineStream()); -await Deno.stdout.write(encoder.encode('> ')); +await Deno.stdout.write(encoder.encode("> ")); for await (const line_ of stdinLines) { const line = line_.trim(); - if (line === 'r' || line === 'reload') { + if (line === "r" || line === "reload") { await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); - } else if (line === 'q' || line === 'exit') { + } else if (line === "q" || line === "exit") { await Promise.all(buildContexts.map((ctx) => ctx.cancel())); await Promise.all(buildContexts.map((ctx) => ctx.dispose())); Deno.exit(0); } - await Deno.stdout.write(encoder.encode('> ')); + await Deno.stdout.write(encoder.encode("> ")); } diff --git a/build/manifest.ts b/build/manifest.ts index 59a2c1e..1cce7dd 100644 --- a/build/manifest.ts +++ b/build/manifest.ts @@ -1,10 +1,10 @@ -import * as JSONC from '@std/jsonc'; +import * as JSONC from "@std/jsonc"; export type ContentScript = { matches: string[]; js?: string[]; css?: string[]; - run_at: 'document_start' | 'document_end' | 'document_idle'; + run_at: "document_start" | "document_end" | "document_idle"; match_about_blank: boolean; match_origin_as_fallback: boolean; }; @@ -44,24 +44,24 @@ export type ExtendedWebExtensionManifest = WebExtensionManifest & { export class Manifest { private manifest: ExtendedWebExtensionManifest; - get manifest_version(): (typeof this.manifest)['manifest_version'] { + get manifest_version(): (typeof this.manifest)["manifest_version"] { return this.manifest.manifest_version; } - get name(): (typeof this.manifest)['name'] { + get name(): (typeof this.manifest)["name"] { return this.manifest.name; } - get version(): (typeof this.manifest)['version'] { + get version(): (typeof this.manifest)["version"] { return this.manifest.version; } - private constructor(manifest: Manifest['manifest']) { + private constructor(manifest: Manifest["manifest"]) { this.manifest = manifest; } static parse(text: string): Manifest { - return new Manifest(JSONC.parse(text) as Manifest['manifest']); + return new Manifest(JSONC.parse(text) as Manifest["manifest"]); } stringify(): string { diff --git a/build/options/copy.ts b/build/options/copy.ts index 866fcb3..5ab0d50 100644 --- a/build/options/copy.ts +++ b/build/options/copy.ts @@ -1,7 +1,7 @@ -import * as esbuild from 'esbuild'; +import * as esbuild from "esbuild"; type BuildOptionsOptions = { - entryPoints: esbuild.BuildOptions['entryPoints']; + entryPoints: esbuild.BuildOptions["entryPoints"]; srcPath: string; destPath: string; loaderExts: string[]; @@ -13,6 +13,6 @@ export const buildOptions = ( entryPoints: options.entryPoints, outdir: options.destPath, loader: Object.fromEntries( - options.loaderExts.map((ext) => [ext, 'copy' as const]), + options.loaderExts.map((ext) => [ext, "copy" as const]), ), }); diff --git a/build/options/javascript.ts b/build/options/javascript.ts index 1282a65..157e64d 100644 --- a/build/options/javascript.ts +++ b/build/options/javascript.ts @@ -1,9 +1,9 @@ -import * as esbuild from 'esbuild'; -import { denoPlugins } from 'esbuild-deno-loader'; -import { debugSwitchPlugin } from 'esbuild-plugin-debug-switch/plugin'; +import * as esbuild from "esbuild"; +import { denoPlugins } from "esbuild-deno-loader"; +import { debugSwitchPlugin } from "esbuild-plugin-debug-switch/plugin"; type BuildOptionsOptions = { - entryPoints: esbuild.BuildOptions['entryPoints']; + entryPoints: esbuild.BuildOptions["entryPoints"]; srcPath: string; destPath: string; dev: boolean; @@ -18,9 +18,9 @@ export const buildOptions = ( ): esbuild.BuildOptions => ({ entryPoints: options.entryPoints, outdir: options.destPath, - platform: 'browser', + platform: "browser", bundle: true, - sourcemap: options.dev ? 'linked' : false, + sourcemap: options.dev ? "linked" : false, minify: !options.dev, jsxFactory: options.jsxFactory, jsxFragment: options.jsxFragmentFactory, diff --git a/build/options/stylesheet.ts b/build/options/stylesheet.ts index a13f6ca..f3734d3 100644 --- a/build/options/stylesheet.ts +++ b/build/options/stylesheet.ts @@ -1,8 +1,8 @@ -import * as esbuild from 'esbuild'; -import { sassPlugin } from 'esbuild-plugin-sass'; +import * as esbuild from "esbuild"; +import { sassPlugin } from "esbuild-plugin-sass"; type BuildOptionsOptions = { - entryPoints: esbuild.BuildOptions['entryPoints']; + entryPoints: esbuild.BuildOptions["entryPoints"]; srcPath: string; destPath: string; dev: boolean; @@ -13,9 +13,9 @@ export const buildOptions = ( ): esbuild.BuildOptions => ({ entryPoints: options.entryPoints, outdir: options.destPath, - platform: 'browser', + platform: "browser", bundle: true, - sourcemap: options.dev ? 'linked' : false, + sourcemap: options.dev ? "linked" : false, minify: !options.dev, plugins: [ sassPlugin(), diff --git a/build/plugins/logBuildResult.ts b/build/plugins/logBuildResult.ts index 6d971e7..b0ca0d4 100644 --- a/build/plugins/logBuildResult.ts +++ b/build/plugins/logBuildResult.ts @@ -1,4 +1,4 @@ -import type * as esbuild from 'esbuild'; +import type * as esbuild from "esbuild"; const printLog = function ( numErrors: number, @@ -8,7 +8,7 @@ const printLog = function ( ) { const buildTime = endTime.getTime() - startTime.getTime(); - let message = `${endTime.toLocaleTimeString('en-GB')}: `; + let message = `${endTime.toLocaleTimeString("en-GB")}: `; if (numErrors > 0 && numWarnings > 0) { message += `Build failed with ${numErrors} errors and ${numWarnings} warnings`; @@ -24,7 +24,7 @@ const printLog = function ( }; export const logBuildResultPlugin = (): esbuild.Plugin => ({ - name: 'log-and-output-filename', + name: "log-and-output-filename", setup(build) { // assign to avoid type error let startTime = new Date(); diff --git a/deno.json b/deno.json index 24a5ed8..4987ac4 100644 --- a/deno.json +++ b/deno.json @@ -24,7 +24,7 @@ "indentWidth": 2, "lineWidth": 80, "proseWrap": "always", - "singleQuote": true, + "singleQuote": false, "useTabs": false }, "tasks": { diff --git a/how_to_build.md b/how_to_build.md index 4c0c05f..a8c24a5 100644 --- a/how_to_build.md +++ b/how_to_build.md @@ -16,4 +16,4 @@ $deno task build ``` - `dist` ディレクトリにビルド結果が出力されます + `dist` ディレクトリにビルド結果が出力されます diff --git a/readme.dev.md b/readme.dev.md index 47bb6ab..3bc9a54 100644 --- a/readme.dev.md +++ b/readme.dev.md @@ -7,25 +7,31 @@ - すべての利用者が一斉にリクエストを行うような機能を避ける - ... - すでに存在する DOM を削除して挿入しない - - Moodle の機能によって追加されているイベントリスナの動作が怪しくなる可能性がある + - Moodle + の機能によって追加されているイベントリスナの動作が怪しくなる可能性がある - Moodle の DOM 構造、スタイルを再利用する - - 例えばダッシュボードにブロックを追加するならすでに存在するブロックと同様の構造・クラス・属性の DOM を挿入する + - 例えばダッシュボードにブロックを追加するならすでに存在するブロックと同様の構造・クラス・属性の + DOM を挿入する - ドロップダウンリストなどロジックを自前で実装しなくて済むことがあります - デザインの統一は重要です ## ワークフローについて -`main` ブランチから派生したブランチで作業し、 `main` ブランチに Pull request を作成するという前提で作成しています。 +`main` ブランチから派生したブランチで作業し、 `main` ブランチに Pull request +を作成するという前提で作成しています。 - attach build artifact to release (`release-attach-artifact.yml`) - Release 時にビルドを実行し、ビルド成果物を Assets に追加する -- attach build artifact to release manually (`release-attach-artifact-manual.yml`) +- attach build artifact to release manually + (`release-attach-artifact-manual.yml`) - 上記と同様のワークフローを手動で実行できるようにしたもの - check latest version (`check-version-increase.yml`) - `main` ブランチへの PR を作成した際にバージョンが増えていることを確認する - - バージョンは [Semantic Versioning 2.0](https://semver.org/lang/ja/) に準拠しています + - バージョンは [Semantic Versioning 2.0](https://semver.org/lang/ja/) + に準拠しています - create- tag when PR is merged (`create-tag-on-pr-merge.yml`) - - `main` ブランチへの PR がマージされたときに `manifest.json5` に書かれたバージョンのタグを作成する + - `main` ブランチへの PR がマージされたときに `manifest.json5` + に書かれたバージョンのタグを作成する - lint, check format and run build as test (`lint-fmt-test.yml`) - push の際に linter, formatter によるチェックとビルドが成功するかを確認する @@ -45,11 +51,14 @@ ### VSCode での開発環境のセットアップ 1. [ビルド方法](how_to_build.md) に従って環境を構築する -2. [Deno 用拡張機能](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno) をインストールする +2. [Deno 用拡張機能](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno) + をインストールする 3. 次のコマンドを実行する: - ```sh - $deno task setup-vscode - ``` + +```sh +$deno task setup-vscode +``` + 4. VSCodeをリロードまたは再起動する ### ビルドなど @@ -66,13 +75,15 @@ $deno task build ``` -- 開発用ビルド (import map が埋め込まれます; `web_accessible_resources` に指定すれば本番用とほぼ変わりません) +- 開発用ビルド (import map が埋め込まれます; `web_accessible_resources` + に指定すれば本番用とほぼ変わりません) ```sh $deno task dev ``` -- 開発用ビルド (watch; ファイル変更時に自動で再ビルドします; 一部プラグインによる出力は watch されません; Enter で手動再ビルド) +- 開発用ビルド (watch; ファイル変更時に自動で再ビルドします; + 一部プラグインによる出力は watch されません; Enter で手動再ビルド) ```sh $deno task watch @@ -92,7 +103,8 @@ ### ビルド設定について -- `manifest.json5` からビルドするファイルの情報を生成しているため、拡張機能で読み込むファイルを指定すれば勝手にビルドしてくれます +- `manifest.json5` + からビルドするファイルの情報を生成しているため、拡張機能で読み込むファイルを指定すれば勝手にビルドしてくれます - 基本的に esbuild の設定はほとんどしなくて良いはずです ## ディレクトリ・ファイル構成 @@ -109,7 +121,8 @@ - `common`: すべてのページで共通のリソース - ... (各ページごとのリソース) - `popup`: ポップアップ (アイコンのクリック) 環境 - - `manifest.json5`: 拡張機能の設定ファイル (`manifest.json` として出力されます) + - `manifest.json5`: 拡張機能の設定ファイル (`manifest.json` + として出力されます) - `manifestTypes.ts`: `manifest.json5` の型定義 - `build.ts`: ビルドスクリプト - `deno.json`: Deno の環境設定ファイル diff --git a/readme.md b/readme.md index 4e939b1..9b0efa0 100644 --- a/readme.md +++ b/readme.md @@ -1,48 +1,51 @@ -# Web Extension for NITech Moodle 4.0 - -[![release](https://img.shields.io/github/v/release/nitech-create/nitech-moodle-extension-40a?include_prereleases)](https://github.com/nitech-create/nitech-moodle-extension-40a/releases/latest) - -開発をお手伝いしてくださる方: [開発者向けドキュメント](./readme.dev.md) - -## 概要 - -名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 - -Web Extension for Moodle 4.0 of NITech. - -### 主な機能 - -- ダッシュボード ( トップページ ) に講義へのショートカットアクセスを追加 - - 今受けている講義だけを曜日・時間でソートして一覧表示 -- 動画を画面内で大きく表示するように変更 -- 時間割表の追加 (予定) -- 課題などのイベント締め切りにカウントダウン表示の追加 -- ナビゲーションのコース表示をわかりやすいように変更 -- ヘッダーのコース表示をわかりやすいように変更 -- 強制ダウンロードリンクの無効化 -- 全体的なスタイルの修正 - -## ブラウザ対応状況 - -| ブラウザ | 対応状況 | -| ------------------------------------- | ------------------------ | -| Chrome (Windows 11, 111.0.5563.147) | 開発中 | -| Microsoft Edge (情報基盤センター推奨) | 開発中 | -| Firefox | 現在非対応(今後対応予定) | - -## 利用方法 - -### Chrome Web Store からインストール - -準備中です - -### GitHub からインストール - -1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) から .zip ファイルをダウンロードする - - または [ビルド方法](./how_to_build.md) に従ってビルドする -2. 拡張機能ページを開く - - `chrome://extensions` をURL欄に入力する - - またはEdgeブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック -3. 開発者モードを有効にします -4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip ファイルをドロップ - - または「パッケージ化されていない拡張機能を読み込む」 +# Web Extension for NITech Moodle 4.0 + +[![release](https://img.shields.io/github/v/release/nitech-create/nitech-moodle-extension-40a?include_prereleases)](https://github.com/nitech-create/nitech-moodle-extension-40a/releases/latest) + +開発をお手伝いしてくださる方: [開発者向けドキュメント](./readme.dev.md) + +## 概要 + +名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) +の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 + +Web Extension for Moodle 4.0 of NITech. + +### 主な機能 + +- ダッシュボード ( トップページ ) に講義へのショートカットアクセスを追加 + - 今受けている講義だけを曜日・時間でソートして一覧表示 +- 動画を画面内で大きく表示するように変更 +- 時間割表の追加 (予定) +- 課題などのイベント締め切りにカウントダウン表示の追加 +- ナビゲーションのコース表示をわかりやすいように変更 +- ヘッダーのコース表示をわかりやすいように変更 +- 強制ダウンロードリンクの無効化 +- 全体的なスタイルの修正 + +## ブラウザ対応状況 + +| ブラウザ | 対応状況 | +| ------------------------------------- | ------------------------ | +| Chrome (Windows 11, 111.0.5563.147) | 開発中 | +| Microsoft Edge (情報基盤センター推奨) | 開発中 | +| Firefox | 現在非対応(今後対応予定) | + +## 利用方法 + +### Chrome Web Store からインストール + +準備中です + +### GitHub からインストール + +1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) + から .zip ファイルをダウンロードする + - または [ビルド方法](./how_to_build.md) に従ってビルドする +2. 拡張機能ページを開く + - `chrome://extensions` をURL欄に入力する + - またはEdgeブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック +3. 開発者モードを有効にします +4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip + ファイルをドロップ + - または「パッケージ化されていない拡張機能を読み込む」 diff --git a/scripts/setup-vscode.ts b/scripts/setup-vscode.ts index 418a164..a372438 100644 --- a/scripts/setup-vscode.ts +++ b/scripts/setup-vscode.ts @@ -1,4 +1,4 @@ -const configFilePath = './.vscode/settings.json'; +const configFilePath = "./.vscode/settings.json"; const configJSON = await (async () => { try { const stat = await Deno.lstat(configFilePath); @@ -9,34 +9,36 @@ const configJSON = await (async () => { return Promise.reject(`${configFilePath} exists but is NOT a file`); } catch { - return '{}'; + return "{}"; } })(); -console.info("\x1b[1mInstall Deno for VSCode on https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno\x1b[0m"); +console.info( + "\x1b[1mInstall Deno for VSCode on https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno\x1b[0m", +); const config = JSON.parse(configJSON); const langSettings = { - 'editor.defaultFormatter': 'denoland.vscode-deno', - 'editor.tabSize': 2, - 'editor.insertSpaces': true, + "editor.defaultFormatter": "denoland.vscode-deno", + "editor.tabSize": 2, + "editor.insertSpaces": true, }; -config['deno.enable'] = true; -config['deno.unstable'] = true; -config['deno.lint'] = true; -config['deno.config'] = './deno.json'; -config['deno.importMap'] = './import_map.json'; -config['[javascript]'] = langSettings; -config['[javascriptreact]'] = langSettings; -config['[typescript]'] = langSettings; -config['[typescriptreact]'] = langSettings; -config['[json]'] = langSettings; +config["deno.enable"] = true; +config["deno.unstable"] = true; +config["deno.lint"] = true; +config["deno.config"] = "./deno.json"; +config["deno.importMap"] = "./import_map.json"; +config["[javascript]"] = langSettings; +config["[javascriptreact]"] = langSettings; +config["[typescript]"] = langSettings; +config["[typescriptreact]"] = langSettings; +config["[json]"] = langSettings; await Deno.writeTextFile(configFilePath, JSON.stringify(config)); const p = Deno.run({ - cmd: ['deno', 'fmt', configFilePath], + cmd: ["deno", "fmt", configFilePath], }); const status = await p.status(); diff --git a/src/common/course.ts b/src/common/course.ts index 5b2bb45..3b31221 100644 --- a/src/common/course.ts +++ b/src/common/course.ts @@ -1,5 +1,5 @@ interface RegularLectureCourse { - type: 'regular-lecture'; + type: "regular-lecture"; /** 講義名 */ name: string; /** moodle での表示名 */ @@ -11,9 +11,9 @@ interface RegularLectureCourse { /** 講義番号 */ code: number; /** 開講する時期 (前期 / 後期 / 第1クォーター など) */ - semester: '1/2' | '2/2' | '1/1' | '1/4' | '2/4' | '3/4' | '4/4' | 'unfixed'; + semester: "1/2" | "2/2" | "1/1" | "1/4" | "2/4" | "3/4" | "4/4" | "unfixed"; /** 開講する曜日 */ - weekOfDay: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'; + weekOfDay: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; /** 開口する時間 (コマ) [`start`, `end`] */ period: [number, number]; /** moodle ページのID */ @@ -23,7 +23,7 @@ interface RegularLectureCourse { } interface SpecialCourse { - type: 'special'; + type: "special"; /** 講義名 */ name: string; /** moodle での表示名 */ @@ -39,66 +39,66 @@ interface SpecialCourse { type Course = RegularLectureCourse | SpecialCourse; const semesterToTextMap = { - '1/2': '前期', - '2/2': '後期', - '1/1': '通年', - '1/4': '第1クォーター', - '2/4': '第2クォーター', - '3/4': '第3クォーター', - '4/4': '第4クォーター', - 'unfixed': '未定', + "1/2": "前期", + "2/2": "後期", + "1/1": "通年", + "1/4": "第1クォーター", + "2/4": "第2クォーター", + "3/4": "第3クォーター", + "4/4": "第4クォーター", + "unfixed": "未定", } as const; const textToSemesterMap = { - '前期': '1/2', - '後期': '2/2', - '通年': '1/1', - '第1クォーター': '1/4', - '第2クォーター': '2/4', - '第3クォーター': '3/4', - '第4クォーター': '4/4', - '未定': 'unfixed', + "前期": "1/2", + "後期": "2/2", + "通年": "1/1", + "第1クォーター": "1/4", + "第2クォーター": "2/4", + "第3クォーター": "3/4", + "第4クォーター": "4/4", + "未定": "unfixed", } as const; const semesterOrdering = { - '1/2': 1, - '2/2': 2, - '1/1': 3, - '1/4': 4, - '2/4': 5, - '3/4': 6, - '4/4': 7, - 'unfixed': 8, + "1/2": 1, + "2/2": 2, + "1/1": 3, + "1/4": 4, + "2/4": 5, + "3/4": 6, + "4/4": 7, + "unfixed": 8, } as const; const weekOfDayToTextMap = { - 'sun': '日曜', - 'mon': '月曜', - 'tue': '火曜', - 'wed': '水曜', - 'thu': '木曜', - 'fri': '金曜', - 'sat': '土曜', + "sun": "日曜", + "mon": "月曜", + "tue": "火曜", + "wed": "水曜", + "thu": "木曜", + "fri": "金曜", + "sat": "土曜", } as const; const textToWeekOfDayMap = { - '日曜': 'sun', - '月曜': 'mon', - '火曜': 'tue', - '水曜': 'wed', - '木曜': 'thu', - '金曜': 'fri', - '土曜': 'sat', + "日曜": "sun", + "月曜": "mon", + "火曜": "tue", + "水曜": "wed", + "木曜": "thu", + "金曜": "fri", + "土曜": "sat", } as const; const weekOfDayOrdering = { - 'sun': 6, - 'mon': 0, - 'tue': 1, - 'wed': 2, - 'thu': 3, - 'fri': 4, - 'sat': 5, + "sun": 6, + "mon": 0, + "tue": 1, + "wed": 2, + "thu": 3, + "fri": 4, + "sat": 5, } as const; export type { Course, RegularLectureCourse, SpecialCourse }; diff --git a/src/common/model/course.ts b/src/common/model/course.ts index 8b24e3c..d9ddc64 100644 --- a/src/common/model/course.ts +++ b/src/common/model/course.ts @@ -3,7 +3,7 @@ type Period = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; type Semester = 1 | 2 | 3 | 4; -type WeekOfDay = 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'; +type WeekOfDay = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; const identifierLikePattern = /^(?(?\d{4})|\w{2})(?\w)(?\w\d{3})$/; @@ -12,21 +12,21 @@ const periodPattern = /(?([1-9]|1\d|20))-(?([1-9]|1\d|20))限/; const weekOfDayIndex = ( weekOfDay: NonNullable, ): number => { - return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'].indexOf(weekOfDay); + return ["sun", "mon", "tue", "wed", "thu", "fri", "sat"].indexOf(weekOfDay); }; const findSemester = function (text: string): [Semester, Semester] | null { - if (text.includes('前期')) { + if (text.includes("前期")) { return [1, 2]; - } else if (text.includes('後期')) { + } else if (text.includes("後期")) { return [3, 4]; - } else if (text.includes('第1クォーター')) { + } else if (text.includes("第1クォーター")) { return [1, 1]; - } else if (text.includes('第2クォーター')) { + } else if (text.includes("第2クォーター")) { return [2, 2]; - } else if (text.includes('第3クォーター')) { + } else if (text.includes("第3クォーター")) { return [3, 3]; - } else if (text.includes('第4クォーター')) { + } else if (text.includes("第4クォーター")) { return [4, 4]; } @@ -34,20 +34,20 @@ const findSemester = function (text: string): [Semester, Semester] | null { }; const findWeekOfDay = function (text: string): WeekOfDay | null { - if (text.includes('日曜')) { - return 'sun'; - } else if (text.includes('月曜')) { - return 'mon'; - } else if (text.includes('火曜')) { - return 'tue'; - } else if (text.includes('水曜')) { - return 'wed'; - } else if (text.includes('木曜')) { - return 'thu'; - } else if (text.includes('金曜')) { - return 'fri'; - } else if (text.includes('土曜')) { - return 'sat'; + if (text.includes("日曜")) { + return "sun"; + } else if (text.includes("月曜")) { + return "mon"; + } else if (text.includes("火曜")) { + return "tue"; + } else if (text.includes("水曜")) { + return "wed"; + } else if (text.includes("木曜")) { + return "thu"; + } else if (text.includes("金曜")) { + return "fri"; + } else if (text.includes("土曜")) { + return "sat"; } return null; @@ -108,16 +108,16 @@ export class Course { this.period = init.period; } - static parse(text: string): Omit { - const cleanText = text.trim().replace(/\s+/g, ' '); - const segments = cleanText.split(' '); - const normalizedText = cleanText.normalize('NFKC').toLowerCase(); + static parse(text: string): Omit { + const cleanText = text.trim().replace(/\s+/g, " "); + const segments = cleanText.split(" "); + const normalizedText = cleanText.normalize("NFKC").toLowerCase(); const name = (() => { // string "コース名" may be embedded for screen reader - const nameIndex = segments.indexOf('コース名'); + const nameIndex = segments.indexOf("コース名"); if (nameIndex > 0 && nameIndex + 1 < segments.length) { - return segments.slice(nameIndex + 1).join(' '); + return segments.slice(nameIndex + 1).join(" "); } return cleanText; })(); diff --git a/src/common/newStorage/courses/actions.ts b/src/common/newStorage/courses/actions.ts index b38e006..da055c1 100644 --- a/src/common/newStorage/courses/actions.ts +++ b/src/common/newStorage/courses/actions.ts @@ -1,14 +1,14 @@ -import type { CourseJson } from '~/common/model/course.ts'; +import type { CourseJson } from "~/common/model/course.ts"; export type SaveCoursesAction = { - type: 'saveCourses'; + type: "saveCourses"; payload: { courses: CourseJson[]; }; }; export type MergeAndSaveCoursesAction = { - type: 'mergeAndSaveCourses'; + type: "mergeAndSaveCourses"; payload: { courses: CourseJson[]; }; diff --git a/src/common/newStorage/courses/index.ts b/src/common/newStorage/courses/index.ts index 1d1965d..078bfbe 100644 --- a/src/common/newStorage/courses/index.ts +++ b/src/common/newStorage/courses/index.ts @@ -1,10 +1,10 @@ -import * as storage from '../storage.ts'; -import type { CourseJson } from '~/common/model/course.ts'; -import { coursesReducer } from './reducer.ts'; -import type { CoursesAction } from './actions.ts'; -import { Course } from '~/common/model/course.ts'; +import * as storage from "../storage.ts"; +import type { CourseJson } from "~/common/model/course.ts"; +import { coursesReducer } from "./reducer.ts"; +import type { CoursesAction } from "./actions.ts"; +import { Course } from "~/common/model/course.ts"; -const storageArea = 'local'; +const storageArea = "local"; const initialCourses: CourseJson[] = []; let cachedCourses: CourseJson[] | null = null; @@ -14,7 +14,7 @@ export const getCoursesJson = async function (): Promise { return cachedCourses; } - const courses = await storage.get('courses', storageArea); + const courses = await storage.get("courses", storageArea); cachedCourses = courses ?? initialCourses; return cachedCourses; @@ -29,7 +29,7 @@ export const reduceAndSaveCourses = async function ( ): Promise { cachedCourses = coursesReducer(await getCoursesJson(), action); return storage.set( - 'courses', + "courses", cachedCourses, storageArea, ); diff --git a/src/common/newStorage/courses/reducer.ts b/src/common/newStorage/courses/reducer.ts index 818d4d0..663acb0 100644 --- a/src/common/newStorage/courses/reducer.ts +++ b/src/common/newStorage/courses/reducer.ts @@ -1,5 +1,5 @@ -import type { CourseJson } from '~/common/model/course.ts'; -import type { CoursesAction } from './actions.ts'; +import type { CourseJson } from "~/common/model/course.ts"; +import type { CoursesAction } from "./actions.ts"; export const coursesReducer = function ( courses: CourseJson[], @@ -7,10 +7,10 @@ export const coursesReducer = function ( ): CourseJson[] { const { payload } = action; - if (action.type === 'saveCourses') { + if (action.type === "saveCourses") { return payload.courses; - } else if (action.type === 'mergeAndSaveCourses') { - const idCourseMap = new Map(); + } else if (action.type === "mergeAndSaveCourses") { + const idCourseMap = new Map(); for (const course of courses) { idCourseMap.set(course.id, course); diff --git a/src/common/newStorage/preferences/action.ts b/src/common/newStorage/preferences/action.ts index 17a9608..c469b02 100644 --- a/src/common/newStorage/preferences/action.ts +++ b/src/common/newStorage/preferences/action.ts @@ -1,54 +1,54 @@ export type PatchRemoveForceDownloadAction = { - type: 'patchRemoveForceDownload'; + type: "patchRemoveForceDownload"; payload: { enabled?: boolean; }; }; export type PatchReplaceBreadcrumbCourseNameAction = { - type: 'patchReplaceBreadcrumbCourseName'; + type: "patchReplaceBreadcrumbCourseName"; payload: { enabled?: boolean; }; }; export type PatchReplaceNavigationCourseNameAction = { - type: 'patchReplaceNavigationCourseName'; + type: "patchReplaceNavigationCourseName"; payload: { enabled?: boolean; }; }; export type PatchDashboardEventsCountdownAction = { - type: 'patchDashboardEventsCountdown'; + type: "patchDashboardEventsCountdown"; payload: { enabled?: boolean; }; }; export type PatchDashboardQuickCourseLinksAction = { - type: 'patchDashboardQuickCourseLinks'; + type: "patchDashboardQuickCourseLinks"; payload: { enabled?: boolean; }; }; export type PatchScormCollapseTocAction = { - type: 'patchScormCollapseToc'; + type: "patchScormCollapseToc"; payload: { enabled?: boolean; }; }; export type PatchScormAutoPlayAction = { - type: 'patchScormAutoPlay'; + type: "patchScormAutoPlay"; payload: { enabled?: boolean; }; }; export type PatchLoginAutoSubmitAction = { - type: 'patchLoginAutoSubmit'; + type: "patchLoginAutoSubmit"; payload: { enabled?: boolean; }; diff --git a/src/common/newStorage/preferences/index.ts b/src/common/newStorage/preferences/index.ts index 305c977..13a18c0 100644 --- a/src/common/newStorage/preferences/index.ts +++ b/src/common/newStorage/preferences/index.ts @@ -1,9 +1,9 @@ -import * as storage from '../storage.ts'; -import type { Preferences } from '~/common/model/preferences.ts'; -import { preferencesReducer } from './reducer.ts'; -import type { PreferencesAction } from './action.ts'; +import * as storage from "../storage.ts"; +import type { Preferences } from "~/common/model/preferences.ts"; +import { preferencesReducer } from "./reducer.ts"; +import type { PreferencesAction } from "./action.ts"; -const storageArea = 'local'; +const storageArea = "local"; export const initialPreferences: Preferences = { removeForceDownload: { @@ -38,7 +38,7 @@ export const getPreferences = async function (): Promise { return cachedPreferences; } - const preferences = await storage.get('preferences', storageArea); + const preferences = await storage.get("preferences", storageArea); cachedPreferences = preferences ?? initialPreferences; return cachedPreferences; @@ -49,7 +49,7 @@ export const reduceAndSavePreferences = async function ( ): Promise { cachedPreferences = preferencesReducer(await getPreferences(), action); return storage.set( - 'preferences', + "preferences", cachedPreferences, storageArea, ); diff --git a/src/common/newStorage/preferences/reducer.ts b/src/common/newStorage/preferences/reducer.ts index 896e6be..57e7dd7 100644 --- a/src/common/newStorage/preferences/reducer.ts +++ b/src/common/newStorage/preferences/reducer.ts @@ -1,5 +1,5 @@ -import type { Preferences } from '~/common/model/preferences.ts'; -import type { PreferencesAction } from './action.ts'; +import type { Preferences } from "~/common/model/preferences.ts"; +import type { PreferencesAction } from "./action.ts"; export const preferencesReducer = function ( preferences: Preferences, @@ -7,7 +7,7 @@ export const preferencesReducer = function ( ): Preferences { const { payload } = action; - if (action.type === 'patchRemoveForceDownload') { + if (action.type === "patchRemoveForceDownload") { return { ...preferences, removeForceDownload: { @@ -15,7 +15,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchReplaceBreadcrumbCourseName') { + } else if (action.type === "patchReplaceBreadcrumbCourseName") { return { ...preferences, replaceBreadcrumbCourseName: { @@ -23,7 +23,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchReplaceNavigationCourseName') { + } else if (action.type === "patchReplaceNavigationCourseName") { return { ...preferences, replaceNavigationCourseName: { @@ -31,7 +31,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchDashboardEventsCountdown') { + } else if (action.type === "patchDashboardEventsCountdown") { return { ...preferences, dashboardEventsCountdown: { @@ -39,7 +39,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchDashboardQuickCourseLinks') { + } else if (action.type === "patchDashboardQuickCourseLinks") { return { ...preferences, dashboardQuickCourseLinks: { @@ -47,7 +47,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchScormCollapseToc') { + } else if (action.type === "patchScormCollapseToc") { return { ...preferences, scormAutoCollapseToc: { @@ -55,7 +55,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchScormAutoPlay') { + } else if (action.type === "patchScormAutoPlay") { return { ...preferences, scormAutoPlay: { @@ -63,7 +63,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === 'patchLoginAutoSubmit') { + } else if (action.type === "patchLoginAutoSubmit") { return { ...preferences, loginAutoSubmit: { diff --git a/src/common/newStorage/storage.ts b/src/common/newStorage/storage.ts index 5126e07..78e9987 100644 --- a/src/common/newStorage/storage.ts +++ b/src/common/newStorage/storage.ts @@ -1,8 +1,8 @@ // @deno-types="@types/webextension-polyfill" -import browser from 'webextension-polyfill'; +import browser from "webextension-polyfill"; -import type { CourseJson } from '~/common/model/course.ts'; -import type { Preferences } from '~/common/model/preferences.ts'; +import type { CourseJson } from "~/common/model/course.ts"; +import type { Preferences } from "~/common/model/preferences.ts"; type Subtract = T extends U ? never : T; @@ -11,9 +11,9 @@ type StorageSchema = { preferences: Preferences; version: number; }; -type StorageKey = Subtract; +type StorageKey = Subtract; -type StorageAreaName = 'local' | 'sync' | 'managed' | 'session'; +type StorageAreaName = "local" | "sync" | "managed" | "session"; const storageGet = async function ( key: K, diff --git a/src/common/storage/course.ts b/src/common/storage/course.ts index ef66350..2379b18 100644 --- a/src/common/storage/course.ts +++ b/src/common/storage/course.ts @@ -1,7 +1,7 @@ -import * as storage from './storage.ts'; -import { Course } from '../course.ts'; +import * as storage from "./storage.ts"; +import { Course } from "../course.ts"; -const storageCourseKey = 'courses'; +const storageCourseKey = "courses"; const mergeCourseList = function (value: Course[], source: Course[]) { const map = new Map(); diff --git a/src/common/storage/options.ts b/src/common/storage/options.ts index 9375867..c00a17b 100644 --- a/src/common/storage/options.ts +++ b/src/common/storage/options.ts @@ -1,9 +1,9 @@ // @deno-types=npm:@types/lodash -import * as lodash from 'lodash'; -import * as storage from './storage.ts'; -import { Options } from '../options.ts'; +import * as lodash from "lodash"; +import * as storage from "./storage.ts"; +import { Options } from "../options.ts"; -const storageOptionsKey = 'options'; +const storageOptionsKey = "options"; const getOptions = async function () { return await storage.get(storageOptionsKey); diff --git a/src/common/storage/storage.ts b/src/common/storage/storage.ts index af8ce93..0412914 100644 --- a/src/common/storage/storage.ts +++ b/src/common/storage/storage.ts @@ -1,8 +1,8 @@ -import browser from 'webextension-polyfill'; +import browser from "webextension-polyfill"; // @deno-types=npm:@types/lodash -import * as lodash from 'lodash'; -import type { Course } from '../course.ts'; -import { Options } from '../options.ts'; +import * as lodash from "lodash"; +import type { Course } from "../course.ts"; +import { Options } from "../options.ts"; /** `storage["local" | "managed" | "sync"]` に保存されている値の型 */ interface StoredValue { @@ -10,7 +10,7 @@ interface StoredValue { options: Options; } -type StorageArea = 'local' | 'managed' | 'sync'; +type StorageArea = "local" | "managed" | "sync"; const defaultValue: StoredValue = { courses: [], @@ -22,7 +22,7 @@ const defaultValue: StoredValue = { /** ストレージから値を取得 */ const get = async function ( key: Key, - storageArea: StorageArea = 'local', + storageArea: StorageArea = "local", ): Promise { const value = await browser.storage[storageArea].get(key) as Partial< StoredValue @@ -40,7 +40,7 @@ let lastPromise: Promise = Promise.resolve(); const update = async function ( key: Key, reducer: (prev: StoredValue[Key]) => StoredValue[Key], - storageArea: StorageArea = 'local', + storageArea: StorageArea = "local", ) { const storeValue = () => new Promise((resolve) => { diff --git a/src/contentScripts/allPages/removeForceDownload/index.ts b/src/contentScripts/allPages/removeForceDownload/index.ts index d25c8b7..c721826 100644 --- a/src/contentScripts/allPages/removeForceDownload/index.ts +++ b/src/contentScripts/allPages/removeForceDownload/index.ts @@ -1,7 +1,7 @@ -import { isDebug } from 'esbuild-plugin-debug-switch'; +import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from '~/common/newStorage/preferences/index.ts'; -import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; +import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const parseUrl = function (url: string): URL | null { try { @@ -13,10 +13,10 @@ const parseUrl = function (url: string): URL | null { const removeForceDownload = () => { if (isDebug) { - console.log('Removing forcedownload'); + console.log("Removing forcedownload"); } - document.querySelectorAll('a').forEach((link) => { + document.querySelectorAll("a").forEach((link) => { const url = parseUrl(link.href); if (url === null) return; @@ -26,7 +26,7 @@ const removeForceDownload = () => { const forceDownloadRemoved = function (url: URL): URL { const removed = new URL(url); - removed.searchParams.delete('forcedownload', '1'); + removed.searchParams.delete("forcedownload", "1"); return removed; }; @@ -36,7 +36,7 @@ const forceDownloadRemoved = function (url: URL): URL { if (!preferences.removeForceDownload.enabled) return; if (isDebug) { - console.log('RemoveForceDownload is enabled.'); + console.log("RemoveForceDownload is enabled."); } removeForceDownload(); diff --git a/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts b/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts index 8d1f5d8..077a030 100644 --- a/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts +++ b/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts @@ -1,17 +1,17 @@ -import { isDebug } from 'esbuild-plugin-debug-switch'; +import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from '~/common/newStorage/preferences/index.ts'; -import { getCourses } from '~/common/newStorage/courses/index.ts'; -import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; +import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { getCourses } from "~/common/newStorage/courses/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const replaceBreadcrumbCourseName = function ( replacementMap: Map, ) { - const breadcrumb = document.querySelector('#page-header nav ol.breadcrumb'); + const breadcrumb = document.querySelector("#page-header nav ol.breadcrumb"); if (!breadcrumb) return; const links = Array.from( - breadcrumb.querySelectorAll('li a'), + breadcrumb.querySelectorAll("li a"), ) as HTMLAnchorElement[]; for (const link of links) { const content = link.textContent?.trim(); @@ -26,7 +26,7 @@ const main = async function () { if (!preferences.replaceBreadcrumbCourseName.enabled) return; if (isDebug) { - console.log('ReplaveBreadcrumbCourseName is enabled.'); + console.log("ReplaveBreadcrumbCourseName is enabled."); } const courses = await getCourses(); diff --git a/src/contentScripts/allPages/replaceNavigationCourseName/index.ts b/src/contentScripts/allPages/replaceNavigationCourseName/index.ts index c631184..5654fcc 100644 --- a/src/contentScripts/allPages/replaceNavigationCourseName/index.ts +++ b/src/contentScripts/allPages/replaceNavigationCourseName/index.ts @@ -1,8 +1,8 @@ -import { isDebug } from 'esbuild-plugin-debug-switch'; +import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from '~/common/newStorage/preferences/index.ts'; -import { getCourses } from '~/common/newStorage/courses/index.ts'; -import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; +import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { getCourses } from "~/common/newStorage/courses/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const replaceNavigationCourseName = function ( replacementMap: Map, @@ -27,7 +27,7 @@ const main = async function () { if (!preferences.replaceNavigationCourseName.enabled) return; if (isDebug) { - console.log('ReplaceNavigationCourseName is enabled.'); + console.log("ReplaceNavigationCourseName is enabled."); } const courses = await getCourses(); diff --git a/src/contentScripts/allPages/startMessage/index.ts b/src/contentScripts/allPages/startMessage/index.ts index 8f7141f..daf92c5 100644 --- a/src/contentScripts/allPages/startMessage/index.ts +++ b/src/contentScripts/allPages/startMessage/index.ts @@ -1,8 +1,8 @@ // Show message when the extension is loaded. -import { env, isDebug } from 'esbuild-plugin-debug-switch'; +import { env, isDebug } from "esbuild-plugin-debug-switch"; console.log(`${env.extensionName} ${env.extensionVersion} is loaded.`); if (isDebug) { - console.log('Debug mode is enabled.'); + console.log("Debug mode is enabled."); } diff --git a/src/contentScripts/common/loadFeature.ts b/src/contentScripts/common/loadFeature.ts index 1d7c742..e87e7e9 100644 --- a/src/contentScripts/common/loadFeature.ts +++ b/src/contentScripts/common/loadFeature.ts @@ -1,8 +1,8 @@ // @deno-types=npm:@types/lodash -import * as lodash from 'lodash'; -import type { Feature, FeatureUniqueName } from '../common/types.ts'; -import { defaultFeatureOption } from '../../common/options.ts'; -import { getOptions } from '../../common/storage/options.ts'; +import * as lodash from "lodash"; +import type { Feature, FeatureUniqueName } from "../common/types.ts"; +import { defaultFeatureOption } from "../../common/options.ts"; +import { getOptions } from "../../common/storage/options.ts"; /** feature を依存関係に従ってトポロジカルソートする */ // DFS を用いて探索 @@ -63,7 +63,7 @@ const sortFeatures = function (features: Feature[]) { * `test` が文字列の場合は完全一致, 正規表現の場合は `RegExp.test` の結果 */ const testByStringOrRegExp = function (test: string | RegExp, target: string) { - if (typeof test === 'string') { + if (typeof test === "string") { return test === target; } else if (test instanceof RegExp) { return test.test(target); @@ -88,7 +88,7 @@ const loadFeature = async function ( const rootPromiseEventTarget = new EventTarget(); const rootPromise = new Promise((resolve) => { - rootPromiseEventTarget.addEventListener('start', () => resolve()); + rootPromiseEventTarget.addEventListener("start", () => resolve()); }); for (const feature of sortedFeatures) { @@ -157,7 +157,7 @@ const loadFeature = async function ( ); } - rootPromiseEventTarget.dispatchEvent(new CustomEvent('start')); + rootPromiseEventTarget.dispatchEvent(new CustomEvent("start")); await Promise.all(loaderPromiseMap.values()); }; diff --git a/src/contentScripts/common/mutationObserverCallback.ts b/src/contentScripts/common/mutationObserverCallback.ts index c3e8f44..c34c417 100644 --- a/src/contentScripts/common/mutationObserverCallback.ts +++ b/src/contentScripts/common/mutationObserverCallback.ts @@ -1,4 +1,4 @@ -import { debounceCallback } from '~/common/debounceCallback.ts'; +import { debounceCallback } from "~/common/debounceCallback.ts"; export type RegisterMutationObserverCallbackOptions = { rootElement: HTMLElement; diff --git a/src/contentScripts/common/types.ts b/src/contentScripts/common/types.ts index ac38642..dbe1487 100644 --- a/src/contentScripts/common/types.ts +++ b/src/contentScripts/common/types.ts @@ -1,4 +1,4 @@ -import { FeatureOption } from '../../common/options.ts'; +import { FeatureOption } from "../../common/options.ts"; export type FeatureUniqueName = string; diff --git a/src/contentScripts/dashboard/dashboard.ts b/src/contentScripts/dashboard/dashboard.ts index 01bf196..cca53c3 100644 --- a/src/contentScripts/dashboard/dashboard.ts +++ b/src/contentScripts/dashboard/dashboard.ts @@ -1,11 +1,11 @@ -import debugMode from '../common/debugMode.ts'; -import loadFeature from '../common/loadFeature.ts'; -import waitForPageLoad from './waitForPageLoad.ts'; -import renderQuickCourseView from './renderQuickCourseView.ts'; -import updateCourseRepository from './updateCourseRepository.ts'; -import addEventsCountdown from './eventsCountdown.ts'; +import debugMode from "../common/debugMode.ts"; +import loadFeature from "../common/loadFeature.ts"; +import waitForPageLoad from "./waitForPageLoad.ts"; +import renderQuickCourseView from "./renderQuickCourseView.ts"; +import updateCourseRepository from "./updateCourseRepository.ts"; +import addEventsCountdown from "./eventsCountdown.ts"; -globalThis.addEventListener('load', () => { +globalThis.addEventListener("load", () => { loadFeature( [ waitForPageLoad, diff --git a/src/contentScripts/dashboard/eventsCountdown.ts b/src/contentScripts/dashboard/eventsCountdown.ts index a63b335..0cfa845 100644 --- a/src/contentScripts/dashboard/eventsCountdown.ts +++ b/src/contentScripts/dashboard/eventsCountdown.ts @@ -1,10 +1,10 @@ /** @jsxImportSource preact */ -import type { Feature } from '../common/types.ts'; +import type { Feature } from "../common/types.ts"; import { EventsCountdownProps, renderEventsCountdown, -} from './eventsCountdown/EventsCountdown.tsx'; +} from "./eventsCountdown/EventsCountdown.tsx"; type AddEventCountdownOptions = { enabled: boolean; @@ -14,8 +14,8 @@ const CalendarLinkDateNumRegExp = /\?.*time=(\d+).*$/; /** 直近イベントにカウントダウンを追加 */ const addEventsCountdown: Feature = { - uniqueName: 'dashboard-events-countdown', - hostnameFilter: 'cms7.ict.nitech.ac.jp', + uniqueName: "dashboard-events-countdown", + hostnameFilter: "cms7.ict.nitech.ac.jp", pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, loader: (options) => { if (!options.enabled) { @@ -23,14 +23,14 @@ const addEventsCountdown: Feature = { } const elUpcomingEvents = document.querySelector( - 'section.block_calendar_upcoming', + "section.block_calendar_upcoming", ); if (!elUpcomingEvents) { return; } - const appRoot = document.createElement('div'); - appRoot.id = 'nitech_moodle_ext_events_countdown_root'; + const appRoot = document.createElement("div"); + appRoot.id = "nitech_moodle_ext_events_countdown_root"; elUpcomingEvents.append(appRoot); const elEventItems = Array.from( @@ -40,13 +40,13 @@ const addEventsCountdown: Feature = { ); // 締め切り時間とそれを描画する DOM 要素のリストを作成 - const eventItems: EventsCountdownProps['items'] = []; + const eventItems: EventsCountdownProps["items"] = []; for (const elEventItem of elEventItems) { - const elLabel = elEventItem.querySelector('div.date'); + const elLabel = elEventItem.querySelector("div.date"); if (!elLabel) { continue; } - const elLink = elLabel.querySelector('a'); + const elLink = elLabel.querySelector("a"); if (!elLink) { continue; } @@ -59,8 +59,8 @@ const addEventsCountdown: Feature = { const expireDate = new Date(parseInt(dateNumMatch[1]) * 1000); - const elCountdown = document.createElement('div'); - elCountdown.className = 'nitech-moodle-ext-event-countdown'; + const elCountdown = document.createElement("div"); + elCountdown.className = "nitech-moodle-ext-event-countdown"; elLabel.appendChild(elCountdown); eventItems.push({ diff --git a/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx b/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx index cd89283..9eb4a32 100644 --- a/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx +++ b/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx @@ -1,8 +1,8 @@ /** @jsxImportSource preact */ // @deno-types="preact/types" -import * as preact from 'preact'; -import { createPortal, useState } from 'preact/compat'; +import * as preact from "preact"; +import { createPortal, useState } from "preact/compat"; /** @param duration 期間の長さ (秒) */ const createDurationText = function (duration: number) { @@ -60,11 +60,11 @@ const Countdown = (props: CountdownProps) => { msUntilNextUpdate(Math.abs(duration), currentTime), ); - let className = ''; + let className = ""; if (duration < 0) { - className = 'exceeded text-muted'; + className = "exceeded text-muted"; } else if (duration < 86400) { - className = 'imminent red'; + className = "imminent red"; } return createPortal( diff --git a/src/contentScripts/dashboard/quickCourseView.scss b/src/contentScripts/dashboard/quickCourseView.scss deleted file mode 100644 index beff657..0000000 --- a/src/contentScripts/dashboard/quickCourseView.scss +++ /dev/null @@ -1,5 +0,0 @@ -#moodle_ext_quick_course_view { - li a { - display: block; - } -} \ No newline at end of file diff --git a/src/contentScripts/dashboard/quickCourseView/CourseItem.tsx b/src/contentScripts/dashboard/quickCourseView/CourseItem.tsx deleted file mode 100644 index 63fc623..0000000 --- a/src/contentScripts/dashboard/quickCourseView/CourseItem.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from 'preact'; -import { Course } from '../../../common/course.ts'; -import { weekOfDayMap } from './defs.ts'; - -const CourseItem = ( - props: { course: Course }, -) => { - const course = props.course; - if (course.type === 'regular-lecture') { - return ( - - {weekOfDayMap[course.weekOfDay]} {course.period[0]}-{course.period[1]}限 - {' '} - {course.name} - - ); - } else { - return ( - - {course.name} - - ); - } -}; - -export default CourseItem; diff --git a/src/contentScripts/dashboard/quickCourseView/ListGroup.tsx b/src/contentScripts/dashboard/quickCourseView/ListGroup.tsx deleted file mode 100644 index 79e69a1..0000000 --- a/src/contentScripts/dashboard/quickCourseView/ListGroup.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from 'preact'; - -const ListGroup = ( - props: { items: { key: string; element: preact.ComponentChild }[] }, -) => ( -
    - {props.items.map((item) => ( -
  • - {item.element} -
  • - ))} -
-); - -export default ListGroup; diff --git a/src/contentScripts/dashboard/quickCourseView/QuickCourseView.tsx b/src/contentScripts/dashboard/quickCourseView/QuickCourseView.tsx deleted file mode 100644 index e5de3e3..0000000 --- a/src/contentScripts/dashboard/quickCourseView/QuickCourseView.tsx +++ /dev/null @@ -1,178 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from 'preact'; -import * as hooks from 'preact/hooks'; -import { Course, RegularLectureCourse } from '../../../common/course.ts'; -import QuickCourseViewControl from './QuickCourseViewControl.tsx'; -import QuickCourseViewBody from './QuickCourseViewBody.tsx'; -import { - Filter, - semesterMap, - semesterOrdering, - weekOfDayOrdering, -} from './defs.ts'; - -const filterCourse = function (courses: Course[], filter: string) { - if (filter === 'all') { - return courses; - } - - const [yearStr, semester] = filter.split('-'); - const year = parseInt(yearStr); - const yearFiltered = courses.filter((course) => course.fullYear === year); - - if (typeof semester !== 'string') { - return yearFiltered; - } - - // TODO: 範囲に共通部分がある場合 (前期と通年, 前期とQ1など) もマッチさせる - - return yearFiltered - .filter((course) => - course.type === 'regular-lecture' && course.semester === semester - ); -}; - -const compareCourse = function (course1: Course, course2: Course) { - if ( - course1.type === 'regular-lecture' && course2.type === 'regular-lecture' - ) { - // 通常の講義 - if (course1.fullYear !== course2.fullYear) { - return course1.fullYear - course2.fullYear; - } - if (course1.semester !== course2.semester) { - return semesterOrdering[course1.semester] - - semesterOrdering[course2.semester]; - } - if (course1.weekOfDay !== course2.weekOfDay) { - return weekOfDayOrdering[course1.weekOfDay] - - weekOfDayOrdering[course2.weekOfDay]; - } - const periodDiff = (course1.period[1] - course2.period[1]) * 10 - - (course1.period[0] - course2.period[0]); - if (periodDiff !== 0) { - return periodDiff; - } - - return course1.code - course2.code; - } else if (course1.type === 'special' && course2.type === 'special') { - // 特殊な講義 - if (course1.fullYear !== course2.fullYear) { - return (course1.fullYear ?? 10000) - (course2.fullYear ?? 10000); - } - - return course1.fullName < course2.fullName ? -1 : 1; - } else if (course1.type !== 'regular-lecture') { - // 通常の講義を優先する - return 1; - } else if (course2.type !== 'regular-lecture') { - // 通常の講義を優先する - return -1; - } - - return 0; -}; - -const QuickCourseView = (props: { courses: Course[] }) => { - const courses = props.courses; - const regularCourses = courses - .filter((course) => - course.type === 'regular-lecture' - ) as RegularLectureCourse[]; - - const date = new Date(); - const shiftedDate = new Date( - date.getFullYear(), - date.getMonth() - 3, - date.getDate(), - ); - // 現在の年と前期/後期のフィルターを最初に選択する - const [filter, setFilter] = hooks.useState( - `${shiftedDate.getFullYear()}-${ - shiftedDate.getMonth() < 6 ? '1/2' : '2/2' - }`, - ); - - const filters = [ - { display: 'すべて', value: 'all' }, - ]; - // 年, 学期の組によるフィルター - const semestersMap = new Map< - string, - [RegularLectureCourse['fullYear'], RegularLectureCourse['semester']] - >(); - regularCourses.forEach((course) => { - semestersMap.set(`${course.fullYear}-${course.semester}`, [ - course.fullYear, - course.semester, - ]); - }); - semestersMap.set( - `${shiftedDate.getFullYear()}-${ - shiftedDate.getMonth() < 6 ? '1/2' : '2/2' - }`, - [shiftedDate.getFullYear(), shiftedDate.getMonth() < 6 ? '1/2' : '2/2'], - ); - const semesters = Array.from(semestersMap.values()).sort((a, b) => { - if (a[0] !== b[0]) { - return a[0] - b[0]; - } - return semesterOrdering[a[1]] - semesterOrdering[b[1]]; - }); - // 年だけのフィルター - const years = Array.from( - new Set(regularCourses.map((course) => course.fullYear)), - ).sort(); - - semesters.forEach(([year, semester]) => { - filters.push({ - display: `${year}年 ${semesterMap[semester]}`, - value: `${year}-${semester}`, - }); - }); - years.forEach((year) => { - filters.push({ - display: `${year}年`, - value: `${year}`, - }); - }); - - return ( -
-
コースリンク
-
-
-
- - -
-
-
-
- ); -}; - -const renderQuickCourseView = function ( - courses: Course[], - targetElement: HTMLElement, -) { - preact.render( - , - targetElement, - ); -}; - -export { renderQuickCourseView }; diff --git a/src/contentScripts/dashboard/quickCourseView/QuickCourseViewBody.tsx b/src/contentScripts/dashboard/quickCourseView/QuickCourseViewBody.tsx deleted file mode 100644 index 3160847..0000000 --- a/src/contentScripts/dashboard/quickCourseView/QuickCourseViewBody.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from 'preact'; -import { Course } from '../../../common/course.ts'; -import ListGroup from './ListGroup.tsx'; -import CourseItem from './CourseItem.tsx'; - -const QuickCourseViewBody = (props: { courses: Course[] }) => ( -
-
-
-
-
- ({ - key: course.fullName, - element: , - }))} - /> -
-
-
-
-
-
-); - -export default QuickCourseViewBody; diff --git a/src/contentScripts/dashboard/quickCourseView/QuickCourseViewControl.tsx b/src/contentScripts/dashboard/quickCourseView/QuickCourseViewControl.tsx deleted file mode 100644 index 5fc6bcc..0000000 --- a/src/contentScripts/dashboard/quickCourseView/QuickCourseViewControl.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from 'preact'; -import * as hooks from 'preact/hooks'; -import { Filter } from './defs.ts'; - -const QuickCourseViewControl = ( - props: { - filterDisplay: string; - filterValue: Filter; - setFilter: hooks.StateUpdater; - filterList: { display: string; value: Filter }[]; - }, -) => ( -
-
- - -
-
-); - -export default QuickCourseViewControl; diff --git a/src/contentScripts/dashboard/quickCourseView/defs.ts b/src/contentScripts/dashboard/quickCourseView/defs.ts deleted file mode 100644 index 00d2bba..0000000 --- a/src/contentScripts/dashboard/quickCourseView/defs.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type Filter = string; - -export { - semesterOrdering, - semesterToTextMap as semesterMap, - weekOfDayOrdering, - weekOfDayToTextMap as weekOfDayMap, -} from '../../../common/course.ts'; diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts index 0c1e0ef..b799eb5 100644 --- a/src/contentScripts/dashboard/readAndStoreCourses/index.ts +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -1,12 +1,12 @@ -import { isDebug } from 'esbuild-plugin-debug-switch'; +import { isDebug } from "esbuild-plugin-debug-switch"; -import { Course } from '~/common/model/course.ts'; -import { reduceAndSaveCourses } from '~/common/newStorage/courses/index.ts'; -import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; +import { Course } from "~/common/model/course.ts"; +import { reduceAndSaveCourses } from "~/common/newStorage/courses/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const readAndStoreCoureses = function () { if (isDebug) { - console.log('Reading courses'); + console.log("Reading courses"); } const myOverview = document.querySelector('*[data-block="myoverview"]'); @@ -17,12 +17,12 @@ const readAndStoreCoureses = function () { )) as HTMLAnchorElement[]; const coursesJson = links.map((link) => { try { - const id = new URL(link.href).searchParams.get('id'); + const id = new URL(link.href).searchParams.get("id"); if (!id) return null; return Course.fromJson({ id, - ...Course.parse(link.textContent || ''), + ...Course.parse(link.textContent || ""), }).toJson(); } catch { return null; @@ -30,7 +30,7 @@ const readAndStoreCoureses = function () { }).filter((course) => course !== null); reduceAndSaveCourses({ - type: 'mergeAndSaveCourses', + type: "mergeAndSaveCourses", payload: { courses: coursesJson, }, @@ -39,7 +39,7 @@ const readAndStoreCoureses = function () { const main = function () { if (isDebug) { - console.log('Running ReadAndStoreCoureses'); + console.log("Running ReadAndStoreCoureses"); } readAndStoreCoureses(); diff --git a/src/contentScripts/dashboard/renderQuickCourseView.ts b/src/contentScripts/dashboard/renderQuickCourseView.ts deleted file mode 100644 index 783b131..0000000 --- a/src/contentScripts/dashboard/renderQuickCourseView.ts +++ /dev/null @@ -1,38 +0,0 @@ -import updateCourseRepository from './updateCourseRepository.ts'; -import type { Feature } from '../common/types.ts'; -import { renderQuickCourseView as renderQuickCourseViewElement } from './quickCourseView/QuickCourseView.tsx'; -import { getCourses } from '../../common/storage/course.ts'; - -const renderQuickCourseView: Feature = { - uniqueName: 'dashboard-quick-course-view', - hostnameFilter: 'cms7.ict.nitech.ac.jp', - pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, - dependencies: [updateCourseRepository.uniqueName], - loader: (options) => { - if (!options.enabled) { - return; - } - - return new Promise((resolve, reject) => { - const cardBlock = document.querySelector('aside#block-region-content'); - if (cardBlock === null) { - reject( - `[${renderQuickCourseView.uniqueName}]: Cannot find element to render quick course view in.`, - ); - return; - } - - const wrapperSection = document.createElement('section'); - wrapperSection.className = 'block_quickcourseview block card mb-3'; - wrapperSection.dataset['block'] = 'quickcourseview'; - cardBlock.insertBefore(wrapperSection, cardBlock.childNodes?.[0] ?? null); - - getCourses().then((courses) => { - renderQuickCourseViewElement(courses, wrapperSection); - resolve(); - }); - }); - }, -}; - -export default renderQuickCourseView; diff --git a/src/contentScripts/dashboard/updateCourseRepository.ts b/src/contentScripts/dashboard/updateCourseRepository.ts index 9a1236a..73429ce 100644 --- a/src/contentScripts/dashboard/updateCourseRepository.ts +++ b/src/contentScripts/dashboard/updateCourseRepository.ts @@ -1,22 +1,22 @@ -import { Course, RegularLectureCourse } from '../../common/course.ts'; -import { storeCourseByMerge } from '../../common/storage/course.ts'; -import type { Feature } from '../common/types.ts'; -import waitForPageLoad from './waitForPageLoad.ts'; -import { textToSemesterMap, textToWeekOfDayMap } from '../../common/course.ts'; +import { Course, RegularLectureCourse } from "../../common/course.ts"; +import { storeCourseByMerge } from "../../common/storage/course.ts"; +import type { Feature } from "../common/types.ts"; +import waitForPageLoad from "./waitForPageLoad.ts"; +import { textToSemesterMap, textToWeekOfDayMap } from "../../common/course.ts"; const regularCourseRegExp = /^(.+)\s*(\d{4})(\d)(\d{4})\s*(前期|後期|第(?:1|2|3|4)クォーター)\s*((?:日|月|火|水|木|金|土)曜)\s*(\d\d?)-(\d\d?)限.*$/; interface DecodedLectureCourse { - type: RegularLectureCourse['type']; - name: RegularLectureCourse['name']; - fullName: RegularLectureCourse['fullName']; - fullYear: RegularLectureCourse['fullYear']; - curriculumPart: RegularLectureCourse['curriculumPart']; - code: RegularLectureCourse['code']; - semester: RegularLectureCourse['semester']; - weekOfDay: RegularLectureCourse['weekOfDay']; - period: RegularLectureCourse['period']; + type: RegularLectureCourse["type"]; + name: RegularLectureCourse["name"]; + fullName: RegularLectureCourse["fullName"]; + fullYear: RegularLectureCourse["fullYear"]; + curriculumPart: RegularLectureCourse["curriculumPart"]; + code: RegularLectureCourse["code"]; + semester: RegularLectureCourse["semester"]; + weekOfDay: RegularLectureCourse["weekOfDay"]; + period: RegularLectureCourse["period"]; } const convertFullWidthToHalfWidth = function (input: string): string { @@ -50,7 +50,7 @@ const decodeRegularLectureCourseText = function ( textToWeekOfDayMap[match[6] as keyof typeof textToWeekOfDayMap]; return { - type: 'regular-lecture', + type: "regular-lecture", name: match[1].trim(), fullName: text, fullYear: parseInt(match[2]), @@ -66,8 +66,8 @@ const pageLinkIdRegExp = /id=(\d+)/; /** コースのリストを「コース概要」のセクションから * 読み取り、ストレージに保存する */ const updateCourseRepository: Feature = { - uniqueName: 'dashboard-update-course-repository', - hostnameFilter: 'cms7.ict.nitech.ac.jp', + uniqueName: "dashboard-update-course-repository", + hostnameFilter: "cms7.ict.nitech.ac.jp", pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, dependencies: [waitForPageLoad.uniqueName], loader: async (options) => { @@ -77,7 +77,7 @@ const updateCourseRepository: Feature = { const thisYear = new Date().getFullYear(); const thisYearStr = `${thisYear}`; - const elMyOverview = document.querySelector('section.block_myoverview'); + const elMyOverview = document.querySelector("section.block_myoverview"); if (!elMyOverview) { throw Error( `[${updateCourseRepository.uniqueName}] Failed to get "my overview" section`, @@ -88,25 +88,25 @@ const updateCourseRepository: Feature = { // 実際になっている様子を確認していないのでちゃんと動くかは要検証 const courses: Course[] = []; const elItemLinks = Array.from( - elMyOverview.querySelectorAll('ul.list-group li a.aalink.coursename'), + elMyOverview.querySelectorAll("ul.list-group li a.aalink.coursename"), ) as HTMLAnchorElement[]; for (const elItemLink of elItemLinks) { const search = new URL(elItemLink.href).search; const pageIdMatch = pageLinkIdRegExp.exec(search); - const pageId = parseInt(pageIdMatch?.[1] ?? '0'); + const pageId = parseInt(pageIdMatch?.[1] ?? "0"); const elShortName = elItemLink.previousElementSibling?.querySelector( - 'div > div', + "div > div", ); if (!elShortName) { continue; } - const shortName = elShortName.textContent ?? ''; + const shortName = elShortName.textContent ?? ""; const text = Array.from(elItemLink.childNodes) .filter((v) => v.nodeType === 3) .map((v) => v.textContent) - .join('') + .join("") .trim(); try { courses.push({ @@ -116,7 +116,7 @@ const updateCourseRepository: Feature = { }); } catch { courses.push({ - type: 'special', + type: "special", name: text, fullName: text, fullYear: text.includes(thisYearStr) ? thisYear : undefined, diff --git a/src/contentScripts/dashboard/waitForPageLoad.ts b/src/contentScripts/dashboard/waitForPageLoad.ts index 48c2f9d..c83d415 100644 --- a/src/contentScripts/dashboard/waitForPageLoad.ts +++ b/src/contentScripts/dashboard/waitForPageLoad.ts @@ -1,6 +1,6 @@ // @deno-types=npm:@types/lodash -import * as lodash from 'lodash'; -import type { Feature } from '../common/types.ts'; +import * as lodash from "lodash"; +import type { Feature } from "../common/types.ts"; const defaultOption = { enabled: true, @@ -9,8 +9,8 @@ const defaultOption = { /** ダッシュボードの内容が読み込まれるまで待つ */ const waitForPageLoad: Feature = { - uniqueName: 'dashboard-wait-for-page-load', - hostnameFilter: 'cms7.ict.nitech.ac.jp', + uniqueName: "dashboard-wait-for-page-load", + hostnameFilter: "cms7.ict.nitech.ac.jp", pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, propagateError: false, loader: (options_) => { diff --git a/src/contentScripts/scorm/autoCollapseToc/index.ts b/src/contentScripts/scorm/autoCollapseToc/index.ts index a7b7d22..4ef76ce 100644 --- a/src/contentScripts/scorm/autoCollapseToc/index.ts +++ b/src/contentScripts/scorm/autoCollapseToc/index.ts @@ -1,14 +1,14 @@ -import { isDebug } from 'esbuild-plugin-debug-switch'; +import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from '~/common/newStorage/preferences/index.ts'; -import { registerMutationObserverCallback } from '~/contentScripts/common/mutationObserverCallback.ts'; +import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const collapseToc = function () { - const toc = document.getElementById('scorm_toc'); - const toggleCollapseButton = document.getElementById('scorm_toc_toggle_btn'); + const toc = document.getElementById("scorm_toc"); + const toggleCollapseButton = document.getElementById("scorm_toc_toggle_btn"); if (!toc || !toggleCollapseButton) return; - if (toc.classList.contains('disabled')) return; + if (toc.classList.contains("disabled")) return; // do not toggle toggle class .disabled directly becausemoodle handles other // elements at the same time when the button is pressed @@ -20,7 +20,7 @@ const main = async function () { if (!preferences.scormAutoCollapseToc.enabled) return; if (isDebug) { - console.log('CollapseToc is enabled.'); + console.log("CollapseToc is enabled."); } collapseToc(); diff --git a/src/options/app/App.tsx b/src/options/app/App.tsx index f990bca..c34c90b 100644 --- a/src/options/app/App.tsx +++ b/src/options/app/App.tsx @@ -1,17 +1,17 @@ /** @jsxImportSource preact */ // @deno-types="preact/types" -import * as preact from 'preact'; -import * as hooks from 'preact/hooks'; +import * as preact from "preact"; +import * as hooks from "preact/hooks"; // @deno-types=npm:@types/lodash -import * as lodash from 'lodash'; -import FeatureOptions from './FeatureOptions.tsx'; +import * as lodash from "lodash"; +import FeatureOptions from "./FeatureOptions.tsx"; import { getOptions, storeOptionsByMerge, -} from '../../common/storage/options.ts'; -import { FeatureOption, Options } from '../../common/options.ts'; +} from "../../common/storage/options.ts"; +import { FeatureOption, Options } from "../../common/options.ts"; interface AppProps { initialOptions: Options; @@ -24,7 +24,7 @@ const App = (props: AppProps) => { getOptions().then(setOptionsRaw); }); const setFeatureOptions = ( - key: keyof Options['features'], + key: keyof Options["features"], value: Partial, ) => { const newPartialOption = lodash.defaultsDeep( diff --git a/src/options/app/FeatureOptions.tsx b/src/options/app/FeatureOptions.tsx index 2b51517..ebcdfe6 100644 --- a/src/options/app/FeatureOptions.tsx +++ b/src/options/app/FeatureOptions.tsx @@ -1,16 +1,16 @@ /** @jsxImportSource preact */ // @deno-types="preact/types" -import * as preact from 'preact'; -import { FeatureOption, Options } from '../../common/options.ts'; -import optionText from './optionText.json' assert { type: 'json' }; +import * as preact from "preact"; +import { FeatureOption, Options } from "../../common/options.ts"; +import optionText from "./optionText.json" assert { type: "json" }; -import ToggleBox from './ToggleBox.tsx'; +import ToggleBox from "./ToggleBox.tsx"; interface FeatureOptionsProps { - options: Options['features']; + options: Options["features"]; setOptions: ( - key: keyof Options['features'], + key: keyof Options["features"], value: Partial, ) => void; } @@ -20,7 +20,7 @@ const FeatureOptions = (props: FeatureOptionsProps) => { .filter(( uniqName, ) => (uniqName in optionText.features)) as (keyof typeof optionText[ - 'features' + "features" ])[]; return ( @@ -31,7 +31,7 @@ const FeatureOptions = (props: FeatureOptionsProps) => { {featureUniqueNames.map((uniqueName) => ( { props.setOptions(uniqueName, { diff --git a/src/options/app/ToggleBox.tsx b/src/options/app/ToggleBox.tsx index cff5532..26716d2 100644 --- a/src/options/app/ToggleBox.tsx +++ b/src/options/app/ToggleBox.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource preact */ // @deno-types="preact/types" -import * as preact from 'preact'; +import * as preact from "preact"; interface ToggleBoxProps { uniqueId: string; @@ -12,14 +12,14 @@ interface ToggleBoxProps { const ToggleBox = (props: ToggleBoxProps) => (
  • -
    +
    -
    +
    - - - - - 設定 - NITech Moodle Extension (40a) - - - - -
    -
    -

    設定

    -
    - NITech Moodle Extension (40a) -
    -
    -
    -
    - - \ No newline at end of file + + + + + 設定 - NITech Moodle Extension (40a) + + + + +
    +
    +

    設定

    +
    + NITech Moodle Extension (40a) +
    +
    +
    +
    + + diff --git a/src/options/options.scss b/src/options/options.scss index 472c51b..fef47d4 100644 --- a/src/options/options.scss +++ b/src/options/options.scss @@ -47,7 +47,7 @@ main { background-color: $c-bg-active; } - +li { + + li { border-top: 1px solid $c-separator; } @@ -93,7 +93,10 @@ main { content: ""; border-radius: 50%; box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); - transform: translate(-$sw-knob-size / 2, ($sw-height - $sw-knob-size) / 2); + transform: translate( + -$sw-knob-size / 2, + ($sw-height - $sw-knob-size) / 2 + ); background-color: $c-toggle-knob-off; transition: all 300ms 0s ease; } @@ -102,7 +105,10 @@ main { background-color: $c-toggle-bg-on; &::after { - transform: translate($sw-width - $sw-knob-size / 2, ($sw-height - $sw-knob-size) / 2); + transform: translate( + $sw-width - $sw-knob-size / 2, + ($sw-height - $sw-knob-size) / 2 + ); background-color: $c-toggle-knob-on; } } diff --git a/src/options/options.ts b/src/options/options.ts index 3392416..5601945 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -1,7 +1,7 @@ -import { renderApp } from './app/App.tsx'; +import { renderApp } from "./app/App.tsx"; -globalThis.addEventListener('load', () => { - const elAppRoot = document.getElementById('app_root'); +globalThis.addEventListener("load", () => { + const elAppRoot = document.getElementById("app_root"); if (!elAppRoot) { throw Error(`element #app_root is not found`); } From 58fe34498816c536b20c476635bbc5a0c9ee8714 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 31 Oct 2024 20:32:12 +0900 Subject: [PATCH 33/53] feat: implement quick course view --- deno.json | 10 +-- deno.lock | 7 +- src/common/model/course.ts | 34 +++----- .../components/filterDropdown.tsx | 61 ++++++++++++++ .../quickCourseLinks/components/liteItem.tsx | 39 +++++++++ .../components/quickCourseLinks.tsx | 82 +++++++++++++++++++ .../dashboard/quickCourseLinks/filter.ts | 79 ++++++++++++++++++ .../quickCourseLinks/hooks/useCourses.ts | 14 ++++ .../quickCourseLinks/hooks/useFilters.ts | 16 ++++ .../dashboard/quickCourseLinks/index.ts | 44 ++++++++++ src/manifest.jsonc | 5 +- 11 files changed, 358 insertions(+), 33 deletions(-) create mode 100644 src/contentScripts/dashboard/quickCourseLinks/components/filterDropdown.tsx create mode 100644 src/contentScripts/dashboard/quickCourseLinks/components/liteItem.tsx create mode 100644 src/contentScripts/dashboard/quickCourseLinks/components/quickCourseLinks.tsx create mode 100644 src/contentScripts/dashboard/quickCourseLinks/filter.ts create mode 100644 src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts create mode 100644 src/contentScripts/dashboard/quickCourseLinks/hooks/useFilters.ts create mode 100644 src/contentScripts/dashboard/quickCourseLinks/index.ts diff --git a/deno.json b/deno.json index 4987ac4..10f89d0 100644 --- a/deno.json +++ b/deno.json @@ -47,12 +47,10 @@ "esbuild-plugin-debug-switch": "jsr:@tsukina-7mochi/esbuild-plugin-debug-switch@^0.2.0", "esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", - "preact": "https://esm.sh/preact@10.13.2", - "preact/compat": "https://esm.sh/preact@10.13.2/compat", - "preact/hooks": "https://esm.sh/preact@10.13.2/hooks", - "preact/jsx-dev-runtime": "https://esm.sh/preact@10.13.2/jsx-dev-runtime", - "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", - "preact/types": "https://raw.githubusercontent.com/preactjs/preact/10.13.2/src/index.d.ts", + "preact": "npm:preact@^10.24.3", + "preact/compat": "npm:preact@^10.24.3/compat", + "react/jsx-runtime": "npm:preact@^10.24.3/jsx-runtime", + "react/jsx-dev-runtime": "npm:preact@^10.24.3/jsx-dev-runtime", "webextension-polyfill": "npm:webextension-polyfill@^0.12.0", "~/": "./src/" }, diff --git a/deno.lock b/deno.lock index ad0747b..625831a 100644 --- a/deno.lock +++ b/deno.lock @@ -13,7 +13,7 @@ "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1": "0.1.1", "npm:esbuild@0.24": "0.24.0", "npm:esbuild@0.24.0": "0.24.0", - "npm:sass@^1.80.3": "1.80.3" + "npm:sass@^1.80.3": "1.80.5" }, "jsr": { "@luca/esbuild-deno-loader@0.11.0": { @@ -273,8 +273,8 @@ "readdirp@4.0.2": { "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==" }, - "sass@1.80.3": { - "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==", + "sass@1.80.5": { + "integrity": "sha512-TQd2aoQl/+zsxRMEDSxVdpPIqeq9UFc6pr7PzkugiTx3VYCFPUaa3P4RrBQsqok4PO200Vkz0vXQBNlg7W907g==", "dependencies": [ "@parcel/watcher", "chokidar", @@ -303,6 +303,7 @@ "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1", "npm:@types/webextension-polyfill@~0.12.1", "npm:esbuild@0.24.0", + "npm:preact@^10.24.3", "npm:webextension-polyfill@0.12" ] } diff --git a/src/common/model/course.ts b/src/common/model/course.ts index d9ddc64..b252e97 100644 --- a/src/common/model/course.ts +++ b/src/common/model/course.ts @@ -64,10 +64,8 @@ export type CourseJson = { /** the year the course is offered */ fullYear?: number; /** the semester the course is offered */ - semesterStart?: Semester; + semester?: [Semester, Semester]; /** the semester the course is offered */ - semesterEnd?: Semester; - /** the day of the week the course is offered */ weekOfDay?: WeekOfDay; /** the period in timetable the course is offered */ period?: [Period, Period]; @@ -88,9 +86,7 @@ export class Course { /** the year the course is offered */ fullYear?: number; /** the semester the course is offered */ - semesterStart?: Semester; - /** the semester the course is offered */ - semesterEnd?: Semester; + semester?: [Semester, Semester]; /** the day of the week the course is offered */ weekOfDay?: WeekOfDay; /** the period in timetable the course is offered */ @@ -102,8 +98,7 @@ export class Course { this.fullName = init.fullName; this.systemCourseName = init.systemCourseName; this.fullYear = init.fullYear; - this.semesterStart = init.semesterStart; - this.semesterEnd = init.semesterEnd; + this.semester = init.semester; this.weekOfDay = init.weekOfDay; this.period = init.period; } @@ -142,12 +137,7 @@ export class Course { } } - let semesterStart: Semester | undefined = undefined; - let semesterEnd: Semester | undefined = undefined; - const semester = findSemester(normalizedText); - if (semester) { - [semesterStart, semesterEnd] = semester; - } + const semester = findSemester(normalizedText) ?? undefined; const weekOfDay = findWeekOfDay(normalizedText) ?? undefined; const periodMatch = normalizedText.match(periodPattern); @@ -165,8 +155,7 @@ export class Course { fullName, systemCourseName, fullYear, - semesterStart, - semesterEnd, + semester, weekOfDay, period, }; @@ -183,8 +172,7 @@ export class Course { fullName: this.fullName, systemCourseName: this.systemCourseName, fullYear: this.fullYear, - semesterStart: this.semesterStart, - semesterEnd: this.semesterEnd, + semester: this.semester, weekOfDay: this.weekOfDay, period: this.period, }; @@ -202,13 +190,13 @@ export class Course { return 1; } - if (this.semesterStart && that.semesterStart) { - if (this.semesterStart !== that.semesterStart) { - return this.semesterStart - that.semesterStart; + if (this.semester && that.semester) { + if (this.semester[0] !== that.semester[0]) { + return this.semester[0] - that.semester[0]; } - } else if (this.semesterStart) { + } else if (this.semester?.[0]) { return -1; - } else if (that.semesterStart) { + } else if (that.semester?.[0]) { return 1; } diff --git a/src/contentScripts/dashboard/quickCourseLinks/components/filterDropdown.tsx b/src/contentScripts/dashboard/quickCourseLinks/components/filterDropdown.tsx new file mode 100644 index 0000000..50810e1 --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/components/filterDropdown.tsx @@ -0,0 +1,61 @@ +import type { Filter } from "../filter.ts"; + +export type FilterDropdownProps = { + options: Filter[]; + filter: Filter; + setFilter: (filter: Filter) => void; +}; + +export const FilterDropdown = ( + props: FilterDropdownProps, +) => { + const { options, filter, setFilter } = props; + + return ( +
    +
    + + +
    +
    + ); +}; diff --git a/src/contentScripts/dashboard/quickCourseLinks/components/liteItem.tsx b/src/contentScripts/dashboard/quickCourseLinks/components/liteItem.tsx new file mode 100644 index 0000000..5f325d6 --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/components/liteItem.tsx @@ -0,0 +1,39 @@ +import type { Course } from "~/common/model/course.ts"; + +export type ListItemProps = Pick< + Course, + "id" | "name" | "weekOfDay" | "period" +>; + +const weekOfDayName: Record, string> = { + sun: "日曜", + mon: "月曜", + tue: "火曜", + wed: "水曜", + thu: "木曜", + fri: "金曜", + sat: "土曜", +}; + +const coursePageUrl = (id: string) => + `https://cms7.ict.nitech.ac.jp/moodle40a/course/view.php?id=${id}`; + +export const ListItem = function ( + props: ListItemProps, +) { + const { id, name, weekOfDay, period } = props; + + return ( +
  • + + {weekOfDay + ? {weekOfDayName[weekOfDay]} + : null} + {period + ? {period[0]}-{period[1]}限 + : null} + {name} + +
  • + ); +}; diff --git a/src/contentScripts/dashboard/quickCourseLinks/components/quickCourseLinks.tsx b/src/contentScripts/dashboard/quickCourseLinks/components/quickCourseLinks.tsx new file mode 100644 index 0000000..803ddb2 --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/components/quickCourseLinks.tsx @@ -0,0 +1,82 @@ +import { useEffect, useMemo, useState } from "preact/compat"; + +import { useCourses } from "../hooks/useCourses.ts"; +import { useFilter } from "../hooks/useFilters.ts"; +import { ListItem } from "./liteItem.tsx"; +import { pickFilterByDate } from "../filter.ts"; +import { FilterDropdown } from "./filterDropdown.tsx"; + +export const QuickCourseLinks = function () { + const [filterInitialized, setFilterInitialized] = useState(false); + const courses = useCourses(); + const { filter, setFilter, options: filterOptions } = useFilter( + courses ?? [], + ); + + useEffect(() => { + if (!courses) return; + if (filterInitialized) return; + + setFilter(pickFilterByDate(filterOptions) ?? filter); + setFilterInitialized(true); + }, [ + courses, + filter, + filterOptions, + filterInitialized, + ]); + + const filteredCourses = useMemo(() => { + const sorted = (courses ?? []).toSorted((a, b) => a.compare(b)); + + if (!filter) return sorted; + return sorted.filter((course) => filter.test(course)); + }, [courses, filter]); + + return ( + <> +
    +
    コースリンク
    +
    +
    +
    + +
    +
    +
    +
    +
    + {filteredCourses.map((course) => ( + + ))} +
    +
    +
    +
    +
    +
    +
    +
    + + ); +}; diff --git a/src/contentScripts/dashboard/quickCourseLinks/filter.ts b/src/contentScripts/dashboard/quickCourseLinks/filter.ts new file mode 100644 index 0000000..00c1ded --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/filter.ts @@ -0,0 +1,79 @@ +import type { Course } from "~/common/model/course.ts"; + +export class Filter { + year?: number; + // matches to course when any semester is included + semesters?: number[]; + label: string; + + constructor( + label: string, + year?: number, + semesters?: number[], + ) { + this.year = year; + this.semesters = semesters; + this.label = label; + } + + test(course: Course): boolean { + // match any courses if no year is specified + if (!this.year) return true; + if (course.fullYear !== this.year) return false; + + // match any semester if no semester is specified + if (!this.semesters) return true; + return this.semesters.some((s) => { + if (!course.semester) return false; + return course.semester[0] <= s && s <= course.semester[1]; + }); + } +} + +export const filtersMatchesToCourses = function (courses: Course[]): Filter[] { + let years = courses + .map((course) => course.fullYear) + .filter((v) => v !== undefined); + years = Array.from(new Set(years)); + years.sort().reverse(); + + return years.flatMap((year) => [ + new Filter(`${year}年`, year), + new Filter(`${year}年 第1クォーター`, year, [1]), + new Filter(`${year}年 第2クォーター`, year, [2]), + new Filter(`${year}年 第3クォーター`, year, [3]), + new Filter(`${year}年 第4クォーター`, year, [4]), + new Filter(`${year}年 前期`, year, [1, 2]), + new Filter(`${year}年 後期`, year, [3, 4]), + ]).filter((filter) => courses.some((course) => filter.test(course))); +}; + +export const pickFilterByDate = function ( + filters: Filter[], + date: Date = new Date(), +): Filter | null { + const year = date.getFullYear(); + const semester = (() => { + // deno-fmt-ignore + switch (date.getMonth()) { + case 0: return 4; + case 1: return 4; + case 2: return 4; + case 3: return 1; + case 4: return 1; + case 5: return 2; + case 6: return 2; + case 7: return 2; + case 8: return 2; + case 9: return 3; + case 10: return 3; + case 11: return 4; + default: return -1; + } + })(); + const filter = filters.find((filter) => { + return filter.year === year && filter.semesters?.includes(semester); + }); + + return filter ?? null; +}; diff --git a/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts b/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts new file mode 100644 index 0000000..322b445 --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts @@ -0,0 +1,14 @@ +import { useEffect, useState } from "preact/hooks"; + +import type { Course } from "~/common/model/course.ts"; +import { getCourses } from "~/common/newStorage/courses/index.ts"; + +export const useCourses = function () { + const [courses, setCourses] = useState(null); + + useEffect(() => { + getCourses().then(setCourses); + }, []); + + return courses; +}; diff --git a/src/contentScripts/dashboard/quickCourseLinks/hooks/useFilters.ts b/src/contentScripts/dashboard/quickCourseLinks/hooks/useFilters.ts new file mode 100644 index 0000000..fc0d634 --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/hooks/useFilters.ts @@ -0,0 +1,16 @@ +import { useMemo, useState } from "preact/hooks"; + +import { Filter } from "../filter.ts"; +import { filtersMatchesToCourses } from "../filter.ts"; +import type { Course } from "~/common/model/course.ts"; + +export const useFilter = function (courses: Course[]) { + const defaultFilter = useMemo(() => (new Filter("すべて")), []); + const options = useMemo(() => { + const filters = filtersMatchesToCourses(courses); + return [...filters, defaultFilter]; + }, [courses]); + const [filter, setFilter] = useState(defaultFilter); + + return { filter, setFilter, options }; +}; diff --git a/src/contentScripts/dashboard/quickCourseLinks/index.ts b/src/contentScripts/dashboard/quickCourseLinks/index.ts new file mode 100644 index 0000000..abe12fa --- /dev/null +++ b/src/contentScripts/dashboard/quickCourseLinks/index.ts @@ -0,0 +1,44 @@ +import * as preact from "preact"; +import { isDebug } from "esbuild-plugin-debug-switch"; + +import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; +import { QuickCourseLinks } from "./components/quickCourseLinks.tsx"; + +const CARD_DATA_BLOCK = "ext40a-quick-course-links"; + +const renderQuickCourseLinks = function () { + const blocksRoot = document.getElementById("block-region-content"); + if (!blocksRoot) return; + + // do not use `blocksRoot` as root of preact element, preact will delete + // the first child node. + if (document.querySelector(`*[data-block=${CARD_DATA_BLOCK}`)) return; + const root = document.createElement("section"); + root.dataset["block"] = CARD_DATA_BLOCK; + root.className = "block card mb-3"; + + blocksRoot.insertBefore(root, blocksRoot.childNodes[0] ?? null); + + preact.render( + preact.createElement(QuickCourseLinks, null), + root, + ); +}; + +const main = async function () { + const preferences = await getPreferences(); + if (!preferences.dashboardQuickCourseLinks.enabled) return; + + if (isDebug) { + console.log("QuickCourseLinks is enabled."); + } + + renderQuickCourseLinks(); + registerMutationObserverCallback(renderQuickCourseLinks, { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }); +}; + +main(); diff --git a/src/manifest.jsonc b/src/manifest.jsonc index b18eb43..de63f69 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -31,7 +31,10 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/my/", "https://cms7.ict.nitech.ac.jp/moodle40a/my/index.php" ], - "js": ["./contentScripts/dashboard/readAndStoreCourses/index.ts"], + "js": [ + "./contentScripts/dashboard/readAndStoreCourses/index.ts", + "./contentScripts/dashboard/quickCourseLinks/index.ts" + ], "css": ["./contentScripts/dashboard/main.scss"] }, // scorm page From 9e4b4b0f8735f13e034ecd207d7d06c179eadc55 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Thu, 31 Oct 2024 22:54:39 +0900 Subject: [PATCH 34/53] build: change source map settings --- build/build.ts | 2 +- build/options/javascript.ts | 4 +++- src/manifest.jsonc | 10 +--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/build/build.ts b/build/build.ts index 3f061eb..40ce520 100644 --- a/build/build.ts +++ b/build/build.ts @@ -99,7 +99,7 @@ if (!watchMode) { Deno.exit(0); } -await Promise.all(buildContexts.map((ctx) => ctx.rebuild())); +await Promise.all(buildContexts.map((ctx) => ctx.watch())); console.log("Watching..."); console.log('press "r" to rebuild'); diff --git a/build/options/javascript.ts b/build/options/javascript.ts index 157e64d..647fd2c 100644 --- a/build/options/javascript.ts +++ b/build/options/javascript.ts @@ -20,8 +20,10 @@ export const buildOptions = ( outdir: options.destPath, platform: "browser", bundle: true, - sourcemap: options.dev ? "linked" : false, + sourcemap: options.dev ? "inline" : false, minify: !options.dev, + jsxDev: options.dev, + jsx: "automatic", jsxFactory: options.jsxFactory, jsxFragment: options.jsxFragmentFactory, plugins: [ diff --git a/src/manifest.jsonc b/src/manifest.jsonc index de63f69..2f099dd 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -73,13 +73,5 @@ }, "permissions": ["storage"], - "host_permissions": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], - - "web_accessible_resources": [ - // source maps - { - "resources": ["contentScripts/*/*.map"], - "matches": [""] - } - ] + "host_permissions": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"] } From e5c33932806548302c4f1f4ae6f5cc7e499c7723 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 01:25:40 +0900 Subject: [PATCH 35/53] feat: add function to show count down timer in event calendar --- .../eventCountdown/components/countdown.tsx | 36 ++++++ .../eventCountdown/hooks/useRemainingTime.ts | 35 ++++++ .../dashboard/eventCountdown/index.ts | 60 ++++++++++ .../dashboard/eventsCountdown.ts | 88 -------------- .../eventsCountdown/EventsCountdown.tsx | 112 ------------------ src/contentScripts/dashboard/main.scss | 12 ++ src/manifest.jsonc | 3 +- 7 files changed, 145 insertions(+), 201 deletions(-) create mode 100644 src/contentScripts/dashboard/eventCountdown/components/countdown.tsx create mode 100644 src/contentScripts/dashboard/eventCountdown/hooks/useRemainingTime.ts create mode 100644 src/contentScripts/dashboard/eventCountdown/index.ts delete mode 100644 src/contentScripts/dashboard/eventsCountdown.ts delete mode 100644 src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx diff --git a/src/contentScripts/dashboard/eventCountdown/components/countdown.tsx b/src/contentScripts/dashboard/eventCountdown/components/countdown.tsx new file mode 100644 index 0000000..1205648 --- /dev/null +++ b/src/contentScripts/dashboard/eventCountdown/components/countdown.tsx @@ -0,0 +1,36 @@ +import { useRemainingTime } from "../hooks/useRemainingTime.ts"; + +const createTimeText = function (seconds: number): string { + if (seconds < 60) { + return `${seconds}秒`; + } else if (seconds < 3600) { + return `${Math.floor(seconds / 60)}分`; + } else if (seconds < 86400) { + return `${Math.floor(seconds / 3600)}時間`; + } else { + return `${Math.floor(seconds / 86400)}日`; + } +}; + +export type CountdownProps = { + targetDate: Date; +}; + +export const Countdown = function (props: CountdownProps) { + const remainingTime = useRemainingTime(props.targetDate); + + const expired = remainingTime < 0; + const timeText = createTimeText(Math.floor(Math.abs(remainingTime / 1000))); + + const decorationClass = expired + ? "text-muted" + : (remainingTime < 86_400_000 ? "red" : ""); + + return ( +
    +
    + {expired ? `${timeText}前` : `あと${timeText}`} +
    +
    + ); +}; diff --git a/src/contentScripts/dashboard/eventCountdown/hooks/useRemainingTime.ts b/src/contentScripts/dashboard/eventCountdown/hooks/useRemainingTime.ts new file mode 100644 index 0000000..769ee98 --- /dev/null +++ b/src/contentScripts/dashboard/eventCountdown/hooks/useRemainingTime.ts @@ -0,0 +1,35 @@ +import { useCallback, useEffect, useState } from "preact/hooks"; + +const calculateUpdateTimeout = function (milliseconds: number): number { + const seconds = milliseconds / 1_000; + if (seconds < 60) { + return 1_000 - (milliseconds % 1_000); + } else if (seconds < 3_600) { + return 60_000 - (milliseconds % 60_000); + } else if (seconds < 86_400) { + return 3_600_000 - (milliseconds % 3_600_000); + } else { + return 86_400_000 - (milliseconds % 86_400_000); + } +}; + +export const useRemainingTime = function (targetDate: Date) { + const [remainingTime, setRemainingTime] = useState(0); + + const updateRemainingTime = useCallback(() => { + const now = new Date(); + const diff = targetDate.getTime() - now.getTime(); + setRemainingTime(diff); + }, [targetDate]); + + useEffect(updateRemainingTime, [targetDate]); + + useEffect(() => { + const delay = calculateUpdateTimeout(Math.abs(remainingTime)); + const timeoutId = setTimeout(updateRemainingTime, delay); + + return () => clearTimeout(timeoutId); + }, [remainingTime]); + + return remainingTime; +}; diff --git a/src/contentScripts/dashboard/eventCountdown/index.ts b/src/contentScripts/dashboard/eventCountdown/index.ts new file mode 100644 index 0000000..83ccff5 --- /dev/null +++ b/src/contentScripts/dashboard/eventCountdown/index.ts @@ -0,0 +1,60 @@ +import * as preact from "preact"; +import { isDebug } from "esbuild-plugin-debug-switch"; + +import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; +import { Countdown } from "./components/countdown.tsx"; + +const CLASS_NAME = "ext40a-event-countdown"; + +const renderEventCountdowns = function () { + const calendarRoot = document.querySelector( + '*[data-block="calendar_upcoming"]', + ); + if (!calendarRoot) return; + + const items = Array.from( + calendarRoot.querySelectorAll( + '*[data-region="event-item"] > *:nth-child(2)', + ), + ); + for (const item of items) { + if (item.classList.contains(CLASS_NAME)) continue; + item.classList.add(CLASS_NAME); + + const link = item.querySelector(".date a"); + if (!link) continue; + + // e.g., https://cms7.ict.nitech.ac.jp/moodle40a/calendar/view.php?view=day&time=1680940800 + const time = new URL(link.href).searchParams.get("time"); + if (!time) continue; + + const date = new Date(Number(time) * 1000); + + const root = document.createElement("div"); + root.className = `${CLASS_NAME} small`; + + item.appendChild(root); + preact.render( + preact.createElement(Countdown, { targetDate: date }), + root, + ); + } +}; + +const main = async function () { + const preferences = await getPreferences(); + if (!preferences.dashboardEventsCountdown.enabled) return; + + if (isDebug) { + console.log("EventCountdown is enabled."); + } + + renderEventCountdowns(); + registerMutationObserverCallback(renderEventCountdowns, { + rootElement: document.body, + observerOptions: { childList: true, subtree: true }, + }); +}; + +main(); diff --git a/src/contentScripts/dashboard/eventsCountdown.ts b/src/contentScripts/dashboard/eventsCountdown.ts deleted file mode 100644 index 0cfa845..0000000 --- a/src/contentScripts/dashboard/eventsCountdown.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** @jsxImportSource preact */ - -import type { Feature } from "../common/types.ts"; -import { - EventsCountdownProps, - renderEventsCountdown, -} from "./eventsCountdown/EventsCountdown.tsx"; - -type AddEventCountdownOptions = { - enabled: boolean; -}; - -const CalendarLinkDateNumRegExp = /\?.*time=(\d+).*$/; - -/** 直近イベントにカウントダウンを追加 */ -const addEventsCountdown: Feature = { - uniqueName: "dashboard-events-countdown", - hostnameFilter: "cms7.ict.nitech.ac.jp", - pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, - loader: (options) => { - if (!options.enabled) { - return; - } - - const elUpcomingEvents = document.querySelector( - "section.block_calendar_upcoming", - ); - if (!elUpcomingEvents) { - return; - } - - const appRoot = document.createElement("div"); - appRoot.id = "nitech_moodle_ext_events_countdown_root"; - elUpcomingEvents.append(appRoot); - - const elEventItems = Array.from( - elUpcomingEvents.querySelectorAll( - 'div[data-region="event-item"]', - ), - ); - - // 締め切り時間とそれを描画する DOM 要素のリストを作成 - const eventItems: EventsCountdownProps["items"] = []; - for (const elEventItem of elEventItems) { - const elLabel = elEventItem.querySelector("div.date"); - if (!elLabel) { - continue; - } - const elLink = elLabel.querySelector("a"); - if (!elLink) { - continue; - } - // elLink.href の例: - // https://cms7.ict.nitech.ac.jp/moodle40a/calendar/view.php?view=day&time=1680940800 - const dateNumMatch = CalendarLinkDateNumRegExp.exec(elLink.href); - if (!dateNumMatch) { - continue; - } - - const expireDate = new Date(parseInt(dateNumMatch[1]) * 1000); - - const elCountdown = document.createElement("div"); - elCountdown.className = "nitech-moodle-ext-event-countdown"; - elLabel.appendChild(elCountdown); - - eventItems.push({ - expireDate, - portalTarget: elCountdown, - }); - } - - renderEventsCountdown({ items: eventItems }, appRoot); - - // ツリーが変化した際 (イベントの削除など) にカウントダウンが削除されるため - // ツリーの変更を監視する - const observer = new MutationObserver(() => { - observer.disconnect(); - // `preact.render(...)` ではうまくいかなかった - addEventsCountdown.loader(options); - }); - observer.observe(elUpcomingEvents, { - childList: true, - subtree: true, - }); - }, -}; - -export default addEventsCountdown; diff --git a/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx b/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx deleted file mode 100644 index 9eb4a32..0000000 --- a/src/contentScripts/dashboard/eventsCountdown/EventsCountdown.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from "preact"; -import { createPortal, useState } from "preact/compat"; - -/** @param duration 期間の長さ (秒) */ -const createDurationText = function (duration: number) { - if (duration < 60) { - // 60秒未満 - return `${duration}秒`; - } else if (duration < 3600) { - // 60分未満 - return `${Math.floor(duration / 60)}分`; - } else if (duration < 86400) { - // 24時間未満 - return `${Math.floor(duration / 3600)}時間`; - } else { - return `${Math.floor(duration / 86400)}日`; - } -}; - -/** 次に描画を更新すべき時間を返す */ -const msUntilNextUpdate = function (duration: number, currentTime: number) { - /** 更新間隔 (s) */ - let refreshRate = 1; - - if (duration < 60) { - // 60秒未満; 秒単位で更新 - refreshRate = 1; - } else if (duration < 3600) { - // 60分未満; 分単位で更新 - refreshRate = 60; - } else if (duration < 86400) { - // 24時間未満; 時間単位で更新 - refreshRate = 3600; - } else { - // 24時間以上; 日単位で更新 - refreshRate = 86400; - } - - const refreshRateMs = refreshRate * 1000; - - return refreshRateMs - currentTime % refreshRateMs; -}; - -interface CountdownProps { - expireDate: Date; - portalTarget: HTMLElement; -} - -const Countdown = (props: CountdownProps) => { - const [currentTime, setCurrentTime] = useState(Date.now()); - - const duration = Math.floor( - (props.expireDate.getTime() - currentTime) / 1000, - ); - setTimeout( - () => setCurrentTime(Date.now()), - msUntilNextUpdate(Math.abs(duration), currentTime), - ); - - let className = ""; - if (duration < 0) { - className = "exceeded text-muted"; - } else if (duration < 86400) { - className = "imminent red"; - } - - return createPortal( - ( - - {duration >= 0 - ? `残り${createDurationText(duration)}` - : `${createDurationText(-duration)}前`} - - ), - props.portalTarget, - ); -}; - -interface EventsCountdownProps { - items: { - expireDate: Date; - portalTarget: HTMLElement; - }[]; -} - -const EventsCountdown = (props: EventsCountdownProps) => ( - <> - {props.items.map((item) => ( - - ))} - -); - -const renderEventsCountdown = function ( - props: EventsCountdownProps, - targetElement: HTMLElement, -) { - preact.render( - , - targetElement, - ); -}; - -export { renderEventsCountdown }; - -export type { EventsCountdownProps }; diff --git a/src/contentScripts/dashboard/main.scss b/src/contentScripts/dashboard/main.scss index 81754ff..62456e1 100644 --- a/src/contentScripts/dashboard/main.scss +++ b/src/contentScripts/dashboard/main.scss @@ -24,3 +24,15 @@ section.block_myoverview ul li div.row { section.block ul li:last-child { border-bottom: none; } + +// fix padding and alignment of event calendar +section.block_calendar_upcoming { + div.event[data-region="event-item"] { + align-items: center; + padding: 12px 0 !important; + } + + div.footer { + padding-top: 8px; + } +} diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 2f099dd..123396f 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -33,7 +33,8 @@ ], "js": [ "./contentScripts/dashboard/readAndStoreCourses/index.ts", - "./contentScripts/dashboard/quickCourseLinks/index.ts" + "./contentScripts/dashboard/quickCourseLinks/index.ts", + "./contentScripts/dashboard/eventCountdown/index.ts" ], "css": ["./contentScripts/dashboard/main.scss"] }, From 8e339ae15bd6033b0fa3886b2972dd9ba270e9f9 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 01:26:46 +0900 Subject: [PATCH 36/53] chore: remove unused files --- src/contentScripts/common/debugMode.ts | 3 - src/contentScripts/common/loadFeature.ts | 164 ------------------ src/contentScripts/common/types.ts | 21 --- src/contentScripts/dashboard/dashboard.ts | 19 -- .../dashboard/updateCourseRepository.ts | 133 -------------- .../dashboard/waitForPageLoad.ts | 51 ------ 6 files changed, 391 deletions(-) delete mode 100644 src/contentScripts/common/debugMode.ts delete mode 100644 src/contentScripts/common/loadFeature.ts delete mode 100644 src/contentScripts/common/types.ts delete mode 100644 src/contentScripts/dashboard/dashboard.ts delete mode 100644 src/contentScripts/dashboard/updateCourseRepository.ts delete mode 100644 src/contentScripts/dashboard/waitForPageLoad.ts diff --git a/src/contentScripts/common/debugMode.ts b/src/contentScripts/common/debugMode.ts deleted file mode 100644 index af7e177..0000000 --- a/src/contentScripts/common/debugMode.ts +++ /dev/null @@ -1,3 +0,0 @@ -const debugMode = true; - -export default debugMode; diff --git a/src/contentScripts/common/loadFeature.ts b/src/contentScripts/common/loadFeature.ts deleted file mode 100644 index e87e7e9..0000000 --- a/src/contentScripts/common/loadFeature.ts +++ /dev/null @@ -1,164 +0,0 @@ -// @deno-types=npm:@types/lodash -import * as lodash from "lodash"; -import type { Feature, FeatureUniqueName } from "../common/types.ts"; -import { defaultFeatureOption } from "../../common/options.ts"; -import { getOptions } from "../../common/storage/options.ts"; - -/** feature を依存関係に従ってトポロジカルソートする */ -// DFS を用いて探索 -const sortFeatures = function (features: Feature[]) { - // 重複チェック - const featureNames = features.map((feature) => feature.uniqueName); - if (new Set(featureNames).size !== features.length) { - throw Error( - `Multiple features has the same unique name or features duplicated`, - ); - } - - const featureNameMap = new Map(); - for (const feature of features) { - featureNameMap.set(feature.uniqueName, feature); - } - - const visited = new Set(); - const result: Feature[] = []; - const visit = function ( - feature: Feature, - localVisited: Set, - ) { - if (visited.has(feature.uniqueName)) { - return; - } - if (localVisited.has(feature.uniqueName)) { - throw Error( - `Circular dependency detected on resolving feature ${feature.uniqueName}`, - ); - } - - visited.add(feature.uniqueName); - localVisited.add(feature.uniqueName); - - for (const depFeatureName of feature.dependencies ?? []) { - const depFeature = featureNameMap.get(depFeatureName); - if (!depFeature) { - throw Error( - `Feature ${depFeatureName} is not provided to feature loader`, - ); - } - visit(depFeature, localVisited); - } - - result.push(feature); - }; - - for (const feature of features) { - visit(feature, new Set()); - } - - return result; -}; - -/** - * 文字列か正規表現で対象文字列をテストする; - * `test` が文字列の場合は完全一致, 正規表現の場合は `RegExp.test` の結果 - */ -const testByStringOrRegExp = function (test: string | RegExp, target: string) { - if (typeof test === "string") { - return test === target; - } else if (test instanceof RegExp) { - return test.test(target); - } - return false; -}; - -/** `Feature` を依存関係を解決しながら読み込む */ -const loadFeature = async function ( - features: Feature[], - contextUrl: URL, - showLog = false, -) { - const options = await getOptions(); - const contextHost = contextUrl.hostname; - const contextPath = contextUrl.pathname; - // ここで URL のフィルターをかけたほうが処理量は減るが、 - // 特定のページでのみ依存関係の解決に失敗するとバグの発見がしづらいため - // 実行時に URL をチェックする - const sortedFeatures = sortFeatures(features); - const loaderPromiseMap = new Map>(); - - const rootPromiseEventTarget = new EventTarget(); - const rootPromise = new Promise((resolve) => { - rootPromiseEventTarget.addEventListener("start", () => resolve()); - }); - - for (const feature of sortedFeatures) { - const depFeaturePromises = (feature.dependencies ?? []).map((name) => - loaderPromiseMap.get(name) - ); - if (depFeaturePromises.some((v) => v === undefined)) { - throw Error( - `Failed to resolve feature ${feature.uniqueName}: dependency feature does not exist`, - ); - } - - if (depFeaturePromises.length === 0) { - depFeaturePromises.push(rootPromise); - } - - // 各 Feature を実行する Promise を作成 - loaderPromiseMap.set( - feature.uniqueName, - Promise.all(depFeaturePromises).then(() => { - if (!testByStringOrRegExp(feature.hostnameFilter, contextHost)) { - if (showLog) { - console.log( - `[FeatureLoader] Skipping ${feature.uniqueName}: hostname does not match`, - ); - } - return; - } - if (!testByStringOrRegExp(feature.pathnameFilter, contextPath)) { - if (showLog) { - console.log( - `[FeatureLoader] Skipping ${feature.uniqueName}: pathname does not match`, - ); - } - return; - } - - if (showLog) { - console.log(`[FeatureLoader] Loading ${feature.uniqueName}`); - } - - const option = lodash.defaultsDeep( - options.features[feature.uniqueName], - defaultFeatureOption, - ); - - if (feature.propagateError === false) { - // 失敗しても警告として出力するだけ - return Promise.resolve(feature.loader(option)).catch( - (err: unknown) => { - console.warn( - `Uncaught error in feature loader ${feature.uniqueName}: `, - err, - ); - }, - ); - } - - return Promise.resolve(feature.loader(option)).catch((err: unknown) => { - return Promise.reject(Error( - `Uncaught error in feature loader ${feature.uniqueName}`, - { cause: err }, - )); - }); - }), - ); - } - - rootPromiseEventTarget.dispatchEvent(new CustomEvent("start")); - await Promise.all(loaderPromiseMap.values()); -}; - -export default loadFeature; diff --git a/src/contentScripts/common/types.ts b/src/contentScripts/common/types.ts deleted file mode 100644 index dbe1487..0000000 --- a/src/contentScripts/common/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FeatureOption } from "../../common/options.ts"; - -export type FeatureUniqueName = string; - -/** - * 独立した機能を表す - */ -export interface Feature { - /** 機能の一意な名前 */ - uniqueName: FeatureUniqueName; - /** ホスト名がマッチ (または一致) した場合に実行される */ - hostnameFilter: RegExp | string; - /** パス名がマッチ (または一致) した場合に実行される */ - pathnameFilter: RegExp | string; - /** 依存する機能の `uniqueName` */ - dependencies?: FeatureUniqueName[]; - /** 機能の本体 (同期でも非同期でも良い) */ - loader: (options: Required) => void | Promise; - /** エラーを伝播するかどうか (デフォルト: `true`) */ - propagateError?: boolean; -} diff --git a/src/contentScripts/dashboard/dashboard.ts b/src/contentScripts/dashboard/dashboard.ts deleted file mode 100644 index cca53c3..0000000 --- a/src/contentScripts/dashboard/dashboard.ts +++ /dev/null @@ -1,19 +0,0 @@ -import debugMode from "../common/debugMode.ts"; -import loadFeature from "../common/loadFeature.ts"; -import waitForPageLoad from "./waitForPageLoad.ts"; -import renderQuickCourseView from "./renderQuickCourseView.ts"; -import updateCourseRepository from "./updateCourseRepository.ts"; -import addEventsCountdown from "./eventsCountdown.ts"; - -globalThis.addEventListener("load", () => { - loadFeature( - [ - waitForPageLoad, - renderQuickCourseView, - updateCourseRepository, - addEventsCountdown, - ], - new URL(location.href), - debugMode, - ); -}); diff --git a/src/contentScripts/dashboard/updateCourseRepository.ts b/src/contentScripts/dashboard/updateCourseRepository.ts deleted file mode 100644 index 73429ce..0000000 --- a/src/contentScripts/dashboard/updateCourseRepository.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Course, RegularLectureCourse } from "../../common/course.ts"; -import { storeCourseByMerge } from "../../common/storage/course.ts"; -import type { Feature } from "../common/types.ts"; -import waitForPageLoad from "./waitForPageLoad.ts"; -import { textToSemesterMap, textToWeekOfDayMap } from "../../common/course.ts"; - -const regularCourseRegExp = - /^(.+)\s*(\d{4})(\d)(\d{4})\s*(前期|後期|第(?:1|2|3|4)クォーター)\s*((?:日|月|火|水|木|金|土)曜)\s*(\d\d?)-(\d\d?)限.*$/; - -interface DecodedLectureCourse { - type: RegularLectureCourse["type"]; - name: RegularLectureCourse["name"]; - fullName: RegularLectureCourse["fullName"]; - fullYear: RegularLectureCourse["fullYear"]; - curriculumPart: RegularLectureCourse["curriculumPart"]; - code: RegularLectureCourse["code"]; - semester: RegularLectureCourse["semester"]; - weekOfDay: RegularLectureCourse["weekOfDay"]; - period: RegularLectureCourse["period"]; -} - -const convertFullWidthToHalfWidth = function (input: string): string { - return input.replace( - /[0-9]/g, - (s) => String.fromCharCode(s.charCodeAt(0) - 0xFEE0), - ); -}; - -const decodeRegularLectureCourseText = function ( - text: string, -): DecodedLectureCourse { - const match = regularCourseRegExp.exec(convertFullWidthToHalfWidth(text)); - if (match === null) { - throw Error(`"${text}" does not matches to the pattern`); - } - - const curriculumPart = parseInt(match[3]); - if (curriculumPart !== 1 && curriculumPart !== 2 && curriculumPart !== 4) { - throw Error(`${curriculumPart} is not a valid curriculum part`); - } - if (!(match[5] in textToSemesterMap)) { - throw Error(`${match[5]} is not a valid semester`); - } - const semester = - textToSemesterMap[match[5] as keyof typeof textToSemesterMap]; - if (!(match[6] in textToWeekOfDayMap)) { - throw Error(`${match[6]} is not a valid week of day`); - } - const weekOfDay = - textToWeekOfDayMap[match[6] as keyof typeof textToWeekOfDayMap]; - - return { - type: "regular-lecture", - name: match[1].trim(), - fullName: text, - fullYear: parseInt(match[2]), - curriculumPart, - code: parseInt(match[4]), - semester, - weekOfDay, - period: [parseInt(match[7]), parseInt(match[8])], - }; -}; - -const pageLinkIdRegExp = /id=(\d+)/; -/** コースのリストを「コース概要」のセクションから - * 読み取り、ストレージに保存する */ -const updateCourseRepository: Feature = { - uniqueName: "dashboard-update-course-repository", - hostnameFilter: "cms7.ict.nitech.ac.jp", - pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, - dependencies: [waitForPageLoad.uniqueName], - loader: async (options) => { - if (!options.enabled) { - return; - } - - const thisYear = new Date().getFullYear(); - const thisYearStr = `${thisYear}`; - const elMyOverview = document.querySelector("section.block_myoverview"); - if (!elMyOverview) { - throw Error( - `[${updateCourseRepository.uniqueName}] Failed to get "my overview" section`, - ); - } - - // コース表示はマルチページになっている - // 実際になっている様子を確認していないのでちゃんと動くかは要検証 - const courses: Course[] = []; - const elItemLinks = Array.from( - elMyOverview.querySelectorAll("ul.list-group li a.aalink.coursename"), - ) as HTMLAnchorElement[]; - for (const elItemLink of elItemLinks) { - const search = new URL(elItemLink.href).search; - const pageIdMatch = pageLinkIdRegExp.exec(search); - const pageId = parseInt(pageIdMatch?.[1] ?? "0"); - const elShortName = elItemLink.previousElementSibling?.querySelector( - "div > div", - ); - - if (!elShortName) { - continue; - } - const shortName = elShortName.textContent ?? ""; - - const text = Array.from(elItemLink.childNodes) - .filter((v) => v.nodeType === 3) - .map((v) => v.textContent) - .join("") - .trim(); - try { - courses.push({ - ...decodeRegularLectureCourseText(text), - pageId, - shortName, - }); - } catch { - courses.push({ - type: "special", - name: text, - fullName: text, - fullYear: text.includes(thisYearStr) ? thisYear : undefined, - pageId, - shortName, - }); - } - } - - await storeCourseByMerge(courses); - }, -}; - -export default updateCourseRepository; diff --git a/src/contentScripts/dashboard/waitForPageLoad.ts b/src/contentScripts/dashboard/waitForPageLoad.ts deleted file mode 100644 index c83d415..0000000 --- a/src/contentScripts/dashboard/waitForPageLoad.ts +++ /dev/null @@ -1,51 +0,0 @@ -// @deno-types=npm:@types/lodash -import * as lodash from "lodash"; -import type { Feature } from "../common/types.ts"; - -const defaultOption = { - enabled: true, - timeout: 5000, -}; - -/** ダッシュボードの内容が読み込まれるまで待つ */ -const waitForPageLoad: Feature = { - uniqueName: "dashboard-wait-for-page-load", - hostnameFilter: "cms7.ict.nitech.ac.jp", - pathnameFilter: /^\/moodle40a\/my\/(index\.php)?$/, - propagateError: false, - loader: (options_) => { - if (!options_.enabled) { - return; - } - const options = lodash.merge(options_, defaultOption); - - return new Promise((resolve, reject) => { - const startTime = Date.now(); - - const checkPageContent = () => { - const loadingContent = document.querySelector( - 'section.block_myoverview div[data-region="paged-content-page"]', - ); - - if (loadingContent !== null) { - // この時点ではまだコース一覧が読み込まれていないため待つ - // Mutation observer とかでうまくいくかもしれないけれど - // コース数が0だと更新が入らなくて動かないかも? - setTimeout(resolve, 1000); - } else { - const timePassed = Date.now() - startTime; - - if (timePassed > options.timeout) { - reject(Error(`[${waitForPageLoad.uniqueName}] timeout`)); - } else { - setTimeout(checkPageContent, 100); - } - } - }; - - checkPageContent(); - }); - }, -}; - -export default waitForPageLoad; From 8d19bbcb01a35ce3d2f779cc646864f653e48793 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 01:29:28 +0900 Subject: [PATCH 37/53] chore: rename allPages -> all --- .../{allPages => all}/removeForceDownload/index.ts | 0 .../replaceBreadcrumbCourseName/index.ts | 0 .../replaceNavigationCourseName/index.ts | 0 .../{allPages => all}/startMessage/index.ts | 0 src/manifest.jsonc | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/contentScripts/{allPages => all}/removeForceDownload/index.ts (100%) rename src/contentScripts/{allPages => all}/replaceBreadcrumbCourseName/index.ts (100%) rename src/contentScripts/{allPages => all}/replaceNavigationCourseName/index.ts (100%) rename src/contentScripts/{allPages => all}/startMessage/index.ts (100%) diff --git a/src/contentScripts/allPages/removeForceDownload/index.ts b/src/contentScripts/all/removeForceDownload/index.ts similarity index 100% rename from src/contentScripts/allPages/removeForceDownload/index.ts rename to src/contentScripts/all/removeForceDownload/index.ts diff --git a/src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts b/src/contentScripts/all/replaceBreadcrumbCourseName/index.ts similarity index 100% rename from src/contentScripts/allPages/replaceBreadcrumbCourseName/index.ts rename to src/contentScripts/all/replaceBreadcrumbCourseName/index.ts diff --git a/src/contentScripts/allPages/replaceNavigationCourseName/index.ts b/src/contentScripts/all/replaceNavigationCourseName/index.ts similarity index 100% rename from src/contentScripts/allPages/replaceNavigationCourseName/index.ts rename to src/contentScripts/all/replaceNavigationCourseName/index.ts diff --git a/src/contentScripts/allPages/startMessage/index.ts b/src/contentScripts/all/startMessage/index.ts similarity index 100% rename from src/contentScripts/allPages/startMessage/index.ts rename to src/contentScripts/all/startMessage/index.ts diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 123396f..b56fb9f 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -19,10 +19,10 @@ { "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/*/*"], "js": [ - "./contentScripts/allPages/startMessage/index.ts", - "./contentScripts/allPages/removeForceDownload/index.ts", - "./contentScripts/allPages/replaceBreadcrumbCourseName/index.ts", - "./contentScripts/allPages/replaceNavigationCourseName/index.ts" + "./contentScripts/all/startMessage/index.ts", + "./contentScripts/all/removeForceDownload/index.ts", + "./contentScripts/all/replaceBreadcrumbCourseName/index.ts", + "./contentScripts/all/replaceNavigationCourseName/index.ts" ] }, // dashboard From 255c4de5727c617f02c706f632220c4b96591130 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 19:47:54 +0900 Subject: [PATCH 38/53] feat: add function to save courses on my courses page --- .../dashboard/readAndStoreCourses/index.ts | 16 +++++++++++++++- src/manifest.jsonc | 10 ++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts index b799eb5..592be39 100644 --- a/src/contentScripts/dashboard/readAndStoreCourses/index.ts +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -12,6 +12,17 @@ const readAndStoreCoureses = function () { const myOverview = document.querySelector('*[data-block="myoverview"]'); if (!myOverview) return; + const pagenationDropdownItem = myOverview.querySelector([ + '*[data-region="courses-view"]', + '*[data-region="paging-control-limit-container"]', + 'a[data-limit="0"]', + ].join(" ")); + if (!pagenationDropdownItem) return; + if (pagenationDropdownItem.getAttribute("aria-current") !== "true") { + pagenationDropdownItem.click(); + return; + } + const links = Array.from(myOverview.querySelectorAll( '*[data-region="courses-view"] ul a.coursename', )) as HTMLAnchorElement[]; @@ -29,8 +40,11 @@ const readAndStoreCoureses = function () { } }).filter((course) => course !== null); + const actionType = location.pathname === "/moodle40a/my/courses.php" + ? "saveCourses" + : "mergeAndSaveCourses"; reduceAndSaveCourses({ - type: "mergeAndSaveCourses", + type: actionType, payload: { courses: coursesJson, }, diff --git a/src/manifest.jsonc b/src/manifest.jsonc index b56fb9f..435592a 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -38,6 +38,16 @@ ], "css": ["./contentScripts/dashboard/main.scss"] }, + // my course config page + { + "matches": [ + "https://cms7.ict.nitech.ac.jp/moodle40a/my/courses.php" + ], + "js": [ + // this is **a special case** to use script in other directory + "./contentScripts/dashboard/readAndStoreCourses/index.ts" + ] + }, // scorm page { "matches": [ From d4a903b80dd69077bf4c8283ea10126baffa96e7 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 19:59:23 +0900 Subject: [PATCH 39/53] feat: make some functions to one-time script --- .../common/mutationObserverCallback.ts | 12 ++++++++++-- .../dashboard/readAndStoreCourses/index.ts | 2 ++ src/contentScripts/scorm/autoCollapseToc/index.ts | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/contentScripts/common/mutationObserverCallback.ts b/src/contentScripts/common/mutationObserverCallback.ts index c34c417..320b639 100644 --- a/src/contentScripts/common/mutationObserverCallback.ts +++ b/src/contentScripts/common/mutationObserverCallback.ts @@ -6,14 +6,22 @@ export type RegisterMutationObserverCallbackOptions = { debounceTimeout?: number; }; +/** + * Registers a mutation observer with a specified callback function that + * triggers when observed mutations occur. The callback function can return + * `true` to automatically disconnect the observer after being called. + * Debounces the callback execution with a specified timeout to prevent frequent calls. + */ export const registerMutationObserverCallback = function ( - callback: MutationCallback, + callback: (...args: Parameters) => boolean | void, options: RegisterMutationObserverCallbackOptions, ) { const debounceTimeout = options?.debounceTimeout ?? 500; const observer = new MutationObserver( - debounceCallback(callback, debounceTimeout), + debounceCallback((...args) => { + if (callback(...args)) observer.disconnect(); + }, debounceTimeout), ); observer.observe(options.rootElement, options.observerOptions); }; diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts index 592be39..8794c58 100644 --- a/src/contentScripts/dashboard/readAndStoreCourses/index.ts +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -49,6 +49,8 @@ const readAndStoreCoureses = function () { courses: coursesJson, }, }); + + return true; }; const main = function () { diff --git a/src/contentScripts/scorm/autoCollapseToc/index.ts b/src/contentScripts/scorm/autoCollapseToc/index.ts index 4ef76ce..1376ad7 100644 --- a/src/contentScripts/scorm/autoCollapseToc/index.ts +++ b/src/contentScripts/scorm/autoCollapseToc/index.ts @@ -13,6 +13,8 @@ const collapseToc = function () { // do not toggle toggle class .disabled directly becausemoodle handles other // elements at the same time when the button is pressed toggleCollapseButton.click(); + + return true; }; const main = async function () { From 926b6c774a6d6693d55ace75726c48e19cf271f9 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 20:00:12 +0900 Subject: [PATCH 40/53] fix: fix type definition of preferences --- src/common/model/preferences.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/model/preferences.ts b/src/common/model/preferences.ts index 2b27259..10e5495 100644 --- a/src/common/model/preferences.ts +++ b/src/common/model/preferences.ts @@ -19,7 +19,7 @@ export type Preferences = { }; // video page - scormCollapseToc: { + scormAutoCollapseToc: { enabled: boolean; }; scormAutoPlay: { From 9bc8aba82743a385846b816267c09bd0401c28dc Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 23:45:59 +0900 Subject: [PATCH 41/53] feat: add option page --- src/common/newStorage/preferences/action.ts | 6 +- src/common/newStorage/preferences/index.ts | 5 +- src/common/newStorage/preferences/reducer.ts | 2 +- src/manifest.jsonc | 6 +- src/options/app/App.tsx | 57 ------- src/options/app/FeatureOptions.tsx | 48 ------ src/options/app/ToggleBox.tsx | 32 ---- src/options/app/optionText.json | 22 --- src/options/components/EditPreferences.tsx | 147 +++++++++++++++++++ src/options/hooks/usePreferences.ts | 117 +++++++++++++++ src/options/{options.html => index.html} | 16 +- src/options/index.scss | 71 +++++++++ src/options/index.ts | 13 ++ src/options/options.scss | 119 --------------- src/options/options.ts | 9 -- 15 files changed, 365 insertions(+), 305 deletions(-) delete mode 100644 src/options/app/App.tsx delete mode 100644 src/options/app/FeatureOptions.tsx delete mode 100644 src/options/app/ToggleBox.tsx delete mode 100644 src/options/app/optionText.json create mode 100644 src/options/components/EditPreferences.tsx create mode 100644 src/options/hooks/usePreferences.ts rename src/options/{options.html => index.html} (56%) create mode 100644 src/options/index.scss create mode 100644 src/options/index.ts delete mode 100644 src/options/options.scss delete mode 100644 src/options/options.ts diff --git a/src/common/newStorage/preferences/action.ts b/src/common/newStorage/preferences/action.ts index c469b02..6e164c4 100644 --- a/src/common/newStorage/preferences/action.ts +++ b/src/common/newStorage/preferences/action.ts @@ -33,8 +33,8 @@ export type PatchDashboardQuickCourseLinksAction = { }; }; -export type PatchScormCollapseTocAction = { - type: "patchScormCollapseToc"; +export type PatchScormAutoCollapseTocAction = { + type: "patchScormAutoCollapseToc"; payload: { enabled?: boolean; }; @@ -60,6 +60,6 @@ export type PreferencesAction = | PatchReplaceNavigationCourseNameAction | PatchDashboardEventsCountdownAction | PatchDashboardQuickCourseLinksAction - | PatchScormCollapseTocAction + | PatchScormAutoCollapseTocAction | PatchScormAutoPlayAction | PatchLoginAutoSubmitAction; diff --git a/src/common/newStorage/preferences/index.ts b/src/common/newStorage/preferences/index.ts index 13a18c0..e2fb3b3 100644 --- a/src/common/newStorage/preferences/index.ts +++ b/src/common/newStorage/preferences/index.ts @@ -46,11 +46,12 @@ export const getPreferences = async function (): Promise { export const reduceAndSavePreferences = async function ( action: PreferencesAction, -): Promise { +): Promise { cachedPreferences = preferencesReducer(await getPreferences(), action); - return storage.set( + await storage.set( "preferences", cachedPreferences, storageArea, ); + return cachedPreferences; }; diff --git a/src/common/newStorage/preferences/reducer.ts b/src/common/newStorage/preferences/reducer.ts index 57e7dd7..1b97440 100644 --- a/src/common/newStorage/preferences/reducer.ts +++ b/src/common/newStorage/preferences/reducer.ts @@ -47,7 +47,7 @@ export const preferencesReducer = function ( ...payload, }, }; - } else if (action.type === "patchScormCollapseToc") { + } else if (action.type === "patchScormAutoCollapseToc") { return { ...preferences, scormAutoCollapseToc: { diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 435592a..26ee639 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -70,13 +70,13 @@ ], "options_ui": { - "page": "./options/options.html", + "page": "./options/index.html", "open_in_tab": true, "browser_style": true, // this field is not exist in the manifest file definition // /added for the build process - "js": ["./options/options.ts"], - "css": ["./options/options.scss"] + "js": ["./options/index.ts"], + "css": ["./options/index.scss"] }, "content_security_policy": { diff --git a/src/options/app/App.tsx b/src/options/app/App.tsx deleted file mode 100644 index c34c90b..0000000 --- a/src/options/app/App.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from "preact"; -import * as hooks from "preact/hooks"; -// @deno-types=npm:@types/lodash -import * as lodash from "lodash"; -import FeatureOptions from "./FeatureOptions.tsx"; - -import { - getOptions, - storeOptionsByMerge, -} from "../../common/storage/options.ts"; -import { FeatureOption, Options } from "../../common/options.ts"; - -interface AppProps { - initialOptions: Options; -} - -const App = (props: AppProps) => { - const [options, setOptionsRaw] = hooks.useState(props.initialOptions); - - hooks.useEffect(() => { - getOptions().then(setOptionsRaw); - }); - const setFeatureOptions = ( - key: keyof Options["features"], - value: Partial, - ) => { - const newPartialOption = lodash.defaultsDeep( - value, - options.features[key], - ) as FeatureOption; - const newOptions = lodash.merge(options, { - features: { [key]: newPartialOption }, - }) as Options; - setOptionsRaw(newOptions); - storeOptionsByMerge(newOptions); - }; - - return ( - <> - - - ); -}; - -const renderApp = function (targetElement: HTMLElement) { - getOptions().then((options) => { - preact.render(, targetElement); - }); -}; - -export { renderApp }; diff --git a/src/options/app/FeatureOptions.tsx b/src/options/app/FeatureOptions.tsx deleted file mode 100644 index ebcdfe6..0000000 --- a/src/options/app/FeatureOptions.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from "preact"; -import { FeatureOption, Options } from "../../common/options.ts"; -import optionText from "./optionText.json" assert { type: "json" }; - -import ToggleBox from "./ToggleBox.tsx"; - -interface FeatureOptionsProps { - options: Options["features"]; - setOptions: ( - key: keyof Options["features"], - value: Partial, - ) => void; -} - -const FeatureOptions = (props: FeatureOptionsProps) => { - const featureUniqueNames = Object.keys(props.options) - .filter(( - uniqName, - ) => (uniqName in optionText.features)) as (keyof typeof optionText[ - "features" - ])[]; - - return ( -
    -

    機能設定

    - -
      - {featureUniqueNames.map((uniqueName) => ( - { - props.setOptions(uniqueName, { - enabled: !props.options[uniqueName].enabled, - }); - }} - /> - ))} -
    -
    - ); -}; - -export default FeatureOptions; diff --git a/src/options/app/ToggleBox.tsx b/src/options/app/ToggleBox.tsx deleted file mode 100644 index 26716d2..0000000 --- a/src/options/app/ToggleBox.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** @jsxImportSource preact */ - -// @deno-types="preact/types" -import * as preact from "preact"; - -interface ToggleBoxProps { - uniqueId: string; - labelText: string; - checked: boolean; - onClick: () => void; -} - -const ToggleBox = (props: ToggleBoxProps) => ( -
  • -
    - -
    -
    - -
    -
  • -); - -export default ToggleBox; diff --git a/src/options/app/optionText.json b/src/options/app/optionText.json deleted file mode 100644 index 2b4d304..0000000 --- a/src/options/app/optionText.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "features": { - "all-pages-remove-force-download": { - "_category": "強制ダウンロードリンクの非強制化" - }, - "all-pages-replace-header-course-name": { - "_category": "ヘッダーの短縮コース名の置き換え" - }, - "all-pages-replace-navigation-texts": { - "_category": "ナビゲーションの短縮コース名の置き換え" - }, - "dashboard-events-countdown": { - "_category": "ダッシュボードの直近イベントにカウントダウンを表示" - }, - "dashboard-quick-course-view": { - "_category": "ダッシュボードにコースへのショートカットを追加" - }, - "scorm-collapse-toc": { - "_category": "動画の目次を折りたたむ" - } - } -} diff --git a/src/options/components/EditPreferences.tsx b/src/options/components/EditPreferences.tsx new file mode 100644 index 0000000..bf07a3a --- /dev/null +++ b/src/options/components/EditPreferences.tsx @@ -0,0 +1,147 @@ +import * as preact from "preact"; + +import { usePreferences } from "../hooks/usePreferences.ts"; + +const PreferencesItem = function ( + props: { + checked: boolean; + onClick: preact.JSX.MouseEventHandler; + children: preact.ComponentChildren; + }, +) { + return ( +
  • + +
  • + ); +}; + +export const EditPreferences = function () { + const preferences = usePreferences(); + + if (!preferences) { + return
    loading...
    ; + } + + return ( + <> +
    +

    全てのページ

    +
      + + preferences.setRemoveForceDownload({ + enabled: e.currentTarget.checked, + })} + > +

      + リンクの強制ダウンロードを無効化する +

      +

      + {/* deno-fmt-ignore */} + moodle では PDF などのリンクに強制的にダウンロードさせるように設定することができます。 + この設定により PDF などがブラウザの規定の動作で開かずダウンロードされてしまうのを防ぎます。 +

      +
      + + preferences.setReplaceBreadcrumbCourseName({ + enabled: e.currentTarget.checked + })} + > +

      + ヘッダーナビゲーションのコース名をわかりやすい名前にする +

      +

      + {/* deno-fmt-ignore */} + 各ページのヘッダーにある現在のページの位置を表すリンクのテキストをわかりやすい名前に置き換えます。 +

      +
      + + preferences.setReplaceNavigationCourseName({ + enabled: e.currentTarget.checked + })} + > +

      + ナビゲーションのコース名をわかりやすい名前にする +

      +

      + {/* deno-fmt-ignore */} + 各ページにあるナビゲーションメニューにおいて、コースリンクのテキストをわかりやすい名前に置き換えます。 +

      +
      +
    +
    + +
    +

    ダッシュボード

    +
      + + preferences.setDashboardEventsCountdown({ + enabled: e.currentTarget.checked + })} + > +

      + 直近イベントに残り時間を表示する +

      +

      + {/* deno-fmt-ignore */} + ダッシュボードにある課題などが表示される「直近イベント」において、表示されているイベントの残り時間を表示します。 + この残り時間はリアルタイムでカウントダウンされます (表示中に終了時刻が変わった場合はページの再読み込みが必要です)。 +

      +
      + + preferences.setDashboardQuickCourseLinks({ + enabled: e.currentTarget.checked + })} + > +

      + 開講中のコースにアクセスしやすくするメニューを表示する +

      +

      + {/* deno-fmt-ignore */} + ダッシュボードに現在開講しているコースを曜日・時間順に表示するクイックメニューを表示します。 +

      +
      +
    +
    + +
    +

    動画ページ

    +
      + + preferences.setScormAutoCollapseToc({ + enabled: e.currentTarget.checked + })} + > +

      + ページを開いたときに目次を折りたたむ +

      +

      + {/* deno-fmt-ignore */} + 動画ページに表示される目次をデフォルトで折りたたみます。 + 画面上により大きく動画を表示することができます。 +

      +
      +
    +
    + + ); +}; diff --git a/src/options/hooks/usePreferences.ts b/src/options/hooks/usePreferences.ts new file mode 100644 index 0000000..4695625 --- /dev/null +++ b/src/options/hooks/usePreferences.ts @@ -0,0 +1,117 @@ +import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; + +import type { Preferences } from "~/common/model/preferences.ts"; +import type { PreferencesAction } from "~/common/newStorage/preferences/action.ts"; +import { + getPreferences, + reduceAndSavePreferences, +} from "~/common/newStorage/preferences/index.ts"; + +type Payload = + (PreferencesAction & { type: T })["payload"]; + +export const usePreferences = function () { + const [preferences, setPreferences] = useState(null); + + useEffect(() => { + getPreferences().then(setPreferences); + }, []); + + if (!preferences) return null; + + const createReduceCallback = function ( + type: T, + ) { + return async (payload: Payload) => { + const newPreferences = await reduceAndSavePreferences({ type, payload }); + setPreferences(newPreferences); + }; + }; + + const removeForceDownload = useMemo( + () => preferences.removeForceDownload, + [preferences], + ); + const setRemoveForceDownload = useCallback( + createReduceCallback("patchRemoveForceDownload"), + [preferences], + ); + + const replaceBreadcrumbCourseName = useMemo( + () => preferences.replaceBreadcrumbCourseName, + [preferences], + ); + const setReplaceBreadcrumbCourseName = useCallback( + createReduceCallback("patchReplaceBreadcrumbCourseName"), + [preferences], + ); + + const replaceNavigationCourseName = useMemo( + () => preferences.replaceNavigationCourseName, + [preferences], + ); + const setReplaceNavigationCourseName = useCallback( + createReduceCallback("patchReplaceNavigationCourseName"), + [preferences], + ); + + const dashboardEventsCountdown = useMemo( + () => preferences.dashboardEventsCountdown, + [preferences], + ); + const setDashboardEventsCountdown = useCallback( + createReduceCallback("patchDashboardEventsCountdown"), + [preferences], + ); + + const dashboardQuickCourseLinks = useMemo( + () => preferences.dashboardQuickCourseLinks, + [preferences], + ); + const setDashboardQuickCourseLinks = useCallback( + createReduceCallback("patchDashboardQuickCourseLinks"), + [preferences], + ); + + const scormAutoCollapseToc = useMemo( + () => preferences.scormAutoCollapseToc, + [preferences], + ); + const setScormAutoCollapseToc = useCallback( + createReduceCallback("patchScormAutoCollapseToc"), + [preferences], + ); + + const scormAutoPlay = useMemo(() => preferences.scormAutoPlay, [preferences]); + const setScormAutoPlay = useCallback( + createReduceCallback("patchScormAutoPlay"), + [preferences], + ); + + const loginAutoSubmit = useMemo(() => preferences.loginAutoSubmit, [ + preferences, + ]); + const setLoginAutoSubmit = useCallback( + createReduceCallback("patchLoginAutoSubmit"), + [preferences], + ); + + return { + removeForceDownload, + setRemoveForceDownload, + replaceBreadcrumbCourseName, + setReplaceBreadcrumbCourseName, + replaceNavigationCourseName, + setReplaceNavigationCourseName, + dashboardEventsCountdown, + setDashboardEventsCountdown, + dashboardQuickCourseLinks, + setDashboardQuickCourseLinks, + scormAutoCollapseToc, + setScormAutoCollapseToc, + scormAutoPlay, + setScormAutoPlay, + loginAutoSubmit, + setLoginAutoSubmit, + }; +}; diff --git a/src/options/options.html b/src/options/index.html similarity index 56% rename from src/options/options.html rename to src/options/index.html index 8778655..a0553b8 100644 --- a/src/options/options.html +++ b/src/options/index.html @@ -4,19 +4,17 @@ + 設定 - NITech Moodle Extension (40a) - - + + +
    -
    -

    設定

    -
    - NITech Moodle Extension (40a) -
    -
    -
    +

    設定 - NITech Moodle Extension (40a)

    + +
    diff --git a/src/options/index.scss b/src/options/index.scss new file mode 100644 index 0000000..4217d13 --- /dev/null +++ b/src/options/index.scss @@ -0,0 +1,71 @@ +html, body { + margin: 0; + padding: 0; + color: #404040; + + line-height: 1.5; + font-weight: 400; + font-size: 100%; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +main { + max-width: 60em; + margin: 0 auto; + padding: 1em; +} + +#preferences { + .preference-group+.preference-group { + margin-top: 1em; + } + + .preference-group { + h3 { + margin: 0; + padding: 0.25em 0; + } + + ul { + margin: 0; + padding: 0; + } + + li { + list-style: none; + + &+li { + border-top: 1px solid #d0d0d0; + } + + &:hover { + background-color: #f8f8f8; + } + + &:active { + background-color: #f0f0f0; + } + + label { + display: flex; + padding: 0.5em 0.5em; + gap: 0.5em; + + > *:first-child { + flex-grow: 1; + } + + p { + &.description { + font-size: 0.8em; + color: #606060; + } + } + } + } + } +} diff --git a/src/options/index.ts b/src/options/index.ts new file mode 100644 index 0000000..edc3803 --- /dev/null +++ b/src/options/index.ts @@ -0,0 +1,13 @@ +import * as preact from "preact"; + +import { EditPreferences } from "./components/EditPreferences.tsx"; + +globalThis.addEventListener("DOMContentLoaded", () => { + const root = document.getElementById("preferences"); + if (!root) throw Error("element #preferences is not found"); + + preact.render( + preact.createElement(EditPreferences, null), + root, + ); +}); diff --git a/src/options/options.scss b/src/options/options.scss deleted file mode 100644 index fef47d4..0000000 --- a/src/options/options.scss +++ /dev/null @@ -1,119 +0,0 @@ -$c-bg: #ffffff; -$c-bg-hover: #f0f0f0; -$c-bg-active: #e0e0e0; -$c-separator: #c0c0c0; -$c-toggle-bg-off: #bdc1c6; -$c-toggle-bg-on: #8bb8f2; -$c-toggle-knob-off: #ffffff; -$c-toggle-knob-on: #1a73e8; - -body { - margin: 0; - padding: 0; - font-size: 14px; - background-color: $c-bg; - - > main { - max-width: 60em; - min-height: 100vh; - margin: 0 auto; - padding: 1em 1em; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); - } -} - -header { - > * { - display: inline-block; - margin-right: 0.5em; - } -} - -main { - ul { - list-style: none; - margin: 0; - padding: 0; - - li { - display: flex; - gap: 1em; - - &:hover { - background-color: $c-bg-hover; - } - - &:active { - background-color: $c-bg-active; - } - - + li { - border-top: 1px solid $c-separator; - } - - label { - cursor: pointer; - } - - div.label { - flex-grow: 1; - - label { - padding: 0.75em 1em; - display: block; - height: 100%; - } - } - - div.control { - display: flex; - align-items: center; - - input { - display: none; - - $sw-width: 2em; - $sw-height: 0.8em; - $sw-knob-size: 1.2em; - - + label { - margin-right: 1em; - display: block; - width: $sw-width; - height: $sw-height; - border-radius: 0.5em; - background-color: $c-toggle-bg-off; - transition: all 300ms 0s ease; - - &::after { - display: block; - position: absolute; - width: $sw-knob-size; - height: $sw-knob-size; - content: ""; - border-radius: 50%; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); - transform: translate( - -$sw-knob-size / 2, - ($sw-height - $sw-knob-size) / 2 - ); - background-color: $c-toggle-knob-off; - transition: all 300ms 0s ease; - } - } - &:checked + label { - background-color: $c-toggle-bg-on; - - &::after { - transform: translate( - $sw-width - $sw-knob-size / 2, - ($sw-height - $sw-knob-size) / 2 - ); - background-color: $c-toggle-knob-on; - } - } - } - } - } - } -} diff --git a/src/options/options.ts b/src/options/options.ts deleted file mode 100644 index 5601945..0000000 --- a/src/options/options.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { renderApp } from "./app/App.tsx"; - -globalThis.addEventListener("load", () => { - const elAppRoot = document.getElementById("app_root"); - if (!elAppRoot) { - throw Error(`element #app_root is not found`); - } - renderApp(elAppRoot); -}); From 3e81708608a01ef52c5309ad78f255b9d205502d Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 23:47:15 +0900 Subject: [PATCH 42/53] chore: remove unused files --- src/common/course.ts | 113 ---------------------------------- src/common/options.ts | 18 ------ src/common/storage/course.ts | 24 -------- src/common/storage/options.ts | 20 ------ src/common/storage/storage.ts | 59 ------------------ 5 files changed, 234 deletions(-) delete mode 100644 src/common/course.ts delete mode 100644 src/common/options.ts delete mode 100644 src/common/storage/course.ts delete mode 100644 src/common/storage/options.ts delete mode 100644 src/common/storage/storage.ts diff --git a/src/common/course.ts b/src/common/course.ts deleted file mode 100644 index 3b31221..0000000 --- a/src/common/course.ts +++ /dev/null @@ -1,113 +0,0 @@ -interface RegularLectureCourse { - type: "regular-lecture"; - /** 講義名 */ - name: string; - /** moodle での表示名 */ - fullName: string; - /** 開講する年度 */ - fullYear: number; - /** 第一部 / 第二部 / 大学院 */ - curriculumPart: 1 | 2 | 4; - /** 講義番号 */ - code: number; - /** 開講する時期 (前期 / 後期 / 第1クォーター など) */ - semester: "1/2" | "2/2" | "1/1" | "1/4" | "2/4" | "3/4" | "4/4" | "unfixed"; - /** 開講する曜日 */ - weekOfDay: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; - /** 開口する時間 (コマ) [`start`, `end`] */ - period: [number, number]; - /** moodle ページのID */ - pageId: number; - /** moodle のコース省略名 */ - shortName: string; -} - -interface SpecialCourse { - type: "special"; - /** 講義名 */ - name: string; - /** moodle での表示名 */ - fullName: string; - /** 開講する年度 */ - fullYear?: number; - /** moodle ページのID */ - pageId: number; - /** moodle のコース省略名 */ - shortName: string; -} - -type Course = RegularLectureCourse | SpecialCourse; - -const semesterToTextMap = { - "1/2": "前期", - "2/2": "後期", - "1/1": "通年", - "1/4": "第1クォーター", - "2/4": "第2クォーター", - "3/4": "第3クォーター", - "4/4": "第4クォーター", - "unfixed": "未定", -} as const; - -const textToSemesterMap = { - "前期": "1/2", - "後期": "2/2", - "通年": "1/1", - "第1クォーター": "1/4", - "第2クォーター": "2/4", - "第3クォーター": "3/4", - "第4クォーター": "4/4", - "未定": "unfixed", -} as const; - -const semesterOrdering = { - "1/2": 1, - "2/2": 2, - "1/1": 3, - "1/4": 4, - "2/4": 5, - "3/4": 6, - "4/4": 7, - "unfixed": 8, -} as const; - -const weekOfDayToTextMap = { - "sun": "日曜", - "mon": "月曜", - "tue": "火曜", - "wed": "水曜", - "thu": "木曜", - "fri": "金曜", - "sat": "土曜", -} as const; - -const textToWeekOfDayMap = { - "日曜": "sun", - "月曜": "mon", - "火曜": "tue", - "水曜": "wed", - "木曜": "thu", - "金曜": "fri", - "土曜": "sat", -} as const; - -const weekOfDayOrdering = { - "sun": 6, - "mon": 0, - "tue": 1, - "wed": 2, - "thu": 3, - "fri": 4, - "sat": 5, -} as const; - -export type { Course, RegularLectureCourse, SpecialCourse }; - -export { - semesterOrdering, - semesterToTextMap, - textToSemesterMap, - textToWeekOfDayMap, - weekOfDayOrdering, - weekOfDayToTextMap, -}; diff --git a/src/common/options.ts b/src/common/options.ts deleted file mode 100644 index 9995b50..0000000 --- a/src/common/options.ts +++ /dev/null @@ -1,18 +0,0 @@ -interface FeatureOption { - enabled: boolean; - [key: string]: unknown; -} - -interface Options { - features: { - [key: string]: FeatureOption; - }; -} - -const defaultFeatureOption = { - enabled: true, -}; - -export type { FeatureOption, Options }; - -export { defaultFeatureOption }; diff --git a/src/common/storage/course.ts b/src/common/storage/course.ts deleted file mode 100644 index 2379b18..0000000 --- a/src/common/storage/course.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as storage from "./storage.ts"; -import { Course } from "../course.ts"; - -const storageCourseKey = "courses"; - -const mergeCourseList = function (value: Course[], source: Course[]) { - const map = new Map(); - source.forEach((course) => map.set(course.fullName, course)); - value.forEach((course) => map.set(course.fullName, course)); - - return Array.from(map.values()); -}; - -const getCourses = async function () { - return await storage.get(storageCourseKey); -}; - -const storeCourseByMerge = async function (courses: Course[]) { - await storage.update(storageCourseKey, (prevCourses) => { - return mergeCourseList(courses, prevCourses); - }); -}; - -export { getCourses, mergeCourseList, storeCourseByMerge }; diff --git a/src/common/storage/options.ts b/src/common/storage/options.ts deleted file mode 100644 index c00a17b..0000000 --- a/src/common/storage/options.ts +++ /dev/null @@ -1,20 +0,0 @@ -// @deno-types=npm:@types/lodash -import * as lodash from "lodash"; -import * as storage from "./storage.ts"; -import { Options } from "../options.ts"; - -const storageOptionsKey = "options"; - -const getOptions = async function () { - return await storage.get(storageOptionsKey); -}; - -const storeOptionsByMerge = async function (options: Partial) { - await storage.update(storageOptionsKey, (prevOptions) => { - return lodash.merge(prevOptions, options); - }); -}; - -export type { Options }; - -export { getOptions, storeOptionsByMerge }; diff --git a/src/common/storage/storage.ts b/src/common/storage/storage.ts deleted file mode 100644 index 0412914..0000000 --- a/src/common/storage/storage.ts +++ /dev/null @@ -1,59 +0,0 @@ -import browser from "webextension-polyfill"; -// @deno-types=npm:@types/lodash -import * as lodash from "lodash"; -import type { Course } from "../course.ts"; -import { Options } from "../options.ts"; - -/** `storage["local" | "managed" | "sync"]` に保存されている値の型 */ -interface StoredValue { - courses: Course[]; - options: Options; -} - -type StorageArea = "local" | "managed" | "sync"; - -const defaultValue: StoredValue = { - courses: [], - options: { - features: {}, - }, -}; - -/** ストレージから値を取得 */ -const get = async function ( - key: Key, - storageArea: StorageArea = "local", -): Promise { - const value = await browser.storage[storageArea].get(key) as Partial< - StoredValue - >; - - return lodash.defaultsDeep(value, defaultValue)[key]; -}; - -/** 最後に作成された (Promise-chain の最後尾の) `Promise` */ -let lastPromise: Promise = Promise.resolve(); -/** - * ストレージに保存されている値を更新する; - * 複数に同時の非同期更新が起こらないことを保証する - */ -const update = async function ( - key: Key, - reducer: (prev: StoredValue[Key]) => StoredValue[Key], - storageArea: StorageArea = "local", -) { - const storeValue = () => - new Promise((resolve) => { - get(key).then((prevValue) => { - browser.storage[storageArea].set({ - [key]: reducer(lodash.defaultsDeep(prevValue, defaultValue[key])), - }); - resolve(); - }); - }); - - lastPromise = lastPromise.then(storeValue); - await lastPromise; -}; - -export { get, update }; From f656a602dcbb953175049cd15ee21b0a81b6e0c4 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Fri, 1 Nov 2024 23:51:43 +0900 Subject: [PATCH 43/53] style: change style of entry point --- src/contentScripts/all/removeForceDownload/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/contentScripts/all/removeForceDownload/index.ts b/src/contentScripts/all/removeForceDownload/index.ts index c721826..1e50e2a 100644 --- a/src/contentScripts/all/removeForceDownload/index.ts +++ b/src/contentScripts/all/removeForceDownload/index.ts @@ -31,7 +31,7 @@ const forceDownloadRemoved = function (url: URL): URL { return removed; }; -(async () => { +const main = async () => { const preferences = await getPreferences(); if (!preferences.removeForceDownload.enabled) return; @@ -47,4 +47,6 @@ const forceDownloadRemoved = function (url: URL): URL { observerOptions: { childList: true, subtree: true }, }, ); -})(); +}; + +main(); From 6c93bafaa4b19fcad68a4ab30fc2e0d1b53b59ef Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Sat, 2 Nov 2024 00:12:38 +0900 Subject: [PATCH 44/53] chore: remove unused dependencies --- deno.json | 1 - 1 file changed, 1 deletion(-) diff --git a/deno.json b/deno.json index 10f89d0..93fbcc5 100644 --- a/deno.json +++ b/deno.json @@ -46,7 +46,6 @@ "esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.0", "esbuild-plugin-debug-switch": "jsr:@tsukina-7mochi/esbuild-plugin-debug-switch@^0.2.0", "esbuild-plugin-sass": "jsr:@tsukina-7mochi/esbuild-plugin-sass@^0.1.1", - "lodash": "https://deno.land/x/lodash@4.17.19/lodash.js", "preact": "npm:preact@^10.24.3", "preact/compat": "npm:preact@^10.24.3/compat", "react/jsx-runtime": "npm:preact@^10.24.3/jsx-runtime", From a43174310752b73708f415453e11711002816e1b Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Sat, 2 Nov 2024 00:16:05 +0900 Subject: [PATCH 45/53] chore: move newStorage -> storage --- src/common/{newStorage => storage}/courses/actions.ts | 0 src/common/{newStorage => storage}/courses/index.ts | 0 src/common/{newStorage => storage}/courses/reducer.ts | 0 src/common/{newStorage => storage}/preferences/action.ts | 0 src/common/{newStorage => storage}/preferences/index.ts | 0 src/common/{newStorage => storage}/preferences/reducer.ts | 0 src/common/{newStorage => storage}/storage.ts | 0 src/contentScripts/all/removeForceDownload/index.ts | 2 +- src/contentScripts/all/replaceBreadcrumbCourseName/index.ts | 4 ++-- src/contentScripts/all/replaceNavigationCourseName/index.ts | 4 ++-- src/contentScripts/dashboard/eventCountdown/index.ts | 2 +- .../dashboard/quickCourseLinks/hooks/useCourses.ts | 2 +- src/contentScripts/dashboard/quickCourseLinks/index.ts | 2 +- src/contentScripts/dashboard/readAndStoreCourses/index.ts | 2 +- src/contentScripts/scorm/autoCollapseToc/index.ts | 2 +- src/options/hooks/usePreferences.ts | 4 ++-- 16 files changed, 12 insertions(+), 12 deletions(-) rename src/common/{newStorage => storage}/courses/actions.ts (100%) rename src/common/{newStorage => storage}/courses/index.ts (100%) rename src/common/{newStorage => storage}/courses/reducer.ts (100%) rename src/common/{newStorage => storage}/preferences/action.ts (100%) rename src/common/{newStorage => storage}/preferences/index.ts (100%) rename src/common/{newStorage => storage}/preferences/reducer.ts (100%) rename src/common/{newStorage => storage}/storage.ts (100%) diff --git a/src/common/newStorage/courses/actions.ts b/src/common/storage/courses/actions.ts similarity index 100% rename from src/common/newStorage/courses/actions.ts rename to src/common/storage/courses/actions.ts diff --git a/src/common/newStorage/courses/index.ts b/src/common/storage/courses/index.ts similarity index 100% rename from src/common/newStorage/courses/index.ts rename to src/common/storage/courses/index.ts diff --git a/src/common/newStorage/courses/reducer.ts b/src/common/storage/courses/reducer.ts similarity index 100% rename from src/common/newStorage/courses/reducer.ts rename to src/common/storage/courses/reducer.ts diff --git a/src/common/newStorage/preferences/action.ts b/src/common/storage/preferences/action.ts similarity index 100% rename from src/common/newStorage/preferences/action.ts rename to src/common/storage/preferences/action.ts diff --git a/src/common/newStorage/preferences/index.ts b/src/common/storage/preferences/index.ts similarity index 100% rename from src/common/newStorage/preferences/index.ts rename to src/common/storage/preferences/index.ts diff --git a/src/common/newStorage/preferences/reducer.ts b/src/common/storage/preferences/reducer.ts similarity index 100% rename from src/common/newStorage/preferences/reducer.ts rename to src/common/storage/preferences/reducer.ts diff --git a/src/common/newStorage/storage.ts b/src/common/storage/storage.ts similarity index 100% rename from src/common/newStorage/storage.ts rename to src/common/storage/storage.ts diff --git a/src/contentScripts/all/removeForceDownload/index.ts b/src/contentScripts/all/removeForceDownload/index.ts index 1e50e2a..a96b56d 100644 --- a/src/contentScripts/all/removeForceDownload/index.ts +++ b/src/contentScripts/all/removeForceDownload/index.ts @@ -1,6 +1,6 @@ import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { getPreferences } from "~/common/storage/preferences/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const parseUrl = function (url: string): URL | null { diff --git a/src/contentScripts/all/replaceBreadcrumbCourseName/index.ts b/src/contentScripts/all/replaceBreadcrumbCourseName/index.ts index 077a030..acd6443 100644 --- a/src/contentScripts/all/replaceBreadcrumbCourseName/index.ts +++ b/src/contentScripts/all/replaceBreadcrumbCourseName/index.ts @@ -1,7 +1,7 @@ import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from "~/common/newStorage/preferences/index.ts"; -import { getCourses } from "~/common/newStorage/courses/index.ts"; +import { getPreferences } from "~/common/storage/preferences/index.ts"; +import { getCourses } from "~/common/storage/courses/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const replaceBreadcrumbCourseName = function ( diff --git a/src/contentScripts/all/replaceNavigationCourseName/index.ts b/src/contentScripts/all/replaceNavigationCourseName/index.ts index 5654fcc..2a96bfa 100644 --- a/src/contentScripts/all/replaceNavigationCourseName/index.ts +++ b/src/contentScripts/all/replaceNavigationCourseName/index.ts @@ -1,7 +1,7 @@ import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from "~/common/newStorage/preferences/index.ts"; -import { getCourses } from "~/common/newStorage/courses/index.ts"; +import { getPreferences } from "~/common/storage/preferences/index.ts"; +import { getCourses } from "~/common/storage/courses/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const replaceNavigationCourseName = function ( diff --git a/src/contentScripts/dashboard/eventCountdown/index.ts b/src/contentScripts/dashboard/eventCountdown/index.ts index 83ccff5..3117d88 100644 --- a/src/contentScripts/dashboard/eventCountdown/index.ts +++ b/src/contentScripts/dashboard/eventCountdown/index.ts @@ -1,7 +1,7 @@ import * as preact from "preact"; import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { getPreferences } from "~/common/storage/preferences/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; import { Countdown } from "./components/countdown.tsx"; diff --git a/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts b/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts index 322b445..55efba6 100644 --- a/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts +++ b/src/contentScripts/dashboard/quickCourseLinks/hooks/useCourses.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "preact/hooks"; import type { Course } from "~/common/model/course.ts"; -import { getCourses } from "~/common/newStorage/courses/index.ts"; +import { getCourses } from "~/common/storage/courses/index.ts"; export const useCourses = function () { const [courses, setCourses] = useState(null); diff --git a/src/contentScripts/dashboard/quickCourseLinks/index.ts b/src/contentScripts/dashboard/quickCourseLinks/index.ts index abe12fa..7752def 100644 --- a/src/contentScripts/dashboard/quickCourseLinks/index.ts +++ b/src/contentScripts/dashboard/quickCourseLinks/index.ts @@ -1,7 +1,7 @@ import * as preact from "preact"; import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { getPreferences } from "~/common/storage/preferences/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; import { QuickCourseLinks } from "./components/quickCourseLinks.tsx"; diff --git a/src/contentScripts/dashboard/readAndStoreCourses/index.ts b/src/contentScripts/dashboard/readAndStoreCourses/index.ts index 8794c58..636e0c0 100644 --- a/src/contentScripts/dashboard/readAndStoreCourses/index.ts +++ b/src/contentScripts/dashboard/readAndStoreCourses/index.ts @@ -1,7 +1,7 @@ import { isDebug } from "esbuild-plugin-debug-switch"; import { Course } from "~/common/model/course.ts"; -import { reduceAndSaveCourses } from "~/common/newStorage/courses/index.ts"; +import { reduceAndSaveCourses } from "~/common/storage/courses/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const readAndStoreCoureses = function () { diff --git a/src/contentScripts/scorm/autoCollapseToc/index.ts b/src/contentScripts/scorm/autoCollapseToc/index.ts index 1376ad7..808174b 100644 --- a/src/contentScripts/scorm/autoCollapseToc/index.ts +++ b/src/contentScripts/scorm/autoCollapseToc/index.ts @@ -1,6 +1,6 @@ import { isDebug } from "esbuild-plugin-debug-switch"; -import { getPreferences } from "~/common/newStorage/preferences/index.ts"; +import { getPreferences } from "~/common/storage/preferences/index.ts"; import { registerMutationObserverCallback } from "~/contentScripts/common/mutationObserverCallback.ts"; const collapseToc = function () { diff --git a/src/options/hooks/usePreferences.ts b/src/options/hooks/usePreferences.ts index 4695625..6cffd05 100644 --- a/src/options/hooks/usePreferences.ts +++ b/src/options/hooks/usePreferences.ts @@ -1,11 +1,11 @@ import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import type { Preferences } from "~/common/model/preferences.ts"; -import type { PreferencesAction } from "~/common/newStorage/preferences/action.ts"; +import type { PreferencesAction } from "~/common/storage/preferences/action.ts"; import { getPreferences, reduceAndSavePreferences, -} from "~/common/newStorage/preferences/index.ts"; +} from "~/common/storage/preferences/index.ts"; type Payload = (PreferencesAction & { type: T })["payload"]; From 9e3144f45505e4dd819a45ee10651a405b2afab3 Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Sat, 2 Nov 2024 00:23:54 +0900 Subject: [PATCH 46/53] chore: bump version --- src/manifest.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 26ee639..7f606be 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -3,7 +3,7 @@ "manifest_version": 3, "name": "NITech Moodle Extension (40a)", "homepage_url": "https://github.com/nitech-create/nitech-moodle-extension-40a", - "version": "0.9.6", + "version": "0.10.0", // Recommended // "action": {}, From 2160f22e8166f784604d43ecd83d3eb2f9501d2c Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Tue, 5 Nov 2024 14:07:05 +0900 Subject: [PATCH 47/53] ci: update ci --- .github/workflows/check-version-increase.yml | 24 -------- .github/workflows/check-version-update.yml | 41 +++++++++++++ .github/workflows/create-tag-on-pr-merge.yml | 37 ++++-------- .github/workflows/lint-fmt-test.yml | 28 --------- .github/workflows/lint-format.yml | 46 +++++++++++++++ .github/workflows/manifest-version.yml | 41 +++++++++++++ .../release-attach-artifact-manual.yml | 58 ------------------- .github/workflows/release-attach-artifact.yml | 41 +++++-------- deno.json | 5 +- deno.lock | 10 +++- scripts/cache-all.sh | 10 ---- scripts/check-version-increase.sh | 25 -------- scripts/check-version.sh | 8 --- scripts/get-manifest-version.sh | 4 -- scripts/getManifestVersion.ts | 17 ++++++ scripts/setup-vscode.ts | 45 -------------- src/options/index.scss | 4 +- 17 files changed, 184 insertions(+), 260 deletions(-) delete mode 100644 .github/workflows/check-version-increase.yml create mode 100644 .github/workflows/check-version-update.yml delete mode 100644 .github/workflows/lint-fmt-test.yml create mode 100644 .github/workflows/lint-format.yml create mode 100644 .github/workflows/manifest-version.yml delete mode 100644 .github/workflows/release-attach-artifact-manual.yml delete mode 100644 scripts/cache-all.sh delete mode 100644 scripts/check-version-increase.sh delete mode 100644 scripts/check-version.sh delete mode 100644 scripts/get-manifest-version.sh create mode 100644 scripts/getManifestVersion.ts delete mode 100644 scripts/setup-vscode.ts diff --git a/.github/workflows/check-version-increase.yml b/.github/workflows/check-version-increase.yml deleted file mode 100644 index fa94670..0000000 --- a/.github/workflows/check-version-increase.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: check latest version - -on: - pull_request: - types: - - opened - - synchronize - branches: - - main - paths: - - "src/**" - -jobs: - lint-and-fmt: - runs-on: ubuntu-latest - timeout-minutes: 1 - - steps: - - uses: actions/checkout@v3 - - - name: Check version - env: - REPO_NAME: ${{ github.repository }} - run: bash scripts/check-version-increase.sh $REPO_NAME diff --git a/.github/workflows/check-version-update.yml b/.github/workflows/check-version-update.yml new file mode 100644 index 0000000..4918bb3 --- /dev/null +++ b/.github/workflows/check-version-update.yml @@ -0,0 +1,41 @@ +name: Check Version Increase + +on: + pull_request: + types: + - opened + - synchronize + branches: + - main + paths: + - "src/**" + +jobs: + get-main-version: + uses: ./.github/workflows/manifest-version.yml + with: + ref: refs/heads/main + checkout-ref: main + + get-current-version: + uses: ./.github/workflows/manifest-version.yml + with: + ref: ${{ github.event.pull_request.head.sha }} + + check-version: + runs-on: ubuntu-latest + needs: [get-main-version, get-current-version] + + steps: + - run: | + echo "current: ${{ needs.get-current-version.outputs.version }}" + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: compare versions + env: + MAIN_VERSION: ${{ needs.get-main-version.outputs.version }} + CURRENT_VERSION: ${{ needs.get-current-version.outputs.version }} + run: test "$MAIN_VERSION" != "$CURRENT_VERSION" diff --git a/.github/workflows/create-tag-on-pr-merge.yml b/.github/workflows/create-tag-on-pr-merge.yml index d7b400e..dab7c96 100644 --- a/.github/workflows/create-tag-on-pr-merge.yml +++ b/.github/workflows/create-tag-on-pr-merge.yml @@ -10,37 +10,22 @@ on: - "src/**" jobs: + get-version: + uses: ./.github/workflows/manifest-version.yml + with: + ref: ${{ github.event.pull_request.head.sha }} + create-tag: if: github.event.pull_request.merged == true runs-on: ubuntu-latest + needs: get-version timeout-minutes: 1 steps: - - uses: actions/checkout@v3 - - - name: Extract version - run: echo "VERSION=v$(sh scripts/get-manifest-version.sh)" >> $GITHUB_ENV - - - name: Get commit URL - env: - COMMITS_URL: ${{ github.event.pull_request.base.repo.commits_url }} - SHA: ${{ github.sha }} - run: echo "COMMIT_URL=$COMMITS_URL" | sed -e "s#{/sha}#/$SHA#" >> $GITHUB_ENV + - uses: actions/checkout@v4 - - name: Get commit message - run: | - EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - echo "MESSAGE<<$EOF" >> $GITHUB_ENV - JSON_TEXT=$(curl -s -L -H "Accept: application/vnd.github+json" $COMMIT_URL) - VERSION_STRING=$(echo "$JSON_TEXT" | jq ".commit.message" | sed -E 's/^"//' | sed -E 's/"$//') - echo -e "$VERSION_STRING" >> $GITHUB_ENV - echo "$EOF" >> $GITHUB_ENV + - name: create tag + run: git tag ${{ needs.get-version.outputs.version }} - - name: Add tag ${{ env.VERSION }} - uses: rickstaa/action-create-tag@v1 - id: create-tag - with: - tag: ${{ env.VERSION }} - tag_exists_error: true - commit_sha: ${{ github.sha }} - message: ${{ env.MESSAGE }} + - name: push tag + run: git push origin ${{ needs.get-version.outputs.version }} diff --git a/.github/workflows/lint-fmt-test.yml b/.github/workflows/lint-fmt-test.yml deleted file mode 100644 index b06de7c..0000000 --- a/.github/workflows/lint-fmt-test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: lint, check format and run build as a test - -on: push - -permissions: - contents: read - -jobs: - lint-and-fmt: - runs-on: ubuntu-latest - timeout-minutes: 1 - - steps: - - uses: actions/checkout@v3 - - - name: Setup Deno - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Lint - run: deno lint - - - name: Check format - run: deno fmt --check - - - name: Build - run: deno task build diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml new file mode 100644 index 0000000..41b6cac --- /dev/null +++ b/.github/workflows/lint-format.yml @@ -0,0 +1,46 @@ +name: Lint and Check Format + +on: push + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 3 + + steps: + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: lint + run: deno lint + + format: + runs-on: ubuntu-latest + timeout-minutes: 3 + + steps: + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: check format + run: deno fmt --check + + build: + runs-on: ubuntu-latest + timeout-minutes: 3 + + steps: + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: build + run: deno task build diff --git a/.github/workflows/manifest-version.yml b/.github/workflows/manifest-version.yml new file mode 100644 index 0000000..dbf7a05 --- /dev/null +++ b/.github/workflows/manifest-version.yml @@ -0,0 +1,41 @@ +name: Manifest Version + +on: + workflow_call: + inputs: + ref: + required: true + type: string + checkout-ref: + type: string + outputs: + version: + value: ${{ jobs.get-version.outputs.version }} + +jobs: + get-version: + runs-on: ubuntu-latest + timeout-minutes: 3 + + outputs: + version: ${{ steps.get-version.outputs.version }} + + steps: + - run: 'echo "ref: ${{ inputs.ref }}"' + + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.checkout-ref || inputs.ref }} + + - uses: actions/checkout@v4 + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: get version + id: get-version + run: | + VERSION=$(NOCOLOR="true" git show "${{ inputs.ref }}:./src/manifest.jsonc" | deno run scripts/getManifestVersion.ts) + echo "Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/release-attach-artifact-manual.yml b/.github/workflows/release-attach-artifact-manual.yml deleted file mode 100644 index e2a5ff5..0000000 --- a/.github/workflows/release-attach-artifact-manual.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: attach build artifact to release manually - -on: workflow_dispatch - -permissions: write-all - -env: - TEMP_ASSET_NAME: asset.zip - -jobs: - attach-build-artifact: - runs-on: ubuntu-latest - timeout-minutes: 1 - - steps: - - uses: actions/checkout@v3 - - - name: Get tag from ref - env: - REF: ${{ github.ref }} - run: | - [[ $REF =~ ^refs/tags/ ]] || (echo "This workflow must be run from tag."; exit 1) - TAG_NAME=$(echo $REF | sed -E s#^refs/tags/##) - echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV - - - name: Get upload url - env: - RELEASES_URL: ${{ github.event.repository.releases_url }} - run: | - URL=$(echo "$RELEASES_URL" | sed -e s:{/id}:/tags/$TAG_NAME:) - JSON_TEXT=$(curl -sSL $URL) - UPLOAD_URL=$(echo "$JSON_TEXT" | jq ".upload_url" | sed -e 's/\"//g') - UPLOAD_URL=$(echo "$UPLOAD_URL" | sed -e "s/{?name,label}/?name=nitech-moodle-extension-40a-${TAG_NAME}.zip/") - echo "UPLOAD_URL=$UPLOAD_URL" >> $GITHUB_ENV - - - name: Setup Deno - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: Build - run: deno task build - - - name: Create zip - run: cd ./dist; zip $TEMP_ASSET_NAME -r . - - - name: Upload release asset - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - curl -L \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - -H "Content-Type: application/octet-stream" \ - $UPLOAD_URL \ - --data-binary "@dist/$TEMP_ASSET_NAME" diff --git a/.github/workflows/release-attach-artifact.yml b/.github/workflows/release-attach-artifact.yml index 9751d26..4f50157 100644 --- a/.github/workflows/release-attach-artifact.yml +++ b/.github/workflows/release-attach-artifact.yml @@ -1,45 +1,36 @@ -name: attach build artifact to release +name: "Release: Attach Artifact" on: release: types: - - published + - released + - prereleased + +permissions: write-all env: - TEMP_ASSET_NAME: asset.zip + FILE_NAME: nitech-moodle-extension-40a-${{ github.event.release.tag_name }}.zip jobs: attach-build-artifact: runs-on: ubuntu-latest - timeout-minutes: 1 + timeout-minutes: 3 steps: - - uses: actions/checkout@v3 - - - name: Set environment variables - env: - TAG_NAME: ${{ github.event.release.tag_name }} - run: | - echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV + - uses: actions/checkout@v4 - - name: Setup Deno - uses: denoland/setup-deno@v1 + - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - - name: Build + - name: build run: deno task build - - name: Create zip - run: cd ./dist; zip $TEMP_ASSET_NAME -r . + - name: zip artifacts + run: cd ./dist; zip $FILE_NAME -r . - - name: Upload release asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 + - name: upload env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./dist/${{ env.TEMP_ASSET_NAME }} - asset_name: nitech-moodle-extension-40a-${{ env.TAG_NAME }}.zip - asset_content_type: application/zip + run: | + gh release upload ${{ github.event.release.tag_name }} $FILE_NAME diff --git a/deno.json b/deno.json index 93fbcc5..654e9b3 100644 --- a/deno.json +++ b/deno.json @@ -10,10 +10,7 @@ ], "jsx": "react-jsx", "jsxFactory": "preact.h", - "jsxFragmentFactory": "preact.Fragment", - "paths": { - "~/*": ["./src/*"] - } + "jsxFragmentFactory": "preact.Fragment" }, "lint": { "rules": { diff --git a/deno.lock b/deno.lock index 625831a..b4d27dc 100644 --- a/deno.lock +++ b/deno.lock @@ -13,7 +13,9 @@ "jsr:@tsukina-7mochi/esbuild-plugin-sass@~0.1.1": "0.1.1", "npm:esbuild@0.24": "0.24.0", "npm:esbuild@0.24.0": "0.24.0", - "npm:sass@^1.80.3": "1.80.5" + "npm:preact@^10.24.3": "10.24.3", + "npm:sass@^1.80.3": "1.80.5", + "npm:webextension-polyfill@0.12": "0.12.0" }, "jsr": { "@luca/esbuild-deno-loader@0.11.0": { @@ -270,6 +272,9 @@ "picomatch@2.3.1": { "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, + "preact@10.24.3": { + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==" + }, "readdirp@4.0.2": { "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==" }, @@ -290,6 +295,9 @@ "dependencies": [ "is-number" ] + }, + "webextension-polyfill@0.12.0": { + "integrity": "sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==" } }, "workspace": { diff --git a/scripts/cache-all.sh b/scripts/cache-all.sh deleted file mode 100644 index c534685..0000000 --- a/scripts/cache-all.sh +++ /dev/null @@ -1,10 +0,0 @@ -options="" -for arg in "$@"; do - if [[ "$arg" == "--reload" ]]; then - options="$options --reload" - fi -done - -for file in $(find . -name "*.ts"); do - deno cache $options $file --import-map ./import_map.json -done diff --git a/scripts/check-version-increase.sh b/scripts/check-version-increase.sh deleted file mode 100644 index 3ba3dcd..0000000 --- a/scripts/check-version-increase.sh +++ /dev/null @@ -1,25 +0,0 @@ -# Check if the version set in manifest.json5 is increased from latest release -# useage: bash check-version.sh nitech-create/nitech-moodle-extension-40a - -manifest_version="$(sh $(dirname $0)/get-manifest-version.sh)" - -if [ $# -lt 1 ]; then - echo "Repository name (user/repo) is needed." - exit 1 -fi - -api_endpoint="https://api.github.com/repos/$1/releases/latest" -release_version=$(curl -sSL -H "Accept: application/vnd.github+json" $api_endpoint \ - | jq ".tag_name" | sed -E 's/^"v?//' | sed -E 's/"$//') - -# semver -semver_tempfile=$(mktemp) -curl -sSL "https://raw.githubusercontent.com/parleer/semver-bash/master/semver" > $semver_tempfile -source $semver_tempfile - -if $(semverGT "$manifest_version" "$release_version"); then - exit 0 -else - echo "The version in manifest file ($manifest_version) is less or equals to latest release ($release_version)" - exit 1 -fi diff --git a/scripts/check-version.sh b/scripts/check-version.sh deleted file mode 100644 index 8c45c63..0000000 --- a/scripts/check-version.sh +++ /dev/null @@ -1,8 +0,0 @@ -# Check if the version set in manifest.json5 matches the one of the CLI argument -# useage: bash check-version.sh v1.0.0 - -manifest_version=$(sh $(dirname $0)/get-manifest-version.sh) -if [ "$manifest_version" != $(echo "$1" | sed -E 's/^v?//') ]; then - echo "The version ($1) does not matchese to the one in manifest file ($manifest_version)." 1>&2 - exit 1 -fi diff --git a/scripts/get-manifest-version.sh b/scripts/get-manifest-version.sh deleted file mode 100644 index e17f7da..0000000 --- a/scripts/get-manifest-version.sh +++ /dev/null @@ -1,4 +0,0 @@ -cat "$(dirname $0)/../src/manifest.json5" \ - | grep -P 'version\s*:\s*"\d+\.\d+\.\d+[^"]*"' \ - | sed -E 's/^\s*version\s*:\s*"//' \ - | sed -E 's/",?\s*$//' diff --git a/scripts/getManifestVersion.ts b/scripts/getManifestVersion.ts new file mode 100644 index 0000000..ad60e7c --- /dev/null +++ b/scripts/getManifestVersion.ts @@ -0,0 +1,17 @@ +import * as JSONC from "@std/jsonc"; + +let content = ""; + +await Deno.stdin.readable + .pipeThrough(new TextDecoderStream()) + .pipeTo( + new WritableStream({ + write(chunk) { + content += chunk; + }, + }), + ); + +const manifest = JSONC.parse(content); + +console.log(manifest.version); diff --git a/scripts/setup-vscode.ts b/scripts/setup-vscode.ts deleted file mode 100644 index a372438..0000000 --- a/scripts/setup-vscode.ts +++ /dev/null @@ -1,45 +0,0 @@ -const configFilePath = "./.vscode/settings.json"; -const configJSON = await (async () => { - try { - const stat = await Deno.lstat(configFilePath); - const a = 2; - if (stat.isFile) { - return Deno.readTextFile(configFilePath); - } - - return Promise.reject(`${configFilePath} exists but is NOT a file`); - } catch { - return "{}"; - } -})(); - -console.info( - "\x1b[1mInstall Deno for VSCode on https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno\x1b[0m", -); - -const config = JSON.parse(configJSON); -const langSettings = { - "editor.defaultFormatter": "denoland.vscode-deno", - "editor.tabSize": 2, - "editor.insertSpaces": true, -}; - -config["deno.enable"] = true; -config["deno.unstable"] = true; -config["deno.lint"] = true; -config["deno.config"] = "./deno.json"; -config["deno.importMap"] = "./import_map.json"; -config["[javascript]"] = langSettings; -config["[javascriptreact]"] = langSettings; -config["[typescript]"] = langSettings; -config["[typescriptreact]"] = langSettings; -config["[json]"] = langSettings; - -await Deno.writeTextFile(configFilePath, JSON.stringify(config)); - -const p = Deno.run({ - cmd: ["deno", "fmt", configFilePath], -}); -const status = await p.status(); - -Deno.exit(status.code); diff --git a/src/options/index.scss b/src/options/index.scss index 4217d13..66a8fd9 100644 --- a/src/options/index.scss +++ b/src/options/index.scss @@ -20,7 +20,7 @@ main { } #preferences { - .preference-group+.preference-group { + .preference-group + .preference-group { margin-top: 1em; } @@ -38,7 +38,7 @@ main { li { list-style: none; - &+li { + & + li { border-top: 1px solid #d0d0d0; } From 2ce27e3997280e8dfa17d58d4df7215197a3cbbb Mon Sep 17 00:00:00 2001 From: Tsukina-7mochi Date: Tue, 5 Nov 2024 16:48:56 +0900 Subject: [PATCH 48/53] style: remove formatter ignore --- src/options/components/EditPreferences.tsx | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/options/components/EditPreferences.tsx b/src/options/components/EditPreferences.tsx index bf07a3a..119bc3f 100644 --- a/src/options/components/EditPreferences.tsx +++ b/src/options/components/EditPreferences.tsx @@ -46,23 +46,24 @@ export const EditPreferences = function () { リンクの強制ダウンロードを無効化する

    - {/* deno-fmt-ignore */} - moodle では PDF などのリンクに強制的にダウンロードさせるように設定することができます。 - この設定により PDF などがブラウザの規定の動作で開かずダウンロードされてしまうのを防ぎます。 + {/* 改行が気持ち悪いが formatter のバグで無視できないので一旦放置 */} + moodle では PDF + などのリンクに強制的にダウンロードさせるように設定することができます。 + この設定により PDF + などがブラウザの規定の動作で開かずダウンロードされてしまうのを防ぎます。

    preferences.setReplaceBreadcrumbCourseName({ - enabled: e.currentTarget.checked + enabled: e.currentTarget.checked, })} >

    ヘッダーナビゲーションのコース名をわかりやすい名前にする

    - {/* deno-fmt-ignore */} 各ページのヘッダーにある現在のページの位置を表すリンクのテキストをわかりやすい名前に置き換えます。

    @@ -70,14 +71,13 @@ export const EditPreferences = function () { checked={preferences.replaceNavigationCourseName.enabled} onClick={(e) => preferences.setReplaceNavigationCourseName({ - enabled: e.currentTarget.checked + enabled: e.currentTarget.checked, })} >

    ナビゲーションのコース名をわかりやすい名前にする

    - {/* deno-fmt-ignore */} 各ページにあるナビゲーションメニューにおいて、コースリンクのテキストをわかりやすい名前に置き換えます。

    @@ -91,30 +91,29 @@ export const EditPreferences = function () { checked={preferences.dashboardEventsCountdown.enabled} onClick={(e) => preferences.setDashboardEventsCountdown({ - enabled: e.currentTarget.checked + enabled: e.currentTarget.checked, })} >

    直近イベントに残り時間を表示する

    - {/* deno-fmt-ignore */} ダッシュボードにある課題などが表示される「直近イベント」において、表示されているイベントの残り時間を表示します。 - この残り時間はリアルタイムでカウントダウンされます (表示中に終了時刻が変わった場合はページの再読み込みが必要です)。 + この残り時間はリアルタイムでカウントダウンされます + (表示中に終了時刻が変わった場合はページの再読み込みが必要です)。

    preferences.setDashboardQuickCourseLinks({ - enabled: e.currentTarget.checked + enabled: e.currentTarget.checked, })} >

    開講中のコースにアクセスしやすくするメニューを表示する

    - {/* deno-fmt-ignore */} ダッシュボードに現在開講しているコースを曜日・時間順に表示するクイックメニューを表示します。

    @@ -128,14 +127,13 @@ export const EditPreferences = function () { checked={preferences.scormAutoCollapseToc.enabled} onClick={(e) => preferences.setScormAutoCollapseToc({ - enabled: e.currentTarget.checked + enabled: e.currentTarget.checked, })} >

    ページを開いたときに目次を折りたたむ

    - {/* deno-fmt-ignore */} 動画ページに表示される目次をデフォルトで折りたたみます。 画面上により大きく動画を表示することができます。

    From f8c39d7102c0fbac572644a5f1280d4976cddc4c Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:50:42 +0900 Subject: [PATCH 49/53] commit readme --- readme.md | 71 ++++++++----------------------------------------------- 1 file changed, 10 insertions(+), 61 deletions(-) diff --git a/readme.md b/readme.md index 2dd5ad7..aea0660 100644 --- a/readme.md +++ b/readme.md @@ -1,55 +1,8 @@ -<<<<<<< HEAD -# NITech Moodle Extension (40a) -Web Extension for NITech Moodle 4.0 - -[Chrome Web Storeで公開中](https://chromewebstore.google.com/detail/nitech-moodle-extension-4/gghacnecolaclhlihmlhffgkmeojehff) - -[![release](https://img.shields.io/github/v/release/nitech-create/nitech-moodle-extension-40a?include_prereleases)](https://github.com/nitech-create/nitech-moodle-extension-40a/releases/latest) - -開発をお手伝いしてくださる方: [開発者向けドキュメント](./readme.dev.md) - -## 概要 - -名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 - -### 主な機能 - -- ダッシュボード ( トップページ ) に講義へのショートカットアクセスを追加 - - 今受けている講義だけを曜日・時間でソートして一覧表示 -- 動画を画面内で大きく表示するように変更 -- 時間割表の追加 (予定) -- 課題などのイベント締め切りにカウントダウン表示の追加 -- ナビゲーションのコース表示をわかりやすいように変更 -- ヘッダーのコース表示をわかりやすいように変更 -- 強制ダウンロードリンクの無効化 -- 全体的なスタイルの修正 - -## ブラウザ対応状況 - -| ブラウザ | 対応状況 | -| ------------------------------------- | ------------------------ | -| Chrome (Windows 11, 111.0.5563.147) | 開発中 | -| Microsoft Edge (情報基盤センター推奨) | 開発中 | -| Firefox | 現在非対応(今後対応予定) | - -## 利用方法 - -### Chrome Web Store からインストール - -[Chrome Web Storeで公開中](https://chromewebstore.google.com/detail/nitech-moodle-extension-4/gghacnecolaclhlihmlhffgkmeojehff) - -### GitHub からインストール - -1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) から .zip ファイルをダウンロードする - - または [ビルド方法](./how_to_build.md) に従ってビルドする -2. 拡張機能ページを開く - - `chrome://extensions` をURL欄に入力する - - またはEdgeブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック -3. 開発者モードを有効にします -4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip ファイルをドロップ - - または「パッケージ化されていない拡張機能を読み込む」 -======= -# Web Extension for NITech Moodle 4.0 +# NITech Moodle Extension (40a) + +Web Extension for NITech Moodle 4.0 + +[Chrome Web Store で公開中](https://chromewebstore.google.com/detail/nitech-moodle-extension-4/gghacnecolaclhlihmlhffgkmeojehff) [![release](https://img.shields.io/github/v/release/nitech-create/nitech-moodle-extension-40a?include_prereleases)](https://github.com/nitech-create/nitech-moodle-extension-40a/releases/latest) @@ -57,10 +10,7 @@ Web Extension for NITech Moodle 4.0 ## 概要 -名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) -の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 - -Web Extension for Moodle 4.0 of NITech. +名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 ### 主な機能 @@ -86,18 +36,17 @@ Web Extension for Moodle 4.0 of NITech. ### Chrome Web Store からインストール -準備中です +[Chrome Web Store で公開中](https://chromewebstore.google.com/detail/nitech-moodle-extension-4/gghacnecolaclhlihmlhffgkmeojehff) ### GitHub からインストール -1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) +1. [Releases]() から .zip ファイルをダウンロードする - または [ビルド方法](./how_to_build.md) に従ってビルドする 2. 拡張機能ページを開く - - `chrome://extensions` をURL欄に入力する - - またはEdgeブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック + - `chrome://extensions` を URL 欄に入力する + - または Edge ブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック 3. 開発者モードを有効にします 4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip ファイルをドロップ - または「パッケージ化されていない拡張機能を読み込む」 ->>>>>>> origin/refactor From 3e6345a909c7659a3ead6396a6e9ab6bab9be309 Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:51:04 +0900 Subject: [PATCH 50/53] verup --- src/manifest.jsonc | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 7f606be..59f2dc1 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -3,7 +3,7 @@ "manifest_version": 3, "name": "NITech Moodle Extension (40a)", "homepage_url": "https://github.com/nitech-create/nitech-moodle-extension-40a", - "version": "0.10.0", + "version": "0.20.0", // Recommended // "action": {}, @@ -40,9 +40,7 @@ }, // my course config page { - "matches": [ - "https://cms7.ict.nitech.ac.jp/moodle40a/my/courses.php" - ], + "matches": ["https://cms7.ict.nitech.ac.jp/moodle40a/my/courses.php"], "js": [ // this is **a special case** to use script in other directory "./contentScripts/dashboard/readAndStoreCourses/index.ts" @@ -54,18 +52,14 @@ "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*", "https://cms7.ict.nitech.ac.jp/moodle40a/mod/scorm/*/*" ], - "js": [ - "./contentScripts/scorm/autoCollapseToc/index.ts" - ] + "js": ["./contentScripts/scorm/autoCollapseToc/index.ts"] }, { "matches": [ "https://cms7.ict.nitech.ac.jp/moodle40a/pluginfile.php/*/mod_scorm/content/*/*" ], "all_frames": true, - "css": [ - "./contentScripts/scormContents/main.scss" - ] + "css": ["./contentScripts/scormContents/main.scss"] } ], From f5086fcd44db4ac63eae42f0a3a8b6627c6d5a23 Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:55:01 +0900 Subject: [PATCH 51/53] fix format --- privacy_policies.md | 58 ++++++++++++++++++++++++++++++++------------- readme.md | 8 ++++--- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/privacy_policies.md b/privacy_policies.md index fd8431c..fa1f8b4 100644 --- a/privacy_policies.md +++ b/privacy_policies.md @@ -1,67 +1,93 @@ # Privacy policy for Chrome extensions -## Chrome拡張のプライバシーポリシー -本プライバシーポリシーは、nitechCreate(以下、「当開発者」)が開発したGoogle Chromeの拡張機能(Extension)(以下、「拡張機能」とします。)の利用において、利用者の個人情報もしくはそれに準ずる情報を取り扱う際に、当開発者が遵守する方針を示したものです。 +## Chrome 拡張のプライバシーポリシー + +本プライバシーポリシーは、nitechCreate(以下、「当開発者」)が開発した Google +Chrome +の拡張機能(Extension)(以下、「拡張機能」とします。)の利用において、利用者の個人情報もしくはそれに準ずる情報を取り扱う際に、当開発者が遵守する方針を示したものです。 ### 基本方針 + 当開発者は、個人情報の重要性を認識し、個人情報を保護することが社会的責務であると考え、個人情報に関する法令を遵守し、拡張機能で取扱う個人情報の取得、利用、管理を適正に行います。 ### 適用範囲 + 本プライバシーポリシーは、当開発者が開発した拡張機能においてのみ適用されます。 ### 個人情報の取得と利用目的 + 当開発者は、個人情報を収集する機能を要した拡張機能を公開しません。 -**ただし、一部個人情報を利用者のブラウザのLocalStorageに保存します。** +**ただし、一部個人情報を利用者のブラウザの LocalStorage に保存します。** #### 取得方法 -特定のWebサイトへアクセスした際に授業などの情報を取得する。 + +特定の Web サイトへアクセスした際に授業などの情報を取得する。 #### 利用目的 + ##### 利便性の向上 -拡張機能では、機能の内容により、下記の情報を **「ブラウザのLocalStorage」へ保存します**。 + +拡張機能では、機能の内容により、下記の情報を **「ブラウザの +LocalStorage」へ保存します**。 + - 拡張機能で用いる授業等の情報 これにより、拡張機能の次回利用時などに授業等の情報が保存されているため、欠落があっても表示されて利便性が向上します。 -**個人情報は当開発者に送信されず拡張機能利用者のブラウザのLocalStorageに保存されます**。 +**個人情報は当開発者に送信されず拡張機能利用者のブラウザの LocalStorage +に保存されます**。 ##### 保存期間について -拡張機能内でデータの扱いとして、LocalStorageを使用します。 -LocalStorageには保存期間が存在しないためデータの保存期間は拡張機能のアンインストール時までとします。 + +拡張機能内でデータの扱いとして、LocalStorage を使用します。 LocalStorage +には保存期間が存在しないためデータの保存期間は拡張機能のアンインストール時までとします。 #### 個人情報の取り扱いの同意について + 当開発者が開発を行った拡張機能では、拡張機能のインストールを行う前に、当プライバシーポリシーをご一読頂くようにお願いします。 インストールをされた時点で、当プライバシーポリシーに同意されたとみなします。 -#### Cookieによる個人情報の取得 -拡張機能では、Cookieを利用することがあります。 +#### Cookie による個人情報の取得 + +拡張機能では、Cookie を利用することがあります。 Cookie(クッキー)とは、ウェブサイトを利用したときに、ブラウザとサーバーとの間で送受信した利用履歴や入力内容などを、訪問者のコンピュータにファイルとして保存しておく仕組みです。 ##### 利用目的について + 拡張機能の利用者の利便性を向上するために活用します。 -ログイン処理や画面遷移を拡張機能から行う際に、Cookieを活用することでユーザーの手間を省いてデータの処理が可能となります。 -なお、拡張機能ではプライバシー保護のため、拡張機能の目的とする情報以外のCookieを送信しません。 +ログイン処理や画面遷移を拡張機能から行う際に、Cookie +を活用することでユーザーの手間を省いてデータの処理が可能となります。 +なお、拡張機能ではプライバシー保護のため、拡張機能の目的とする情報以外の Cookie +を送信しません。 ##### 保存期間について -Cookieの保存期間は利用者のブラウザーにて設定されているデフォルトの期間保存されます。 + +Cookie +の保存期間は利用者のブラウザーにて設定されているデフォルトの期間保存されます。 ### 個人情報の管理 + 当開発者は、拡張機能内における個人情報の管理について、以下を徹底します。 #### 情報の正確性の確保 + 利用者が入力したデータにおいて、常に正しい情報を保持します。 #### 安全管理措置 + 拡張機能において、情報の漏洩、滅失を防止するために拡張機能内において利用目的外のサーバーへの情報送信を行いません。 #### 個人情報の第三者への提供について + 当開発者の開発する拡張機能は、利用者から提供いただいた個人情報を、訪問者本人の同意を得ることなく第三者に提供することはありません。 また、今後第三者提供を行うことになった場合には、提供する情報と提供目的などを提示し、訪問者から同意を得た場合のみ第三者提供を行います。 #### 問い合わせ先 + 拡張機能、又は個人情報の取扱いに関しては、下記の連絡先よりお問い合わせください。 -X: https://x.com/nitechCreate (DMやリプライ) -お問い合わせフォーム: https://forms.gle/obR3yYBi3q5jH3KW8 +- X アカウント(DM やリプライ): https://x.com/nitechCreate +- お問い合わせフォーム: https://forms.gle/obR3yYBi3q5jH3KW8 ## 策定日 -2024年10月22日 策定 + +2024 年 10 月 22 日 策定 diff --git a/readme.md b/readme.md index aea0660..70a36b0 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,8 @@ Web Extension for NITech Moodle 4.0 ## 概要 -名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 +名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) +の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 ### 主な機能 @@ -40,12 +41,13 @@ Web Extension for NITech Moodle 4.0 ### GitHub からインストール -1. [Releases]() +1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) から .zip ファイルをダウンロードする - または [ビルド方法](./how_to_build.md) に従ってビルドする 2. 拡張機能ページを開く - `chrome://extensions` を URL 欄に入力する - - または Edge ブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック + - または Edge + ブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック 3. 開発者モードを有効にします 4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip ファイルをドロップ From 0d37d40b83a13412c312d5f7db9c5a587d423e44 Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:02:19 +0900 Subject: [PATCH 52/53] format readme.md --- readme.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 34723e3..638cfd4 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,7 @@ Web Extension for NITech Moodle 4.0 ### GitHub からインストール -1. [Releases]() +1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) から .zip ファイルをダウンロードする - または [ビルド方法](./how_to_build.md) に従ってビルドする 2. 拡張機能ページを開く @@ -61,7 +61,8 @@ Web Extension for NITech Moodle 4.0 ## 概要 -名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 +名古屋工業大学のオンライン授業サポートシステムとして採用されている Moodle (4.0) +の機能を改善・拡張して使いやすくするブラウザ用拡張機能です。非公式であり、問題が起きても責任は取れません。 Web Extension for Moodle 4.0 of NITech. @@ -93,11 +94,13 @@ Web Extension for Moodle 4.0 of NITech. ### GitHub からインストール -1. [Releases]() +1. [Releases](https://github.com/nitech-create/nitech-moodle-extension-40a/(releases)) から .zip ファイルをダウンロードする - または [ビルド方法](./how_to_build.md) に従ってビルドする 2. 拡張機能ページを開く - `chrome://extensions` を URL 欄に入力する - - または Edge ブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック + - または Edge + ブラウザ右上のクッキーみたいなアイコンを押して、「拡張機能の管理」をクリック 3. 開発者モードを有効にします -4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip ファイルをドロップ - または「パッケージ化されていない拡張機能を読み込む」 +4. `manifest.json` が含まれるフォルダまたはダウンロードした .zip + ファイルをドロップ - または「パッケージ化されていない拡張機能を読み込む」 From c4a4286d7c3685b69e9e470f89ea25f42904ecda Mon Sep 17 00:00:00 2001 From: KoCSience <90507944+KoCSience@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:03:41 +0900 Subject: [PATCH 53/53] fix version --- src/manifest.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.jsonc b/src/manifest.jsonc index 59f2dc1..882c3e0 100644 --- a/src/manifest.jsonc +++ b/src/manifest.jsonc @@ -3,7 +3,7 @@ "manifest_version": 3, "name": "NITech Moodle Extension (40a)", "homepage_url": "https://github.com/nitech-create/nitech-moodle-extension-40a", - "version": "0.20.0", + "version": "0.10.0", // Recommended // "action": {},