fix: preserve favorite identity and ready lifecycle
This commit is contained in:
@@ -84,6 +84,7 @@ export interface MarketRowDom {
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
backendMetricsCells: Record<BackendMetricField, HTMLElement>;
|
||||
coreUserId?: string;
|
||||
exportFields?: Record<string, string>;
|
||||
hasDirectRatesSource?: boolean;
|
||||
location?: string;
|
||||
@@ -660,6 +661,7 @@ function syncDivGridRoot(root: HTMLElement): MarketTableDom | null {
|
||||
authorId,
|
||||
authorName,
|
||||
backendMetricsCells: backendMetricsCells as Record<BackendMetricField, HTMLElement>,
|
||||
coreUserId: fallbackMarketRow?.coreUserId,
|
||||
exportFields,
|
||||
hasDirectRatesSource:
|
||||
fallbackMarketRow?.hasDirectRatesSource ?? false,
|
||||
|
||||
+47
-44
@@ -232,6 +232,8 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
let scheduledSyncPromise: Promise<void> | null = null;
|
||||
let resolveScheduledSync: (() => void) | null = null;
|
||||
let rejectScheduledSync: ((reason?: unknown) => void) | null = null;
|
||||
let toolbarRemountScheduled = false;
|
||||
let lastNativeToolbarActionCount = -1;
|
||||
const selectedAuthorIds = new Set<string>();
|
||||
let toolbar: ReturnType<typeof ensurePluginToolbar> | undefined;
|
||||
let cachedFavoritesState: FavoritesStateV1 | undefined;
|
||||
@@ -269,6 +271,13 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
|
||||
const toolbarNeedsRemount =
|
||||
!toolbar || !isPluginToolbarMounted(toolbar.root, options.document);
|
||||
const nativeToolbarActionCount = readNativeToolbarActionCount(options.document);
|
||||
const needsToolbarRemount =
|
||||
toolbarNeedsRemount &&
|
||||
(!toolbarRemountScheduled || nativeToolbarActionCount !== lastNativeToolbarActionCount);
|
||||
if (!toolbarNeedsRemount) {
|
||||
toolbarRemountScheduled = false;
|
||||
}
|
||||
const selectionControlsMissing =
|
||||
!options.document.querySelector('[data-market-selection-checkbox="row"]') ||
|
||||
!options.document.querySelector('[data-market-selection-checkbox="header"]');
|
||||
@@ -279,7 +288,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
favoritesUnavailable && nextPageSignature !== lastKnownPageSignature;
|
||||
if (
|
||||
nextPageSignature === lastKnownPageSignature &&
|
||||
!toolbarNeedsRemount &&
|
||||
!needsToolbarRemount &&
|
||||
!selectionControlsMissing &&
|
||||
!favoriteControlsMissing &&
|
||||
!needsUnavailableFavoritesRecovery
|
||||
@@ -287,6 +296,10 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsToolbarRemount) {
|
||||
toolbarRemountScheduled = true;
|
||||
lastNativeToolbarActionCount = nativeToolbarActionCount;
|
||||
}
|
||||
if (favoriteControlsMissing || needsUnavailableFavoritesRecovery) {
|
||||
needsFavoritesRefresh = true;
|
||||
}
|
||||
@@ -535,9 +548,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
const ready = (async () => {
|
||||
await runSyncCycle();
|
||||
await refreshFavorites();
|
||||
const initialScheduledSync = scheduledSyncPromise;
|
||||
await waitForDomSettled();
|
||||
await (initialScheduledSync ?? activeSyncPromise ?? scheduledSyncPromise);
|
||||
await waitForSyncStabilization();
|
||||
})();
|
||||
|
||||
return {
|
||||
@@ -767,7 +778,6 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
folderIds.add(membership.folderId);
|
||||
folderIdsByAuthorId.set(membership.authorId, folderIds);
|
||||
});
|
||||
const serializedCoreUserIds = readSerializedFavoriteCoreUserIds(options.document);
|
||||
|
||||
syncFavoriteRowPickers({
|
||||
document: options.document,
|
||||
@@ -779,9 +789,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
actionCell: rowDom.actionCell,
|
||||
authorId: rowDom.authorId,
|
||||
authorName: rowDom.authorName,
|
||||
coreUserId:
|
||||
(rowDom as MarketRowDom & { coreUserId?: string }).coreUserId ??
|
||||
serializedCoreUserIds.get(rowDom.authorId)
|
||||
coreUserId: rowDom.coreUserId
|
||||
}))
|
||||
});
|
||||
favoriteActionButtons = Array.from(
|
||||
@@ -1829,6 +1837,21 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
return nextScheduledSyncPromise;
|
||||
}
|
||||
|
||||
async function waitForSyncStabilization(): Promise<void> {
|
||||
while (!isDisposed) {
|
||||
const pendingSync = activeSyncPromise ?? scheduledSyncPromise;
|
||||
if (pendingSync) {
|
||||
await pendingSync;
|
||||
continue;
|
||||
}
|
||||
|
||||
await waitForDomSettled();
|
||||
if (!activeSyncPromise && !scheduledSyncPromise) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runWithoutMutationSync(callback: () => void): void {
|
||||
if (isDisposed) {
|
||||
return;
|
||||
@@ -1892,6 +1915,9 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
|
||||
async function runSingleSyncCycle(): Promise<void> {
|
||||
toolbar = ensurePluginToolbar(options.document, toolbarHandlers);
|
||||
if (isPluginToolbarMounted(toolbar.root, options.document)) {
|
||||
toolbarRemountScheduled = false;
|
||||
}
|
||||
await hydrateCurrentPage();
|
||||
if (needsFavoritesRefresh) {
|
||||
needsFavoritesRefresh = false;
|
||||
@@ -1903,6 +1929,19 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
|
||||
}
|
||||
|
||||
function readNativeToolbarActionCount(document: Document): number {
|
||||
return Array.from(document.querySelectorAll("button, a, [role='button']")).filter(
|
||||
(element) => {
|
||||
if (element.closest("[data-plugin-toolbar='root']")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const text = element.textContent?.replace(/\s+/g, "").trim();
|
||||
return text === "自定义指标" || text === "导出";
|
||||
}
|
||||
).length;
|
||||
}
|
||||
|
||||
function setScrollTop(element: HTMLElement, top: number): void {
|
||||
element.scrollTop = top;
|
||||
element.dispatchEvent(new Event("scroll"));
|
||||
@@ -1944,42 +1983,6 @@ function readRowSnapshot(rowDom: MarketRowDom): MarketRowSnapshot {
|
||||
};
|
||||
}
|
||||
|
||||
function readSerializedFavoriteCoreUserIds(document: Document): Map<string, string> {
|
||||
const coreUserIds = new Map<string, string>();
|
||||
const serializedRows = document.documentElement.getAttribute("data-sces-market-rows");
|
||||
if (!serializedRows) {
|
||||
return coreUserIds;
|
||||
}
|
||||
|
||||
try {
|
||||
const rows = JSON.parse(serializedRows) as unknown;
|
||||
if (!Array.isArray(rows)) {
|
||||
return coreUserIds;
|
||||
}
|
||||
|
||||
rows.forEach((row) => {
|
||||
if (!row || typeof row !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
const authorId = (row as { authorId?: unknown }).authorId;
|
||||
const coreUserId = (row as { coreUserId?: unknown }).coreUserId;
|
||||
if (
|
||||
typeof authorId === "string" &&
|
||||
authorId.trim() &&
|
||||
typeof coreUserId === "string" &&
|
||||
coreUserId.trim()
|
||||
) {
|
||||
coreUserIds.set(authorId.trim(), coreUserId.trim());
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
return coreUserIds;
|
||||
}
|
||||
|
||||
return coreUserIds;
|
||||
}
|
||||
|
||||
function readFavoriteOperationError(error: unknown): string {
|
||||
if (error instanceof Error && error.message.trim()) {
|
||||
return error.message;
|
||||
|
||||
Reference in New Issue
Block a user