From e5d75208fc05d6b46ad89a500e1a4eb3655cf6d9 Mon Sep 17 00:00:00 2001 From: eric2788 Date: Mon, 11 Mar 2024 15:07:27 +0800 Subject: [PATCH 01/11] updated documentations about contribute --- CONTRIBUTING.md | 12 ++++++------ docs/background.md | 2 +- docs/features.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8f54841..183dd544 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,8 +30,8 @@ 1. Fork 本仓库到你的本地仓库 2. 在你的本地仓库中进行修改 -3. 如开发新功能,请自行添加合理且可行的单元/集成测试 -4. 完成后,确保你的代码通过所有单元/集成测试 +3. 如开发新功能,请自行添加合理且可行的端到端测试 +4. 完成后,确保你的代码通过所有端到端测试 5. 提交 Pull Request 到本仓库的 `develop` 分支 ## 程式码规范 @@ -84,12 +84,12 @@ src/ ``` tests/ -├── features/ # 功能模块的集成测试代码。 +├── features/ # 功能模块的端到端测试代码。 ├── fixtures/ # 测试中需要用到的前置依赖。 ├── helpers/ # 使用类形式包装的测试辅助工具。 -├── pages/ # 扩展页面的集成测试代码。 +├── pages/ # 扩展页面的端到端测试代码。 ├── utils/ # 辅助测试的函数和工具。 -├── content.spec.ts # 内容脚本的集成测试代码。 +├── content.spec.ts # 内容脚本的端到端测试代码。 ├── options.ts # fixtures 选项类型定义文件。 └── theme.setup.ts # 大海报房间测试的前置依赖。 ``` @@ -103,7 +103,7 @@ tests/ 5. 有关如何编写贡献代码,请参阅 [入门指南](#入门指南) 。 -#### 如要在本地运行集成测试: +#### 如要在本地运行端到端测试: - 请先运行 `pnpm dlx playwright install` 安装 PlayWright 的浏览器引擎 - 完成后,运行 `pnpm build && pnpm test:prepare` 编译并部署测试环境 - 最后,运行 `pnpm test` 运行测试 (或者用 playwright vscode 插件运行测试) diff --git a/docs/background.md b/docs/background.md index 0bdbd632..7a7e3672 100644 --- a/docs/background.md +++ b/docs/background.md @@ -147,7 +147,7 @@ export type ForwardBody = { room: string } -// 支援 await 函数 +// 支援 async 函数 const handler: ForwardHandler = (req) => { let pos: 'scroll' | 'top' | 'bottom' = 'scroll' diff --git a/docs/features.md b/docs/features.md index df49fe3c..6425a8ef 100644 --- a/docs/features.md +++ b/docs/features.md @@ -2,7 +2,7 @@ > 本扩展基于 [Plasmo CSUI](https://docs.plasmo.com/framework/content-scripts-ui) 进行前端渲染。 -如要新增功能,你需要到以下地方新增(其余未列明的地方均为可选): +如要新增功能,你需要到以下地方新增: ``` src/ From 05a6c82057afedd2040095cf1d2f9ae5016c9794 Mon Sep 17 00:00:00 2001 From: eric2788 Date: Mon, 11 Mar 2024 21:59:35 +0800 Subject: [PATCH 02/11] added partial test to reduce feature/hotfix branch always test all the tests --- .github/workflows/build-test.yml | 2 -- .github/workflows/partial-test.yml | 48 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/partial-test.yml diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index eb155b67..317a826f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -4,8 +4,6 @@ on: push: branches: - 'develop' - - 'hotfix/**' - - 'feature/**' paths: - 'src/**' - 'tests/**' diff --git a/.github/workflows/partial-test.yml b/.github/workflows/partial-test.yml new file mode 100644 index 00000000..4e05465d --- /dev/null +++ b/.github/workflows/partial-test.yml @@ -0,0 +1,48 @@ +name: Test Only @scoped + +on: + push: + branches: + - 'hotfix/**' + - 'feature/**' + paths: + - 'src/**' + - 'tests/**' + - 'playwright.config.ts' + - 'pnpm-lock.yaml' + - '.github/workflows/build-test.yml' + workflow_dispatch: + inputs: + debug: + description: 'Enable debug logging' + required: false + default: 'false' + +jobs: + fast-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + browser: [chrome, edge] + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: 8 + - name: Setup Node.js + uses: actions/setup-node@v4.0.1 + with: + node-version: 20 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build And Package Extensions + run: pnpm build --zip --target=${{ matrix.browser }}-mv3 + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Test + id: test + run: pnpm test -- --project=${{ matrix.browser }} --grep=@scoped --pass-with-no-tests + env: + DEBUG: true \ No newline at end of file From e436e1764496292e7d9f794f11ecfa9331b0a3be Mon Sep 17 00:00:00 2001 From: Eric Lam Date: Tue, 12 Mar 2024 09:28:53 +0800 Subject: [PATCH 03/11] =?UTF-8?q?Fixed=20#68:=20=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=B7=BB=E5=8A=A0=E5=90=8C=E4=BC=A0=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E3=80=82=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed cannot request user info while adding list and added test cases * fixed pnpm clean in windows got error --- .gitignore | 1 + package.json | 3 +- src/api/bilibili.ts | 1 + src/logger.ts | 14 +++- src/settings/components/DataTable.tsx | 4 +- .../jimaku/components/ListingFragment.tsx | 38 ++++++++-- tests/features/jimaku.spec.ts | 73 +++++++++++++++++++ tests/helpers/bilibili-page.ts | 12 +-- 8 files changed, 131 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index bafa3b38..83f7f041 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ npm-debug.log* out/ build/ dist/ +*.local.yml # plasmo - https://www.plasmo.com .plasmo diff --git a/package.json b/package.json index 3876489f..ce7f3688 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "package": "plasmo package", "clean": "run-script-os", "clean:nix": "rm -rf build && rm -rf .plasmo", - "clean:windows": "rmdir /s /q build && rmdir /s /q .plasmo", + "clean:windows": "if exist build rmdir /s /q build && if exist .plasmo rmdir /s /q .plasmo", "test": "playwright test", "test:prepare": "run-script-os", "test:prepare:nix": "mv build/chrome-mv3-prod build/extension", @@ -66,6 +66,7 @@ "manifest": { "host_permissions": [ "*://api.vtbs.moe/*", + "*://api.bilibili.com/*", "*://api.live.bilibili.com/*", "*://live.bilibili.com/*", "*://*.bilivideo.com/*", diff --git a/src/api/bilibili.ts b/src/api/bilibili.ts index 5e0515c5..dfc5fb3c 100644 --- a/src/api/bilibili.ts +++ b/src/api/bilibili.ts @@ -111,6 +111,7 @@ export async function requestUserInfo(mid: string): Promise } }) + if (res.code === -404) return undefined if (res.code !== 0) throw new Error(`B站API请求错误: ${res.message}`) return res.data } diff --git a/src/logger.ts b/src/logger.ts index 9e75a91f..30fbb1aa 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,6 +1,16 @@ + +function ciOnly(func: any) { + return function (...data: any[]) { + if (process.env.CI || process.env.NODE_ENV === 'development') { + func(...data) + } + } +} + + console.info = console.info.bind(console, '[bilibili-vup-stream-enhancer]') console.warn = console.warn.bind(console, '[bilibili-vup-stream-enhancer]') console.error = console.error.bind(console, '[bilibili-vup-stream-enhancer]') console.log = console.log.bind(console, '[bilibili-vup-stream-enhancer]') -console.debug = process.env.NODE_ENV === 'production' ? () => {} : console.debug.bind(console, '[bilibili-vup-stream-enhancer]') -console.trace = process.env.NODE_ENV === 'production' ? () => {} : console.trace.bind(console, '[bilibili-vup-stream-enhancer]') \ No newline at end of file +console.debug = ciOnly(console.debug.bind(console, '[bilibili-vup-stream-enhancer]')) +console.trace = ciOnly(console.trace.bind(console, '[bilibili-vup-stream-enhancer]')) \ No newline at end of file diff --git a/src/settings/components/DataTable.tsx b/src/settings/components/DataTable.tsx index c40ea852..7f8eb90f 100644 --- a/src/settings/components/DataTable.tsx +++ b/src/settings/components/DataTable.tsx @@ -23,7 +23,7 @@ export type DataTableProps = { headers: TableHeader[] values: T[] actions: TableAction[] - onAdd?: (value: string) => void + onAdd?: (value: string, e: KeyboardEvent) => void headerSlot?: React.ReactNode } @@ -45,7 +45,7 @@ function DataTable(props: DataTableProps): JSX.Element { const onAdd = (e: Event | any) => { e?.preventDefault() if (!input) return - props.onAdd?.(input) + props.onAdd?.(input, e) setInput('') } diff --git a/src/settings/features/jimaku/components/ListingFragment.tsx b/src/settings/features/jimaku/components/ListingFragment.tsx index add03cd3..d1b063f8 100644 --- a/src/settings/features/jimaku/components/ListingFragment.tsx +++ b/src/settings/features/jimaku/components/ListingFragment.tsx @@ -5,6 +5,7 @@ import type { ExposeHandler, StateProxy } from "~hooks/binding" import type { TableHeader } from "~settings/components/DataTable" import DataTable from "~settings/components/DataTable" import DeleteIcon from "~settings/components/DeleteIcon" +import type { WbiAccInfoResponse } from "~types/bilibili" import type { ArrElement, PickKeys } from "~types/common" import { catcher } from "~utils/fetch" import { removeArr } from "~utils/misc" @@ -45,7 +46,7 @@ const user_headers: TableHeader[] = [ function ListingFragment({ state, useHandler }: StateProxy): JSX.Element { - const addUserRecord = >(key: K) => async (value: string) => { + const addUserRecord = >(key: K) => async (value: string, e: KeyboardEvent) => { const handler = (state as ExposeHandler) @@ -53,13 +54,40 @@ function ListingFragment({ state, useHandler }: StateProxy): JSX. toast.error(`用户 ${value} 已经在列表中`) return } - const user = await catcher(requestUserInfo(value)) - if (!user) { - toast.error(`用户 ${value} 不存在`) - return + + let user: Partial = undefined + + if (!e.shiftKey) { + try { + user = await requestUserInfo(value) + if (!user) { + toast.error(`用户 ${value} 不存在`) + return + } + } catch (err) { + console.error(err) + toast.error( +
+
+ 添加用户 {value} 失败: {err?.message ?? String(err)} +
+ (使用 Shift Enter 可略过用户验证) +
+ ) + } + } else { + const mid = parseInt(value) + if (isNaN(mid)) { + toast.error(`添加用户 ${value} 失败: 不是有效的数字`) + return + } + user = { mid, name: '用戶' + value } } + + state[key].push({ id: user.mid.toString(), name: user.name, addedDate: new Date().toLocaleDateString() }) handler.set(key, state[key] as any) + toast.success(`添加用户 ${value} 成功`) } diff --git a/tests/features/jimaku.spec.ts b/tests/features/jimaku.spec.ts index 43e9792f..99b8d6fa 100644 --- a/tests/features/jimaku.spec.ts +++ b/tests/features/jimaku.spec.ts @@ -204,6 +204,79 @@ test('測試房間名單列表(黑名單/白名單)', async ({ room, content, co }) +test('测试添加同传用户名单/黑名单', async ({ content, context, tabUrl, room }) => { + + const subtitleList = content.locator('#subtitle-list') + await expect(subtitleList).toBeVisible() + + const user1 = 1 + const user2 = 2 + + const txt = '由 playwright 工具發送' + const subtitles = content.locator('#subtitle-list > p').filter({ hasText: txt }) + + const withBracket = `【${txt}】` + + logger.info('正在測試同傳字幕正則覆蓋....') + + await room.sendDanmaku(withBracket, user1) + await room.sendDanmaku(withBracket, user1) + await room.sendDanmaku(txt, user1) // this should not be covered + + await expect(subtitles).toHaveCount(2) + + logger.info('正在測試添加同傳用戶名單...') + + const settingsPage = await context.newPage() + await settingsPage.goto(tabUrl('settings.html'), { waitUntil: 'domcontentloaded' }) + + await settingsPage.getByText('功能设定').click() + await settingsPage.getByText('同传名单设定').click() + + await settingsPage.getByTestId('tongchuan-mans-input').fill(user1.toString()) + await settingsPage.getByTestId('tongchuan-mans-input').press('Shift+Enter') + await settingsPage.getByText(`添加用户 ${user1} 成功`).waitFor({ state: 'visible' }) + + await settingsPage.getByText('保存设定').click() + + await room.page.bringToFront() + await subtitleList.waitFor({ state: 'visible' }) + await room.sendDanmaku(withBracket, user1) + await room.sendDanmaku(withBracket, user1) + await room.sendDanmaku(txt, user1) // this should be covered + + await expect(subtitles).toHaveCount(3) + + logger.info('正在測試添加同傳用戶黑名單...') + + await settingsPage.bringToFront() + await settingsPage.getByTestId('tongchuan-blacklist-input').fill(user2.toString()) + await settingsPage.getByTestId('tongchuan-blacklist-input').press('Shift+Enter') + await settingsPage.getByText(`添加用户 ${user2} 成功`).waitFor({ state: 'visible' }) + + await settingsPage.getByText('保存设定').click() + + await room.page.bringToFront() + await subtitleList.waitFor({ state: 'visible' }) + await room.sendDanmaku(withBracket, user2) // should be blacklisted + await room.sendDanmaku(withBracket, user2) // should be blacklisted + + await expect(subtitles).toHaveCount(0) + + // github host runner does not have access to bilibili user api + if (!process.env.CI) { + + logger.info('正在嘗試添加不存在用戶...') + + await settingsPage.bringToFront() + await settingsPage.getByTestId('tongchuan-mans-input').fill('1234569') + await settingsPage.getByTestId('tongchuan-mans-input').press('Enter') + + await expect(settingsPage.getByText('用户 1234569 不存在')).toBeVisible() + + } +}) + test('測試全屏時字幕區塊是否存在 + 顯示切換', async ({ content: p, room }) => { test.skip(await room.isThemePage(), '此測試不適用於大海報房間') diff --git a/tests/helpers/bilibili-page.ts b/tests/helpers/bilibili-page.ts index be5b135f..b08c0da1 100644 --- a/tests/helpers/bilibili-page.ts +++ b/tests/helpers/bilibili-page.ts @@ -90,9 +90,10 @@ export class BilibiliPage implements AsyncDisposable { /** * 模拟发送弹幕。 - * @param danmaku 弹幕内容。 + * @param danmaku 弹幕内容 + * @param uid 弹幕发送者用户ID。 */ - async sendDanmaku(danmaku: string): Promise { + async sendDanmaku(danmaku: string, uid: number = randomNumber()): Promise { const f = await this.getContentLocator() await sendFakeBLiveMessage(f, 'DANMU_MSG', { cmd: 'DANMU_MSG', @@ -118,7 +119,7 @@ export class BilibiliPage implements AsyncDisposable { ], danmaku, [ - randomNumber(), + uid, "username", undefined, undefined, @@ -154,8 +155,9 @@ export class BilibiliPage implements AsyncDisposable { * @param user 用户名。 * @param price 价格。 * @param message 消息内容。 + * @param uid 用户ID。 */ - async sendSuperChat(user: string, price: number, message: string): Promise { + async sendSuperChat(user: string, price: number, message: string, uid: number = randomNumber()): Promise { const f = await this.getContentLocator() await sendFakeBLiveMessage(f, 'SUPER_CHAT_MESSAGE', { cmd: 'SUPER_CHAT_MESSAGE', @@ -169,7 +171,7 @@ export class BilibiliPage implements AsyncDisposable { name_color: '#444444', uname: user }, - uid: randomNumber(), + uid: uid, price: price, message: message, start_time: Date.now() From 6bc98e5109107114d5b7ff46e101b69d11e5418f Mon Sep 17 00:00:00 2001 From: Eric Lam Date: Wed, 13 Mar 2024 10:24:53 +0800 Subject: [PATCH 04/11] =?UTF-8?q?Fixed=20#70:=20=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=20CSS=20=E8=A6=86=E7=9B=96=E4=BA=86=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E7=BD=91=E9=A1=B5/=E8=84=9A=E6=9C=AC=E7=9A=84=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changed to use react-shadow-root to fix css out scoped * added new test case and optimize workflows * fixed 屏蔽選中同傳發送者 not work * fixed shadow root making children disappeared * reshaped shadow-root component * fixed full screen css not applied and optimize context menu in full screen * changed non-vtb-room select strategy to random --- .github/workflows/build-test.yml | 13 +++- .github/workflows/partial-test.yml | 22 +++++-- .gitignore | 2 + package.json | 1 + pnpm-lock.yaml | 15 +++++ src/components/DraggableFloatingButton.tsx | 2 +- src/components/ShadowRoot.tsx | 62 ++++++++++++------- src/components/ShadowStyle.tsx | 20 ++++++ src/components/TailwindScope.tsx | 5 +- src/contents/index/components/ButtonList.tsx | 6 +- src/contexts/ShadowRootContext.ts | 6 ++ src/features/jimaku/components/ButtonArea.tsx | 2 +- src/features/jimaku/components/JimakuArea.tsx | 25 +++++--- src/features/jimaku/components/JimakuLine.tsx | 2 +- src/features/jimaku/components/JimakuList.tsx | 53 +++++++++++++--- src/features/jimaku/index.tsx | 5 +- .../components/SuperChatFloatingButton.tsx | 14 ++--- src/logger.ts | 13 +--- tests/content.spec.ts | 37 ++++++++++- tests/features/jimaku.spec.ts | 39 ++++++++++-- tests/features/superchat.spec.ts | 4 +- tests/helpers/bilibili-page.ts | 2 +- 22 files changed, 262 insertions(+), 88 deletions(-) create mode 100644 src/components/ShadowStyle.tsx create mode 100644 src/contexts/ShadowRootContext.ts diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 317a826f..33d60945 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -56,6 +56,8 @@ jobs: theme: ['', '-theme'] steps: - uses: actions/checkout@v4 + - name: Ensure No @Scoped Test + run: grep -r --include "*.spec.ts" "@scoped" && echo "please remove @scoped from tests" && exit 1 || echo "No @scoped tests found" - name: Download artifacts uses: actions/download-artifact@v4 id: download @@ -74,8 +76,15 @@ jobs: cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Cache playwright binaries + uses: actions/cache@v3 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }} - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps + if: steps.playwright-cache.outputs.cache-hit != 'true' - name: Setup Project id: project run: | @@ -90,7 +99,7 @@ jobs: fi pnpm test -- --project=${{ steps.project.outputs.project }} - name: Upload Test Results - if: always() + if: always() && steps.test.conclusion != 'skipped' uses: actions/upload-artifact@v4 with: name: ${{ steps.project.outputs.project }}-test-results @@ -99,7 +108,7 @@ jobs: playwright-report/ if-no-files-found: ignore - name: Upload Results To R2 - if: ${{ failure() && steps.test.conclusion == 'failure' }} + if: failure() && steps.test.conclusion == 'failure' uses: eric2788/r2-upload-action@master id: upload continue-on-error: true diff --git a/.github/workflows/partial-test.yml b/.github/workflows/partial-test.yml index 4e05465d..fb307df5 100644 --- a/.github/workflows/partial-test.yml +++ b/.github/workflows/partial-test.yml @@ -37,12 +37,24 @@ jobs: cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build And Package Extensions - run: pnpm build --zip --target=${{ matrix.browser }}-mv3 - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps + - name: Build Extension and Prepare Tests + run: pnpm test:rebuild + - name: Cache playwright binaries + uses: actions/cache@v3 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }} + - run: pnpm exec playwright install --with-deps + if: steps.playwright-cache.outputs.cache-hit != 'true' - name: Test id: test - run: pnpm test -- --project=${{ matrix.browser }} --grep=@scoped --pass-with-no-tests + run: | + pnpm test -- --project=${{ matrix.browser }} \ + --grep=@scoped \ + --pass-with-no-tests \ + --global-timeout=3600000 \ + --timeout=60000 \ + --max-failures=5 env: DEBUG: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 83f7f041..2816f403 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ out/ build/ dist/ *.local.yml +*-local.tsx +*-local.ts # plasmo - https://www.plasmo.com .plasmo diff --git a/package.json b/package.json index ce7f3688..9c56d813 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "react-dom": "18.2.0", "react-joyride": "^2.7.4", "react-rnd": "^10.4.1", + "react-shadow-root": "^6.2.0", "react-state-proxy": "^1.4.11", "sonner": "^1.4.3", "tailwindcss": "^3.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b86debb8..78f3f06c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ dependencies: react-rnd: specifier: ^10.4.1 version: 10.4.1(react-dom@18.2.0)(react@18.2.0) + react-shadow-root: + specifier: ^6.2.0 + version: 6.2.0(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) react-state-proxy: specifier: ^1.4.11 version: 1.4.11 @@ -6259,6 +6262,18 @@ packages: tslib: 2.3.1 dev: false + /react-shadow-root@6.2.0(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ruFCVvbzFjWordVAZnrIcVNZpHz7whpNW2sRrAYBPmRpE9uTyN0Xt/1lkw52ktAmoSUkEptF2hjfISHmPirKTg==} + peerDependencies: + prop-types: '>=15.6.0' + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-state-proxy@1.4.11: resolution: {integrity: sha512-h8Fax7iujADk4/1PoRWD9160OYV2FjHmJlVmetFgSW6j3vvRqPO24KkFZtA0SrBURVbTfEhRt6VZt1W5jf71nQ==} engines: {node: '>=14.13.1'} diff --git a/src/components/DraggableFloatingButton.tsx b/src/components/DraggableFloatingButton.tsx index 9e27e8d0..a79ed804 100644 --- a/src/components/DraggableFloatingButton.tsx +++ b/src/components/DraggableFloatingButton.tsx @@ -78,7 +78,7 @@ function DraggableFloatingButton(props: DraggableFloatingButtonProps): JSX.Eleme height: 25, }} > -
+
diff --git a/src/components/ShadowRoot.tsx b/src/components/ShadowRoot.tsx index bbbdea00..37a0041b 100644 --- a/src/components/ShadowRoot.tsx +++ b/src/components/ShadowRoot.tsx @@ -1,27 +1,41 @@ -import { useEffect, useRef } from 'react'; - -/** - * Renders a component that attaches a shadow root to a DOM element and sets its inner HTML to a slot. - * @param {Object} props - The component props. - * @param {React.ReactNode} props.children - The content to be rendered inside the shadow root. - * @returns {React.ReactElement} The rendered component. - * @example - * // Usage: - * - *
Hello, world!
- * - */ -function ShadowRoot({ children }: { children: React.ReactNode }): JSX.Element { - const ref = useRef(null) - - useEffect(() => { - if (ref.current) { - const shadowRoot = ref.current.attachShadow({ mode: 'open' }) - shadowRoot.innerHTML = '' - } - }, []); - - return
{children}
+import { useEffect, useRef, useState } from "react" +import ReactShadowRoot from "react-shadow-root" +import ShadowRootContext from "~contexts/ShadowRootContext" + + +export type ShadowRootProps = { + children: React.ReactNode + styles: string[] +} + + +function ShadowRoot({ children, styles }: ShadowRootProps): JSX.Element { + + const reactShadowRoot = useRef(null) + const [shadowRoot, setShadowRoot] = useState(null) + + useEffect(() => { + + if (reactShadowRoot.current) { + setShadowRoot(reactShadowRoot.current.shadowRoot) + console.debug('ShadowRoot created') + } + + }, []) + + return ( + + {styles?.map((style, i) => )} +
+ {shadowRoot && ( + + {children} + + )} +
+
+ ) + } export default ShadowRoot \ No newline at end of file diff --git a/src/components/ShadowStyle.tsx b/src/components/ShadowStyle.tsx new file mode 100644 index 00000000..3df1712d --- /dev/null +++ b/src/components/ShadowStyle.tsx @@ -0,0 +1,20 @@ +import { useContext, useEffect, useRef, useState, type RefObject } from "react"; +import { createPortal } from "react-dom"; +import ShadowRootContext from "~contexts/ShadowRootContext"; + + + +function ShadowStyle({ children }: { children: React.ReactNode }): JSX.Element { + + const host = useContext(ShadowRootContext) + + if (!host) { + console.warn('No ShadowRoot found: ShadowStyle must be used inside a ShadowRoot') + } + + return host ? createPortal(, host) : <> + +} + + +export default ShadowStyle \ No newline at end of file diff --git a/src/components/TailwindScope.tsx b/src/components/TailwindScope.tsx index 682fc8f6..68a0c9f9 100644 --- a/src/components/TailwindScope.tsx +++ b/src/components/TailwindScope.tsx @@ -1,5 +1,5 @@ -import ShadowRoot from './ShadowRoot'; import styleText from 'data-text:~style.css'; +import ShadowRoot from '~components/ShadowRoot'; /** * Renders a component that applies a Tailwind CSS scope to its children. @@ -12,8 +12,7 @@ import styleText from 'data-text:~style.css'; function TailwindScope({ children, dark }: { children: React.ReactNode, dark?: boolean }): JSX.Element { return (
- - + {children}
diff --git a/src/contents/index/components/ButtonList.tsx b/src/contents/index/components/ButtonList.tsx index 4a924c3b..e07e89da 100644 --- a/src/contents/index/components/ButtonList.tsx +++ b/src/contents/index/components/ButtonList.tsx @@ -1,5 +1,5 @@ import { Button } from "@material-tailwind/react" -import { useContext } from "react" +import { useCallback, useContext } from "react" import { sendForward } from "~background/forwards" import ContentContext from "~contexts/ContentContexts" import { usePopupWindow } from "~hooks/window" @@ -18,9 +18,9 @@ function ButtonList(): JSX.Element { height: 450 }) - const restart = () => sendForward('background', 'redirect', { target: 'content-script', command: 'command', body: { command: 'restart' }, queryInfo: { url: '*://live.bilibili.com/*' } }) + const restart = useCallback(() => sendForward('background', 'redirect', { target: 'content-script', command: 'command', body: { command: 'restart' }, queryInfo: { url: '*://live.bilibili.com/*' } }), []) const addBlackList = () => confirm(`确定添加房间 ${info.room}${info.room === info.shortRoom ? '' : `(${info.shortRoom})`} 到黑名单?`) && sendMessager('add-black-list', { roomId: info.room }) - const openSettings = () => sendMessager('open-tab', { tab: 'settings' }) + const openSettings = useCallback(() => sendMessager('open-tab', { tab: 'settings' }), []) const openMonitor = createPopupWindow(`stream.html`, { roomId: info.room, title: info.title, diff --git a/src/contexts/ShadowRootContext.ts b/src/contexts/ShadowRootContext.ts new file mode 100644 index 00000000..06395301 --- /dev/null +++ b/src/contexts/ShadowRootContext.ts @@ -0,0 +1,6 @@ +import { createContext } from "react"; + + +const ShadowRootContext = createContext(null) + +export default ShadowRootContext \ No newline at end of file diff --git a/src/features/jimaku/components/ButtonArea.tsx b/src/features/jimaku/components/ButtonArea.tsx index fa62476d..89e9a772 100644 --- a/src/features/jimaku/components/ButtonArea.tsx +++ b/src/features/jimaku/components/ButtonArea.tsx @@ -38,7 +38,7 @@ function ButtonArea({ clearJimaku, jimakus }: ButtonAreaProps): JSX.Element { return ( {show && ( -
+
删除所有字幕记录 diff --git a/src/features/jimaku/components/JimakuArea.tsx b/src/features/jimaku/components/JimakuArea.tsx index 04612fd1..375365fe 100644 --- a/src/features/jimaku/components/JimakuArea.tsx +++ b/src/features/jimaku/components/JimakuArea.tsx @@ -1,10 +1,9 @@ -import { Fragment, useContext, useEffect, useMemo, useState } from 'react'; +import { Fragment, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import styled from '@emotion/styled'; import { Rnd } from 'react-rnd'; import ConditionalWrapper from '~components/ConditionalWrapper'; -import JimakuFeatureContext from '~contexts/JimakuFeatureContext'; import ContentContext from '~contexts/ContentContexts'; +import JimakuFeatureContext from '~contexts/JimakuFeatureContext'; import { useWebScreenChange } from '~hooks/bilibili'; import { useTeleport } from '~hooks/teleport'; import type { JimakuSchema } from '~settings/features/jimaku/components/JimakuFragment'; @@ -12,8 +11,10 @@ import { rgba } from '~utils/misc'; import type { Jimaku } from "./JimakuLine"; import JimakuList from './JimakuList'; import JimakuVisibleButton from './JimakuVisibleButton'; +import ShadowStyle from '~components/ShadowStyle'; +import TailwindScope from '~components/TailwindScope'; -const createJimakuScope = (jimakuStyle: JimakuSchema) => styled.div` +const createAreaStyleSheet = (jimakuStyle: JimakuSchema) => ` .subtitle-normal::-webkit-scrollbar { width: 5px; @@ -52,7 +53,7 @@ function JimakuArea({ jimaku }: JimakuAreaProps): JSX.Element { const dev = settings['settings.developer'] - const Area = useMemo(() => createJimakuScope(jimakuStyle), [jimakuStyle]) + const areaCssText = useMemo(() => createAreaStyleSheet(jimakuStyle), [jimakuStyle]) useEffect(() => { // make danmaku chat list peer with video @@ -95,15 +96,21 @@ function JimakuArea({ jimaku }: JimakuAreaProps): JSX.Element { subTitleStyle.backgroundColor = rgba(jimakuStyle.backgroundColor, (jimakuStyle.backgroundOpacity / 100)) } + const shouldPutIntoLivePlayer = screenStatus !== 'normal' || isTheme + return ( - + + {areaCssText} - + {screenStatus !== 'normal' && setVisible(!visible)} dev={dev} />} diff --git a/src/features/jimaku/components/JimakuLine.tsx b/src/features/jimaku/components/JimakuLine.tsx index d9f6f469..7d50aa01 100644 --- a/src/features/jimaku/components/JimakuLine.tsx +++ b/src/features/jimaku/components/JimakuLine.tsx @@ -24,7 +24,7 @@ function JimakuLine({ item, show, index, observer }: JimakuLineProps): JSX.Eleme const ref = useRowOptimizer(observer) return ( -

+

{item.text}

) diff --git a/src/features/jimaku/components/JimakuList.tsx b/src/features/jimaku/components/JimakuList.tsx index 963e8db5..924a0286 100644 --- a/src/features/jimaku/components/JimakuList.tsx +++ b/src/features/jimaku/components/JimakuList.tsx @@ -1,16 +1,19 @@ import type React from "react"; -import { Item, type ItemParams, Menu, useContextMenu } from 'react-contexify'; +import { Item, Menu, useContextMenu, type ItemParams } from 'react-contexify'; import { toast } from 'sonner/dist'; import { useKeepBottom } from '~hooks/keep-bottom'; import { useScrollOptimizer } from '~hooks/optimizer'; import { getSettingStorage, setSettingStorage } from '~utils/storage'; -import JimakuLine from './JimakuLine'; import type { Jimaku } from "./JimakuLine"; +import JimakuLine from './JimakuLine'; +import styleText from 'data-text:react-contexify/dist/ReactContexify.css'; +import { useContext, useRef } from "react"; import 'react-contexify/dist/ReactContexify.css'; -import { useContext } from "react"; import JimakuFeatureContext from "~contexts/JimakuFeatureContext"; +import type { UserRecord } from "~settings/features/jimaku/components/ListingFragment"; +import ShadowStyle from "~components/ShadowStyle"; export type JimakuListProps = { @@ -22,7 +25,7 @@ export type JimakuListProps = { function JimakuList(props: JimakuListProps): JSX.Element { - const { jimakuZone: jimakuStyle } = useContext(JimakuFeatureContext) + const { jimakuZone: jimakuStyle, listingZone } = useContext(JimakuFeatureContext) const { jimaku, style } = props @@ -37,15 +40,44 @@ function JimakuList(props: JimakuListProps): JSX.Element { }) const displayContextMenu = (jimaku: Jimaku) => (e: React.MouseEvent) => { - show({ event: e, props: jimaku }) + + const parent = element.current + + if (!parent) { + console.warn('cannot show menu: parent is not mounted yet') + return + } + + const rootElement = (parent.getRootNode() as ShadowRoot).host; + const inFullScreen = rootElement.clientHeight === 0 + const mouseX = inFullScreen ? parent.clientWidth / 2 + 10 : e.clientX + const mouseY = inFullScreen ? (e.screenY - parent.getBoundingClientRect().top) - Math.max(1400 - window.innerHeight, 0) : e.clientY + + console.log( + e.screenY, + parent.getBoundingClientRect().top, + window.innerHeight + ) + + show({ + event: e, + props: jimaku, + position: { + x: mouseX, + y: mouseY + } + }) } - const blockUser = async ({ props }: ItemParams) => { + const blockUser = async ({ props }: ItemParams) => { if (!window.confirm(`是否屏蔽 ${props.uname}(${props.uid}) 的同传弹幕?`)) return const settings = await getSettingStorage('settings.features') - settings.jimaku.listingZone.tongchuanBlackList.push({ id: props.uid, name: props.uname, addedDate: new Date().toLocaleDateString() }) + const record: UserRecord = { id: props.uid.toString(), name: props.uname, addedDate: new Date().toLocaleDateString() } + settings.jimaku.listingZone.tongchuanBlackList.push(record) await setSettingStorage('settings.features', settings) - toast.success(`已成功屏蔽 ${props.uname}(${props.uid}) 的同传弹幕`) + // add blacklist for current (no need restart) + listingZone.tongchuanBlackList.push(record) + toast.success(`已不再接收 ${props.uname}(${props.uid}) 的同传弹幕`) } @@ -56,7 +88,7 @@ function JimakuList(props: JimakuListProps): JSX.Element { id="subtitle-list" ref={ref} style={style} - className="z-[9999] overflow-y-auto overflow-x-hidden w-full subtitle-normal"> + className="z-[3000] overflow-y-auto overflow-x-hidden w-full subtitle-normal"> {jimaku.map((item, i) => ( ))} - + {styleText} + 屏蔽选中同传发送者
diff --git a/src/features/jimaku/index.tsx b/src/features/jimaku/index.tsx index faf16d72..795d3877 100644 --- a/src/features/jimaku/index.tsx +++ b/src/features/jimaku/index.tsx @@ -5,13 +5,14 @@ import { toast } from 'sonner/dist'; import { isNativeVtuber } from "~api/bilibili"; import OfflineRecordsProvider from '~components/OfflineRecordsProvider'; import TailwindScope from '~components/TailwindScope'; -import JimakuFeatureContext from "~contexts/JimakuFeatureContext"; import ContentContext from "~contexts/ContentContexts"; +import JimakuFeatureContext from "~contexts/JimakuFeatureContext"; import { parseJimaku } from "~utils/bilibili"; import { retryCatcher } from "~utils/fetch"; +import { findOrCreateElement } from '~utils/react-node'; import type { FeatureHookRender } from ".."; import JimakuCaptureLayer from './components/JimakuCaptureLayer'; -import { findOrCreateElement } from '~utils/react-node'; + function warnIfAdaptive() { diff --git a/src/features/superchat/components/SuperChatFloatingButton.tsx b/src/features/superchat/components/SuperChatFloatingButton.tsx index 705913da..de4f9c71 100644 --- a/src/features/superchat/components/SuperChatFloatingButton.tsx +++ b/src/features/superchat/components/SuperChatFloatingButton.tsx @@ -1,11 +1,9 @@ import { Item, Menu, useContextMenu } from 'react-contexify'; - -import DraggableFloatingButton from '~components/DraggableFloatingButton'; -import ShadowRoot from '~components/ShadowRoot'; import styleText from 'data-text:react-contexify/dist/ReactContexify.css'; -import { useContext } from 'react'; -import SuperChatFeatureContext from '~contexts/SuperChatFeatureContext'; +import { Fragment, useContext } from 'react'; +import DraggableFloatingButton from '~components/DraggableFloatingButton'; import BJFThemeDarkContext from '~contexts/BLiveThemeDarkContext'; +import SuperChatFeatureContext from '~contexts/SuperChatFeatureContext'; export type SuperChatFloatingButtonProps = { children: React.ReactNode @@ -13,7 +11,7 @@ export type SuperChatFloatingButtonProps = { function SuperChatFloatingButton({ children }: SuperChatFloatingButtonProps): JSX.Element { - const [ themeDark ] = useContext(BJFThemeDarkContext) + const [themeDark] = useContext(BJFThemeDarkContext) const { floatingButtonColor } = useContext(SuperChatFeatureContext) const { show } = useContextMenu({ @@ -21,7 +19,7 @@ function SuperChatFloatingButton({ children }: SuperChatFloatingButtonProps): JS }) return ( - + show({ event: e })} className='hover:brightness-90 duration-150 dark:bg-gray-700 dark:hover:bg-gray-800 text-white'>
@@ -35,7 +33,7 @@ function SuperChatFloatingButton({ children }: SuperChatFloatingButtonProps): JS {''} {children} - + ) } diff --git a/src/logger.ts b/src/logger.ts index 30fbb1aa..18ebb0fb 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,16 +1,9 @@ - -function ciOnly(func: any) { - return function (...data: any[]) { - if (process.env.CI || process.env.NODE_ENV === 'development') { - func(...data) - } - } -} +const debug = process.env.CI || process.env.NODE_ENV !== 'production' console.info = console.info.bind(console, '[bilibili-vup-stream-enhancer]') console.warn = console.warn.bind(console, '[bilibili-vup-stream-enhancer]') console.error = console.error.bind(console, '[bilibili-vup-stream-enhancer]') console.log = console.log.bind(console, '[bilibili-vup-stream-enhancer]') -console.debug = ciOnly(console.debug.bind(console, '[bilibili-vup-stream-enhancer]')) -console.trace = ciOnly(console.trace.bind(console, '[bilibili-vup-stream-enhancer]')) \ No newline at end of file +console.debug = debug ? console.debug.bind(console, '[bilibili-vup-stream-enhancer]') : () => { } +console.trace = debug ? console.trace.bind(console, '[bilibili-vup-stream-enhancer]') : () => { } \ No newline at end of file diff --git a/tests/content.spec.ts b/tests/content.spec.ts index e68bb0f8..20dcd849 100644 --- a/tests/content.spec.ts +++ b/tests/content.spec.ts @@ -1,6 +1,7 @@ import { test, expect } from "./fixtures/content"; import logger from "./helpers/logger"; import { receiveOneBLiveMessage } from "./utils/bilibili"; +import { random } from "./utils/misc"; test('測試主元素是否存在', async ({ content }) => { @@ -11,6 +12,40 @@ test('測試主元素是否存在', async ({ content }) => { await expect(csui.locator('#bjf-root')).toBeAttached() }) +test('测试扩展CSS有否影响到外围', async ({ content, room, isThemeRoom }) => { + + test.skip(isThemeRoom, '此測試不適用於大海報房間') + + logger.info('正在測試 CSS 有否外溢...') + + await content.evaluate(() => { + const span = document.createElement('span') + span.id = 'bjf-test-span' + span.textContent = '测试 CSS 有否外溢' + span.className = "text-red-500" + document.body.append(span) + }) + + const span = content.locator('#bjf-test-span') + await expect(span).toBeVisible() + await expect(span).toHaveCSS('color', 'rgb(0, 0, 0)') + + logger.info('正在測試 CSS 有否在 shadow root 内生效...') + + await content.evaluate(() => { + const root = document.querySelector('bjf-csui').shadowRoot + const span = document.createElement('span') + span.id = 'bjf-test-span-inside' + span.className = "text-red-500" + span.textContent = '测试 CSS 有否在 shadow root 内生效' + root.append(span) + }) + + const spanInside = content.locator('#bjf-test-span-inside') + await expect(spanInside).toBeVisible() + await expect(spanInside).toHaveCSS('color', 'rgb(244, 67, 54)') +}) + test('測試貼邊浮動按鈕和主菜單區塊是否存在', async ({ content }) => { @@ -213,7 +248,7 @@ test('测试仅限虚拟主播', async ({ context, room, tabUrl, api }) => { const nonVtbRooms = await api.getLiveRooms(1, 11) // 获取知识分区直播间 test.skip(nonVtbRooms.length === 0, '没有知识分区直播间') - await room.enterToRoom(nonVtbRooms[0]) + await room.enterToRoom(random(nonVtbRooms)) const content = await room.getContentLocator() const button = content.getByText('功能菜单') diff --git a/tests/features/jimaku.spec.ts b/tests/features/jimaku.spec.ts index 99b8d6fa..5e3b0041 100644 --- a/tests/features/jimaku.spec.ts +++ b/tests/features/jimaku.spec.ts @@ -277,9 +277,40 @@ test('测试添加同传用户名单/黑名单', async ({ content, context, tabU } }) -test('測試全屏時字幕區塊是否存在 + 顯示切換', async ({ content: p, room }) => { +test('測試右鍵同傳字幕來屏蔽同傳發送者', async ({ content, room, page }) => { - test.skip(await room.isThemePage(), '此測試不適用於大海報房間') + await content.locator('#subtitle-list').waitFor({ state: 'visible' }) + + const user1 = 1 + const user2 = 2 + const txt = '由 playwright 工具發送' + + const subtitles = content.locator('#subtitle-list > p').filter({ hasText: txt }) + + logger.info('正在測試同傳字幕發送...') + await room.sendDanmaku(`【${txt}】`, user1) + await expect(subtitles).toHaveCount(1) + + logger.info('正在測試右鍵屏蔽同傳發送者...') + page.once('dialog', (dialog) => dialog.accept()) + await subtitles.nth(0).click({ button: 'right' }) + await content.getByText('屏蔽选中同传发送者').click() + + await content.getByText(/已不再接收 (.+) 的同传弹幕/).waitFor({ state: 'visible' }) + + logger.info('正在測試屏蔽後是否生效...') + + await room.sendDanmaku(`【${txt}】`, user1) // this should be blocked + await expect(subtitles).toHaveCount(1) + + await room.sendDanmaku(`【${txt}】`, user2) // this should not be blocked + await expect(subtitles).toHaveCount(2) + +}) + +test('測試全屏時字幕區塊是否存在 + 顯示切換', async ({ content: p, room, isThemeRoom }) => { + + test.skip(isThemeRoom, '此測試不適用於大海報房間') const area = p.locator('#jimaku-area div#subtitle-list') await expect(area).toBeAttached() @@ -390,7 +421,5 @@ async function ensureButtonListVisible(p: PageFrame) { async function getButtonList(content: PageFrame): Promise { await ensureButtonListVisible(content) await content.locator('#jimaku-area').waitFor({ state: 'visible' }) - const main = await content.locator('#jimaku-area div > div > div:nth-child(3) > button').all() - const theme = content.locator('#jimaku-area > div > div > div > button').all() - return main.length ? main : await theme + return await content.getByTestId('subtitle-button-list').locator('button').all() } \ No newline at end of file diff --git a/tests/features/superchat.spec.ts b/tests/features/superchat.spec.ts index 46d04a90..69ecc6cb 100644 --- a/tests/features/superchat.spec.ts +++ b/tests/features/superchat.spec.ts @@ -18,7 +18,7 @@ test('測試功能元素是否存在', async ({ content }) => { }) -test('測試浮動按鈕和醒目留言記錄列表是否存在', async ({ content, page }) => { +test('測試浮動按鈕和醒目留言記錄列表是否存在', async ({ content }) => { logger.info('正在測試浮動按鈕是否存在') const section = content.locator('bjf-csui section#bjf-feature-superchat') @@ -86,7 +86,7 @@ test('測試寫入醒目留言和醒目留言按鈕 (插入/刪除/下載)', asy test('測試拖拽按鈕', async ({ content }) => { - const dragPoint = content.locator('bjf-csui section#bjf-feature-superchat > div > div') + const dragPoint = content.locator('bjf-csui section#bjf-feature-superchat [data-type="draggable-button"]') const p1 = content.locator('#rank-list-ctnr-box') const p2 = content.locator('#head-info-vm') diff --git a/tests/helpers/bilibili-page.ts b/tests/helpers/bilibili-page.ts index b08c0da1..da671153 100644 --- a/tests/helpers/bilibili-page.ts +++ b/tests/helpers/bilibili-page.ts @@ -120,7 +120,7 @@ export class BilibiliPage implements AsyncDisposable { danmaku, [ uid, - "username", + `用户 ${uid}`, undefined, undefined, undefined, From da4ede5ab22725a65e9b4e654247a6ed064917c5 Mon Sep 17 00:00:00 2001 From: eric2788 Date: Wed, 13 Mar 2024 11:37:29 +0800 Subject: [PATCH 05/11] revised pull request trigger for develop --- .github/workflows/build-test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 33d60945..58acfa2b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,6 +12,11 @@ on: - '.github/workflows/build-test.yml' pull_request: branches: [develop] + types: + - opened + - reopened + - ready_for_review + - review_requested workflow_dispatch: inputs: debug: From 89e8358eede958516247805e6b2d08a5acf7d7db Mon Sep 17 00:00:00 2001 From: eric2788 Date: Wed, 13 Mar 2024 11:56:15 +0800 Subject: [PATCH 06/11] updated documentation - shadow components - CONTRIBUTING.md --- CONTRIBUTING.md | 28 ++++++++++++++++++++++ src/components/ShadowRoot.tsx | 43 +++++++++++++++++++++++++--------- src/components/ShadowStyle.tsx | 22 +++++++++++++++++ 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 183dd544..3f3c62ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,10 +7,12 @@ - [事前准备](#事前准备) - [贡献流程](#贡献流程) - [程式码规范](#程式码规范) + - [提交规范](#提交规范) - [项目架构](#项目架构) - [源代码](#源代码) - [测试代码](#测试代码) - [快速开始](#快速开始) + - [关于自动测试](#关于自动测试) - [问题回报](#问题回报) - [讨论与支援](#讨论与支援) @@ -43,6 +45,13 @@ > 请放心,我们会在您提交 Pull Request 时进行代码检查,如有任何问题我们会在检查时提出。 +### 提交规范 + +- 本项目在 commit message 上没有限制, 清晰明了即可 +- PR分支请确保是基于 `develop` 分支创建,且分支名称应该为 `[类型]/[issue号]-[概要]` 的格式;例如 `feature/123-new-feature` +- 请确保你的每一条 commits 都有意义,如有必要请使用 `git rebase` 合并 commits +- 如果你的 PR 是为了修复某个 issue,请在 PR 描述中写明 `Fixed|Resolved #issue号`,以便自动连结 issue + ## 项目架构 本项目的架构如下: @@ -109,6 +118,25 @@ tests/ - 最后,运行 `pnpm test` 运行测试 (或者用 playwright vscode 插件运行测试) - 每次更新后可以运行 `pnpm test:rebuild` 重新编译并部署测试环境 +### 关于自动测试 + +本项目的自动测试分为两种类型:快速测试和完整测试。 + +快速测试: 只测试所有刻上了 `@scoped` 标签的测试用例。 + +完整测试: 从头开始测试所有测试用例,进行前需要先移除所有 `@scoped` 标签。 + +- 当以下格式的分支被推送时,会触发快速测试: + - `feature/**`: 新功能分支 + - `hotfix/**`: 修复分支 + +- 当向 `develop` 分支提交 PR 时,以下条件会触发完整测试: + - 当用户开启了 PR 时 + - 当用户从草稿状态转为正式状态时 + - 当用户请求检查时 + +- 必要时,我们会在 PR 检查时手动触发完整测试。 + #### 入门指南 请参阅 [`docs/`](/docs/) 下的 `.md` 文件来查看详细的代码编写流程。 diff --git a/src/components/ShadowRoot.tsx b/src/components/ShadowRoot.tsx index 37a0041b..a5ada5d8 100644 --- a/src/components/ShadowRoot.tsx +++ b/src/components/ShadowRoot.tsx @@ -9,23 +9,45 @@ export type ShadowRootProps = { } +/** + * Renders a component that creates a shadow root and provides it as a context value. + * + * @component + * @example + * // Example usage of ShadowRoot component + * function App() { + * const styles = ['body { background-color: lightgray; }']; + * return ( + *
+ *

App

+ * + *

ShadowRoot Content

+ *
+ *
+ * ); + * } + * + * @param {Object} props - The component props. + * @param {ReactNode} props.children - The content to be rendered inside the shadow root. + * @param {string[]} props.styles - An array of CSS styles to be applied to the shadow root. + * @returns {JSX.Element} The rendered ShadowRoot component. + */ function ShadowRoot({ children, styles }: ShadowRootProps): JSX.Element { - - const reactShadowRoot = useRef(null) - const [shadowRoot, setShadowRoot] = useState(null) + const reactShadowRoot = useRef(null); + const [shadowRoot, setShadowRoot] = useState(null); useEffect(() => { - if (reactShadowRoot.current) { - setShadowRoot(reactShadowRoot.current.shadowRoot) - console.debug('ShadowRoot created') + setShadowRoot(reactShadowRoot.current.shadowRoot); + console.debug('ShadowRoot created'); } - - }, []) + }, []); return ( - {styles?.map((style, i) => )} + {styles?.map((style, i) => ( + + ))}
{shadowRoot && ( @@ -34,8 +56,7 @@ function ShadowRoot({ children, styles }: ShadowRootProps): JSX.Element { )}
- ) - + ); } export default ShadowRoot \ No newline at end of file diff --git a/src/components/ShadowStyle.tsx b/src/components/ShadowStyle.tsx index 3df1712d..4364619e 100644 --- a/src/components/ShadowStyle.tsx +++ b/src/components/ShadowStyle.tsx @@ -4,6 +4,28 @@ import ShadowRootContext from "~contexts/ShadowRootContext"; +/** + * promote a style element to the root level of ShadowRoot. + * @param {Object} props - The component props. + * @param {React.ReactNode} props.children - The children to be promoted inside the style element. + * @returns {JSX.Element} - The rendered style element. + * + * @example + * import { ShadowStyle } from "~components/ShadowStyle"; + * + * function MyComponent() { + * return ( + * // The style will be promoted to the root level of the ShadowRoot. + * + * {` + * .my-class { + * color: red; + * } + * `} + * + * ) + * } + */ function ShadowStyle({ children }: { children: React.ReactNode }): JSX.Element { const host = useContext(ShadowRootContext) From 8a478ae3af717641fd37bb3f91b9c505f840de7c Mon Sep 17 00:00:00 2001 From: Eric Lam Date: Wed, 13 Mar 2024 19:31:31 +0800 Subject: [PATCH 07/11] =?UTF-8?q?Resolved=20#69:=20MV3=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=90=8C=E4=BC=A0=E5=AD=97=E5=B9=95=E6=82=AC=E6=B5=AE?= =?UTF-8?q?=E6=A1=86=E6=97=A0=E6=B3=95=E6=8B=89=E5=87=BA=E5=88=B0=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8=E7=94=BB=E9=9D=A2=E5=A4=96=20(#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/jimaku/components/JimakuArea.tsx | 2 +- .../jimaku/components/JimakuFragment.tsx | 35 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/features/jimaku/components/JimakuArea.tsx b/src/features/jimaku/components/JimakuArea.tsx index 375365fe..4803a02e 100644 --- a/src/features/jimaku/components/JimakuArea.tsx +++ b/src/features/jimaku/components/JimakuArea.tsx @@ -108,8 +108,8 @@ function JimakuArea({ jimaku }: JimakuAreaProps): JSX.Element { {areaCssText} = { backgroundHeight: 150, backgroundColor: '#808080', backgroundOpacity: 40, - filterUserLevel: 0 + filterUserLevel: 0, + areaDragBoundary: false } function JimakuFragment({ state, useHandler }: StateProxy): JSX.Element { - const stringHandler = useHandler>((e) => e.target.value) - const numberHandler = useHandler, number>((e) => e.target.valueAsNumber) + const str = useHandler>((e) => e.target.value) + const num = useHandler, number>((e) => e.target.valueAsNumber) + const bool = useHandler, boolean>((e) => e.target.checked) const hundredHints = return (
- + {hundredHints}
- + {hundredHints}
@@ -64,28 +69,28 @@ function JimakuFragment({ state, useHandler }: StateProxy): JSX.El ]} />
- + {hundredHints}
- +
- + {hundredHints}
- + (UL等级过滤无法应用于隐藏同传弹幕和透明度) ]} />
- +
- +
@@ -109,6 +114,14 @@ function JimakuFragment({ state, useHandler }: StateProxy): JSX.El { value: 'bottom', label: '下到上' }, ]} /> + + +
) } From 64c85c5101f1ef01e16ebf5ba4af2cd35e4a675b Mon Sep 17 00:00:00 2001 From: eric2788 Date: Wed, 13 Mar 2024 20:22:37 +0800 Subject: [PATCH 08/11] updated issue tempplates and documentations --- .github/ISSUE_TEMPLATE/bug-report.yaml | 81 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature-request.yaml | 31 ++++++++ README.md | 11 +++ 3 files changed, 123 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yaml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 00000000..db38fffe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,81 @@ +name: 问题回报 +description: 创建一个问题回报 +title: "[Bug]: " +labels: ["问题反映/修复", "求助"] +projects: ["eric2788/4"] +body: + - type: checkboxes + attributes: + label: 感谢您抽出时间填写此问题回报! + description: 在提交问题之前,请确保您已经搜索了现有的问题,以确保您的问题是独特的。 + options: + - label: 我已经搜索了现有的问题,并且确认我的问题是独特的 + required: true + - type: textarea + id: problem + attributes: + label: 问题描述 + description: 清楚简明扼要地描述你所遇到的问题 + Tip: 可以添加图片或其他文件作为补充 + validations: + required: true + - type: textarea + id: expected-result + attributes: + label: 期望结果 + description: 描述一下正常的时候理应出现的结果是如何? + Tip: 可以添加图片或其他文件作为补充 + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: 复现步骤 + description: 请提供详细的复现步骤 + Tip: 可以添加图片或其他文件作为补充 + placeholder: | + 1. 进入 '...' + 2. 点进 '....' + 3. 打开 '....' + 4. 无法使用 + validations: + required: true + - type: input + id: browser + attributes: + label: 浏览器 + description: 你所使用的浏览器以及版本? + placeholder: e.g. Chrome 版本 122.0.6261.113 (64 位元) + validations: + required: true + - type: dropdown + id: other-browsers + attributes: + label: 其他浏览器上的复现 + description: 除了你所使用的浏览器外,你还有在其他浏览器上复现了这个问题吗? + multiple: true + options: + - Chrome + - Microsoft Edge + - Opera + - Brave + - type: inpput + id: extension-version + attributes: + label: 扩展版本 + description: 你所使用的扩展版本? + placeholder: e.g. 2.0.0 + - type: textarea + id: logs + attributes: + label: 相关的日志(如有) + description: 你可以打开 F12 的控制台查找相关的日志 + validations: + required: false + - type: textarea + id: others + attributes: + label: 补充(如有) + description: 你还有其他补充吗? + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 00000000..c467131e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,31 @@ +name: 功能请求 +description: 创建一个功能请求 +title: "[功能请求]: <title>" +labels: ["功能请求/增強"] +projects: ["eric2788/4"] +body: + - type: textarea + id: feature + attributes: + label: 功能描述 + description: 请描述你所希望的功能是什么? + Tip: 可以添加图片或其他文件作为补充 + validations: + required: true + - type: textarea + id: why + attributes: + label: 为什么需要这个功能? + description: 请描述为什么你需要这个功能? + placeholder: | + e.g. 我经常需要在 '...' 时做 '....',但是现在无法做到 + Tip: 可以添加图片或其他文件作为补充 + validations: + required: true + - type: textarea + id: others + attributes: + label: 补充(如有) + description: 你还有其他补充吗? + validations: + required: false \ No newline at end of file diff --git a/README.md b/README.md index 9fef5004..fd399884 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,17 @@ - FireFox 不需要在 2024 年 6 月前强制进入 mv3 阶段。 - FireFox 目前对于 mv3 支援依然尚未完善,包括众多的BUG和未知问题。 +## 先行版本 + +安装先行版本需要手动下载及安装,且可能会有未知问题。 +如发现问题,欢迎到 issue 进行回报。(连带 `先行版本` 标签) + + +- [从 Artifacts 下载](https://github.com/eric2788/bilibili-vup-stream-enhancer/actions/workflows/build-test.yml?query=branch%3Adevelop) + +- [安装方式](https://jingyan.baidu.com/article/3065b3b6cc6cf6ffcef8a444.html) + + ## ➵ 简介 本浏览器插件透过挂接 WebSocket 为管人观众提供众多功能。 本插件虽然功能众多,但全部主要功能均为可选,你仍可为界面保持简化。 From 67200f0304c73908393a9e3d98ed5673eaec05f5 Mon Sep 17 00:00:00 2001 From: eric2788 <tsukiko852@gmail.com> Date: Wed, 13 Mar 2024 20:26:21 +0800 Subject: [PATCH 09/11] added notes for publish edge --- .github/workflows/publish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a19f958d..2853d695 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -63,6 +63,7 @@ jobs: "clientSecret": "${{ secrets.EDGE_CLIENT_SECRET}}", "productId": "${{ secrets.EDGE_PRODUCT_ID }}", "accessTokenUrl": "${{ secrets.EDGE_ACCESS_TOKEN_URL }}", - "uploadOnly": ${{ inputs.dry-run }} + "uploadOnly": ${{ inputs.dry-run }}, + "notes": "https://github.com/eric2788/bilibili-vup-stream-enhancer" } } \ No newline at end of file From 4476e86a65f2fddf1d7758691fe4860ad1e3a797 Mon Sep 17 00:00:00 2001 From: Eric Lam <tsukiko852@gmail.com> Date: Wed, 13 Mar 2024 22:26:37 +0800 Subject: [PATCH 10/11] =?UTF-8?q?Resolved=20#44:=20[MV3]=20=E8=AE=B0?= =?UTF-8?q?=E4=BD=8F=E4=B8=8A=E4=B8=80=E6=AC=A1=E5=85=A8=E5=B1=8F=E5=AD=97?= =?UTF-8?q?=E5=B9=95=E8=A7=86=E7=AA=97=E7=9A=84=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/jimaku/components/JimakuArea.tsx | 26 +++++++++++++++---- .../jimaku/components/JimakuFragment.tsx | 10 ++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/features/jimaku/components/JimakuArea.tsx b/src/features/jimaku/components/JimakuArea.tsx index 4803a02e..fbbe1639 100644 --- a/src/features/jimaku/components/JimakuArea.tsx +++ b/src/features/jimaku/components/JimakuArea.tsx @@ -98,6 +98,15 @@ function JimakuArea({ jimaku }: JimakuAreaProps): JSX.Element { const shouldPutIntoLivePlayer = screenStatus !== 'normal' || isTheme + const [ dragState, setDragState ] = useState<{ + x: number, + y: number, + width: string | number, + height: string | number + }>(() => ({ x: 100, y: -300, width: '50%', height: jimakuStyle.backgroundHeight })) + + const rmbState = jimakuStyle.rememberDragState + return ( <Fragment> <Teleport container={rootContainer}> @@ -114,12 +123,19 @@ function JimakuArea({ jimaku }: JimakuAreaProps): JSX.Element { minHeight={100} minWidth={200} scale={0.93} - default={{ - x: 100, - y: -300, - width: '50%', - height: jimakuStyle.backgroundHeight, + onDragStop={(e, d) => { + if (!rmbState) return + setDragState(state => ({ ...state, x: d.x, y: d.y })) + }} + onResizeStop={(e, direction, ref, delta, position) => { + if (!rmbState) return + setDragState({ + width: ref.style.width, + height: ref.style.height, + ...position + }) }} + default={dragState} > <JimakuList jimaku={jimaku} diff --git a/src/settings/features/jimaku/components/JimakuFragment.tsx b/src/settings/features/jimaku/components/JimakuFragment.tsx index 2dbbfe65..4c7abb0f 100644 --- a/src/settings/features/jimaku/components/JimakuFragment.tsx +++ b/src/settings/features/jimaku/components/JimakuFragment.tsx @@ -21,6 +21,7 @@ export type JimakuSchema = { backgroundOpacity: HundredNumber filterUserLevel: number areaDragBoundary: boolean + rememberDragState: boolean } @@ -36,7 +37,8 @@ export const jimakuDefaultSettings: Readonly<JimakuSchema> = { backgroundColor: '#808080', backgroundOpacity: 40, filterUserLevel: 0, - areaDragBoundary: false + areaDragBoundary: false, + rememberDragState: true } function JimakuFragment({ state, useHandler }: StateProxy<JimakuSchema>): JSX.Element { @@ -121,6 +123,12 @@ function JimakuFragment({ state, useHandler }: StateProxy<JimakuSchema>): JSX.El onChange={bool('areaDragBoundary')} value={state.areaDragBoundary} /> + <SwitchListItem + label="记住全屏时字幕背景的拖拽位置和缩放" + hint="刷新页面后,状态依然会被重设" + onChange={bool('rememberDragState')} + value={state.rememberDragState} + /> </List> </Fragment> ) From 3e23f1d97199905791593fd6c55b2de42aa6396f Mon Sep 17 00:00:00 2001 From: eric2788 <tsukiko852@gmail.com> Date: Wed, 13 Mar 2024 23:31:06 +0800 Subject: [PATCH 11/11] updated version and on hold right click context menu on full screen --- package.json | 2 +- src/features/jimaku/components/JimakuList.tsx | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 9c56d813..84f85d99 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bilibili-vup-stream-enhancer", "displayName": "Bilibili Vup Stream Enhancer", - "version": "2.0.0", + "version": "2.0.1", "description": "管人观众专用直播增强扩展", "author": "Eric Lam <tsukiko852@gmail.com>", "license": "MIT", diff --git a/src/features/jimaku/components/JimakuList.tsx b/src/features/jimaku/components/JimakuList.tsx index 924a0286..d72dbade 100644 --- a/src/features/jimaku/components/JimakuList.tsx +++ b/src/features/jimaku/components/JimakuList.tsx @@ -50,22 +50,26 @@ function JimakuList(props: JimakuListProps): JSX.Element { const rootElement = (parent.getRootNode() as ShadowRoot).host; const inFullScreen = rootElement.clientHeight === 0 - const mouseX = inFullScreen ? parent.clientWidth / 2 + 10 : e.clientX - const mouseY = inFullScreen ? (e.screenY - parent.getBoundingClientRect().top) - Math.max(1400 - window.innerHeight, 0) : e.clientY + // dunno why, but the clientX and clientY are not correct in fullscreen mode + // const mouseX = inFullScreen ? parent.clientWidth / 2 + 10 : e.clientX + // const mouseY = inFullScreen ? (e.screenY - parent.getBoundingClientRect().top) - Math.max(1400 - window.innerHeight, 0) : e.clientY - console.log( - e.screenY, - parent.getBoundingClientRect().top, - window.innerHeight - ) + // console.log( + // e.screenY, + // parent.getBoundingClientRect().top, + // window.innerHeight + // ) + + // currently we don't support fullscreen mode + if (inFullScreen) return show({ event: e, props: jimaku, - position: { - x: mouseX, - y: mouseY - } + // position: { + // x: mouseX, + // y: mouseY + // } }) }