fix(desktop): keep attachment imports together across sync

This commit is contained in:
tianma-if committed 2026-09-24 09:02:46 +08:00
1 parent ba6dc54c0e
commit 5fbfc43be9
3 files changed
+55 -1

No files matched your search

+11
View File
@@ -1716,7 +1716,11 @@ export const WorkspaceApp = ({
setTemplatesOpen(false);
setMobileBottomNavActive("home");
creatingMemoSelectionRef.current = true;
let resumeDesktopSync: (() => void) | null = null;
try {
if (isDesktopResourceRuntime()) {
resumeDesktopSync = await (await import("@/lib/desktop-sync")).pauseDesktopSyncForImport();
}
const preparedFile = imageCompressionEnabled ? (await compressImageForUpload(file)).file : file;
const memo = await createScreenshotMemo({
notebookId,
@@ -1762,6 +1766,8 @@ export const WorkspaceApp = ({
title: t("memoList.importScreenshotFailedTitle"),
description: t("memoList.importScreenshotFailed"),
});
} finally {
resumeDesktopSync?.();
}
}, [defaultMemoNotebookId, imageCompressionEnabled, localDataScope, memoView, notebooks, repository, selectedNotebookId, t]);
@@ -1809,7 +1815,11 @@ export const WorkspaceApp = ({
creatingMemoSelectionRef.current = true;
setWeChatImportsInProgress((count) => count + 1);
let savedMemo: MemoDetail | null = null;
let resumeDesktopSync: (() => void) | null = null;
try {
if (isDesktopResourceRuntime()) {
resumeDesktopSync = await (await import("@/lib/desktop-sync")).pauseDesktopSyncForImport();
}
const memo = await createWeChatChatMemo({
notebookId,
title: payload.title?.trim() || "",
@@ -1867,6 +1877,7 @@ export const WorkspaceApp = ({
}
creatingMemoSelectionRef.current = false;
} finally {
resumeDesktopSync?.();
setWeChatImportsInProgress((count) => Math.max(0, count - 1));
}
}, [defaultMemoNotebookId, imageCompressionEnabled, localDataScope, memoView, notebooks, repository, selectedNotebookId, t]);
+19
View File
@@ -11,18 +11,37 @@ const {
mergeMemoIdMappings,
mergeSyncedMemos,
normalizeDesktopMemoPayload,
pauseDesktopSyncForImport,
orderBootstrapNotebooks,
orderDesktopSyncChanges,
resolveDesktopMemoSyncBase,
resolveDesktopStaleMemoUpdate,
desktopLocalRevisionWitnessesRemote,
rewriteStagedResource,
syncDesktopData,
shouldAttemptDesktopRecoveryPull,
shouldPullDesktopChanges,
} = await import("./desktop-sync.ts");
const { ApiRequestError } = await import("./api.ts");
describe("desktop staged resource sync", () => {
test("holds cloud sync until an imported screenshot is saved", async () => {
const originalWindow = globalThis.window;
const events = [];
globalThis.window = { dispatchEvent: (event) => { events.push(event.type); return true; } };
try {
const release = await pauseDesktopSyncForImport();
const result = await syncDesktopData();
expect(result.attempted).toBe(0);
expect(events).not.toContain("edgeever:sync-queue-changed");
release();
release();
expect(events.filter((event) => event === "edgeever:sync-queue-changed")).toHaveLength(1);
} finally {
globalThis.window = originalWindow;
}
});
test("rewrites placeholders in memo JSON and markdown", () => {
const rewrites = [{ memoId: "memo-1", placeholder: "edgeever-staged://stage-1", url: "/api/v1/resources/resource-1/blob" }];
const value = {
+25 -1
View File
@@ -19,7 +19,7 @@ import { api, ApiRequestError } from "@/lib/api";
import { isDesktopResourceRuntime, mapMarkdownResourceUrls, mapTiptapResourceUrls, toApiResourceUrl } from "@/lib/desktop-resources";
import { readEmergencyDraft } from "@/lib/emergency-draft";
import { notebookDeleteIdsFromPayload } from "@/lib/notebook-delete";
import { notifyMemoIdRemapped, notifyMemoSyncAcknowledged } from "@/lib/sync-events";
import { notifyMemoIdRemapped, notifyMemoSyncAcknowledged, notifySyncQueueChanged } from "@/lib/sync-events";
type StagedResourceRewrite = { memoId: string; placeholder: string; url: string };
@@ -767,9 +767,33 @@ export const mergeSyncedMemos = <T>(target: Map<string, T>, source: ReadonlyMap<
};
let activeSync: Promise<DesktopSyncResult> | null = null;
let pausedImports = 0;
// Importers create a local memo before its attachment can be staged. Keep that
// short sequence together so cloud acknowledgement cannot remap the memo ID
// between create and update (or leave an empty cloud memo on failure).
export const pauseDesktopSyncForImport = async () => {
pausedImports += 1;
try {
if (activeSync) await activeSync;
} catch (error) {
pausedImports -= 1;
throw error;
}
let released = false;
return () => {
if (released) return;
released = true;
pausedImports -= 1;
if (pausedImports === 0) notifySyncQueueChanged();
};
};
export const syncDesktopData = () => {
if (activeSync) return activeSync;
if (pausedImports > 0) {
return Promise.resolve({ attempted: 0, synced: 0, failed: 0, conflicted: 0, memoIdMappings: new Map(), syncedMemos: new Map() });
}
activeSync = (async () => {
const memoIdMappings = new Map<string, string>();
const syncedMemos = new Map<string, DesktopRpcResponses["memo.get"]["memo"]>();