From 02be56888a1a6e5233d1d40e71107cfe95550ac2 Mon Sep 17 00:00:00 2001 From: admin123 Date: Fri, 8 May 2026 00:39:15 +0800 Subject: [PATCH] fix: align market selection checkboxes with rows - Fix checkbox column misalignment with creator list items - Add syncContentCellHeight to keep selection cells aligned with native rows - Add MarketAlignmentDiagnostic for debugging alignment issues - Update documentation to clarify dist-release folder usage - Add npm run build:release step to colleague setup guide --- docs/【给同事】从Git下载使用说明.md | 8 +++ docs/【超简单版】插件安装使用指南.md | 9 ++- package-lock.json | 64 ++++++++++++++++++++ package.json | 1 + src/content/market/dom-sync.ts | 54 ++++++++++++++++- src/content/market/index.ts | 56 ++++++++++++++++++ tests/market-dom-sync.test.ts | 87 ++++++++++++++++++++++++++++ 7 files changed, 276 insertions(+), 3 deletions(-) diff --git a/docs/【给同事】从Git下载使用说明.md b/docs/【给同事】从Git下载使用说明.md index 882283b..91d8b4c 100644 --- a/docs/【给同事】从Git下载使用说明.md +++ b/docs/【给同事】从Git下载使用说明.md @@ -43,6 +43,8 @@ git clone https://git.internal.intelligrow.cn/wangshaoqing/star-chart-search-enh star-chart-search-enhancer/dist-release/ ``` + ⚠️ **重要**:必须选择 `dist-release` 这个子文件夹,不要选外层文件夹 + ✅ 安装成功!你会看到插件卡片。 --- @@ -74,10 +76,16 @@ git clone https://git.internal.intelligrow.cn/wangshaoqing/star-chart-search-enh ```bash cd Desktop/star-chart-search-enhancer git pull +npm run build:release ``` 然后到 `chrome://extensions` 页面点击插件卡片的 **"重新加载"** 🔄 +⚠️ **如果重新加载后还是旧版本**: +- 先点击插件卡片的 **"移除"** 删除旧版本 +- 然后重新点击 **"加载已解压的扩展程序"** +- 再次选择 `dist-release` 文件夹 + --- ## ❓ 常见问题 diff --git a/docs/【超简单版】插件安装使用指南.md b/docs/【超简单版】插件安装使用指南.md index 52634a3..1dd707b 100644 --- a/docs/【超简单版】插件安装使用指南.md +++ b/docs/【超简单版】插件安装使用指南.md @@ -37,7 +37,9 @@ 4. 点击左上角出现的 **"加载已解压的扩展程序"** -5. 选择刚才解压出来的那个文件夹 +5. 选择刚才解压出来的文件夹里的 **`dist-release`** 文件夹 + + ⚠️ **重要**:必须选择 `dist-release` 这个子文件夹,不要选外层文件夹 6. 看到绿色的插件卡片出现,就装好了! @@ -95,6 +97,11 @@ https://xingtu.cn/ad/creator/market 3. 打开 `chrome://extensions` 4. 找到插件卡片,点击 **"重新加载"** 🔄 +⚠️ **如果重新加载后还是旧版本**: +- 先点击插件卡片的 **"移除"** 删除旧版本 +- 然后重新点击 **"加载已解压的扩展程序"** +- 再次选择 `dist-release` 文件夹 + --- ## ❓ 常见问题 diff --git a/package-lock.json b/package-lock.json index f3d882f..2d400dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@logto/chrome-extension": "^0.1.27" }, "devDependencies": { + "@playwright/test": "^1.59.1", "jsdom": "^29.0.2", "tsup": "^8.5.1", "typescript": "^6.0.3", @@ -826,6 +827,22 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.16", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz", @@ -2502,6 +2519,53 @@ "pathe": "^2.0.1" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", diff --git a/package.json b/package.json index 527b6db..6f709ba 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "license": "UNLICENSED", "devDependencies": { + "@playwright/test": "^1.59.1", "jsdom": "^29.0.2", "tsup": "^8.5.1", "typescript": "^6.0.3", diff --git a/src/content/market/dom-sync.ts b/src/content/market/dom-sync.ts index b871179..9f4494a 100644 --- a/src/content/market/dom-sync.ts +++ b/src/content/market/dom-sync.ts @@ -99,10 +99,39 @@ export interface MarketTableDom { rows: MarketRowDom[]; } +export interface MarketAlignmentDiagnostic { + authorId: string; + authorName: string; + checkboxTop: number; + rowTop: number; + topDelta: number; +} + export function syncMarketTable(root: ParentNode): MarketTableDom | null { return syncSyntheticMarketTable(root) ?? syncDivGridMarketTable(root); } +export function readMarketAlignmentDiagnostics( + table: MarketTableDom +): MarketAlignmentDiagnostic[] { + return table.rows.map((rowDom) => { + const selectionCell = rowDom.selectionCheckbox.parentElement; + const selectionRect = selectionCell?.getBoundingClientRect(); + const rowRect = rowDom.row.getBoundingClientRect(); + + const checkboxTop = selectionRect?.top ?? Number.NaN; + const rowTop = rowRect.top ?? Number.NaN; + + return { + authorId: rowDom.authorId, + authorName: rowDom.authorName, + checkboxTop, + rowTop, + topDelta: checkboxTop - rowTop + }; + }); +} + export function readMarketPageSignature(root: ParentNode): string { const document = getOwnerDocument(root); const explicitPageIndex = @@ -801,14 +830,15 @@ function syncDivColumnCells( const templateCells = getDirectContentCells(templateColumn); for (let index = 0; index < rowCount; index += 1) { const existingCell = getDirectContentCells(column)[index] ?? null; + const templateCell = + templateCells[index] ?? templateCells[templateCells.length - 1] ?? null; if (existingCell) { existingCell.dataset.marketRowCell = field; applyPluginContentCellStyles(existingCell); + syncContentCellHeight(existingCell, templateCell); continue; } - const templateCell = - templateCells[index] ?? templateCells[templateCells.length - 1] ?? null; const nextCell = field === SELECTION_COLUMN_KEY ? templateCell @@ -820,11 +850,31 @@ function syncDivColumnCells( nextCell.dataset.marketRowCell = field; applyColumnWidth(nextCell, field); applyPluginContentCellStyles(nextCell); + syncContentCellHeight(nextCell, templateCell); nextCell.textContent = ""; column.appendChild(nextCell); } } +function syncContentCellHeight( + cell: HTMLElement, + templateCell: HTMLElement | null +): void { + if (!templateCell) { + return; + } + + const measuredHeight = Math.round(templateCell.getBoundingClientRect().height); + const nextHeight = + measuredHeight > 0 ? `${measuredHeight}px` : templateCell.style.height; + + if (nextHeight) { + cell.style.height = nextHeight; + } else { + cell.style.removeProperty("height"); + } +} + function applyPluginHeaderCellStyles(cell: HTMLElement): void { cell.style.display = "flex"; cell.style.alignItems = "center"; diff --git a/src/content/market/index.ts b/src/content/market/index.ts index cbb0340..252cdad 100644 --- a/src/content/market/index.ts +++ b/src/content/market/index.ts @@ -4,6 +4,7 @@ import { createBatchPayload, type BatchPayload } from "./batch-payload"; import { applyRowOrder, applyRowVisibility, + readMarketAlignmentDiagnostics, readMarketPageSignature, renderMarketRowState, syncPluginSortHeaders, @@ -396,6 +397,7 @@ export function createMarketController(options: CreateMarketControllerOptions) { applyRowOrder(table, records.map((record) => record.authorId)); bindSelectionControls(table); syncMarketSelectionState(table, selectedAuthorIds); + logMarketAlignmentDiagnosticsIfEnabled(options.window, table, "applyCurrentView"); lastKnownPageSignature = readMarketPageSignature(options.document); }); } @@ -662,12 +664,23 @@ export function createMarketController(options: CreateMarketControllerOptions) { } const step = Math.max(scrollContainer.clientHeight, 240); + logMarketScrollTraceIfEnabled(options.window, { + event: "start", + maxScrollTop, + originalScrollTop, + step + }); for ( let nextScrollTop = Math.min(originalScrollTop + step, maxScrollTop); nextScrollTop > originalScrollTop && nextScrollTop <= maxScrollTop; nextScrollTop = Math.min(nextScrollTop + step, maxScrollTop) ) { setScrollTop(scrollContainer, nextScrollTop); + logMarketScrollTraceIfEnabled(options.window, { + event: "step", + nextScrollTop, + scrollTop: scrollContainer.scrollTop + }); hydrationSnapshot = await collectCurrentPageSnapshotsUntilSettled(); if ( hydrationSnapshot.missingDefaultFieldCount === 0 && @@ -683,6 +696,10 @@ export function createMarketController(options: CreateMarketControllerOptions) { if (scrollContainer.scrollTop !== originalScrollTop) { setScrollTop(scrollContainer, originalScrollTop); + logMarketScrollTraceIfEnabled(options.window, { + event: "restore", + restoredScrollTop: scrollContainer.scrollTop + }); } } @@ -1009,6 +1026,45 @@ function setScrollTop(element: HTMLElement, top: number): void { element.dispatchEvent(new Event("scroll")); } +function logMarketAlignmentDiagnosticsIfEnabled( + window: Window, + table: ReturnType, + label: string +): void { + if (!isMarketDebugLoggingEnabled(window) || !table) { + return; + } + + const diagnostics = readMarketAlignmentDiagnostics(table); + console.info(`[SCES debug] ${label} alignment`, diagnostics); +} + +function logMarketScrollTraceIfEnabled( + window: Window, + payload: Record +): void { + if (!isMarketDebugLoggingEnabled(window)) { + return; + } + + console.info("[SCES debug] submit/export scroll", payload); +} + +function isMarketDebugLoggingEnabled(window: Window): boolean { + try { + const searchParams = new URL(window.location.href).searchParams; + if (searchParams.get("scesDebug") === "1") { + return true; + } + } catch {} + + try { + return window.localStorage.getItem("scesDebugMarket") === "1"; + } catch { + return false; + } +} + function readCurrentPageRows(document: Document): MarketRowSnapshot[] { const table = syncMarketTable(document); if (!table) { diff --git a/tests/market-dom-sync.test.ts b/tests/market-dom-sync.test.ts index 385f921..e700980 100644 --- a/tests/market-dom-sync.test.ts +++ b/tests/market-dom-sync.test.ts @@ -7,6 +7,7 @@ import { applyRowVisibility, findNextPageControl, isPageControlDisabled, + readMarketAlignmentDiagnostics, readMarketPageSignature, renderMarketRowState, syncMarketTable @@ -329,6 +330,70 @@ describe("market-dom-sync", () => { expect(readSelectionCellHeights()).toEqual(["120px", "120px"]); }); + test("resyncs selection cell heights when native author row heights change", () => { + document.body.innerHTML = buildRealMarketGridFixture(); + + expect(syncMarketTable(document)).not.toBeNull(); + expect(readSelectionCellHeights()).toEqual(["120px", "120px"]); + + const authorCells = Array.from( + document.querySelectorAll('[data-testid="author-section"] .content-column:not([data-market-column-group="selection"]) > .content-cell') + ) as HTMLElement[]; + const middleCells = Array.from( + document.querySelectorAll('.middle-columns .content-column > .content-cell') + ) as HTMLElement[]; + const rightCells = Array.from( + document.querySelectorAll('[data-testid="right-section"] .content-column > .content-cell') + ) as HTMLElement[]; + + [...authorCells, ...middleCells, ...rightCells].forEach((cell) => { + cell.style.height = "152px"; + }); + + expect(syncMarketTable(document)).not.toBeNull(); + expect(readSelectionCellHeights()).toEqual(["152px", "152px"]); + }); + + test("reads alignment diagnostics for selection cells against native rows", () => { + document.body.innerHTML = buildRealMarketGridFixture(); + + const table = syncMarketTable(document); + if (!table) { + throw new Error("Expected market table"); + } + + const firstSelectionCell = table.rows[0].selectionCheckbox.parentElement as HTMLElement; + const secondSelectionCell = table.rows[1].selectionCheckbox.parentElement as HTMLElement; + + vi.spyOn(firstSelectionCell, "getBoundingClientRect").mockReturnValue( + createRect({ height: 24, top: 100 }) + ); + vi.spyOn(table.rows[0].row, "getBoundingClientRect").mockReturnValue( + createRect({ height: 120, top: 120 }) + ); + vi.spyOn(secondSelectionCell, "getBoundingClientRect").mockReturnValue( + createRect({ height: 24, top: 260 }) + ); + vi.spyOn(table.rows[1].row, "getBoundingClientRect").mockReturnValue( + createRect({ height: 120, top: 250 }) + ); + + expect(readMarketAlignmentDiagnostics(table)).toEqual([ + expect.objectContaining({ + authorId: "111", + checkboxTop: 100, + rowTop: 120, + topDelta: -20 + }), + expect.objectContaining({ + authorId: "222", + checkboxTop: 260, + rowTop: 250, + topDelta: 10 + }) + ]); + }); + test("uses native-like alignment styles for plugin cells", () => { document.body.innerHTML = buildRealMarketGridFixtureWithScopedAttributes(); @@ -1047,3 +1112,25 @@ function readVisualCells(root: Element | null): HTMLElement[] { return cells.indexOf(left) - cells.indexOf(right); }); } + +function createRect({ + height, + top +}: { + height: number; + top: number; +}): DOMRect { + return { + bottom: top + height, + height, + left: 0, + right: 0, + toJSON() { + return {}; + }, + top, + width: 0, + x: 0, + y: top + } as DOMRect; +}