fix(desktop): keep attachment imports together across sync
This commit is contained in:
1 parent
ba6dc54c0e
commit
5fbfc43be9
3 files changed
+55
-1
No files matched your search
@@ -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]);
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"]>();
|
||||
|
||||
Reference in new issue
Block a user