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
This commit is contained in:
parent
ecfe136628
commit
02be56888a
@ -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` 文件夹
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
@ -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` 文件夹
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
64
package-lock.json
generated
64
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
},
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.59.1",
|
||||
"jsdom": "^29.0.2",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^6.0.3",
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<typeof syncMarketTable>,
|
||||
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<string, unknown>
|
||||
): 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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user