fix: retain staged images while editor drafts reference them
This commit is contained in:
1 parent
672016cc3c
commit
ad9fa6666a
2 files changed
+50
-3
No files matched your search
@@ -6,6 +6,7 @@ const {
|
||||
createDesktopSyncDiagnosticText,
|
||||
createDesktopSyncSummary,
|
||||
isStagedResourceReferenced,
|
||||
stagedIdsReferencedByUnsavedContent,
|
||||
hasDesktopSyncStateReset,
|
||||
mergeMemoIdMappings,
|
||||
mergeSyncedMemos,
|
||||
@@ -68,6 +69,15 @@ describe("desktop staged resource sync", () => {
|
||||
], "stage-1")).toBe(true);
|
||||
});
|
||||
|
||||
test("keeps staged bytes while an editor draft or visible image still uses the old URL", () => {
|
||||
const rewrites = [{ memoId: "memo-1", placeholder: "edgeever-staged://stage-1", url: "/api/v1/resources/res-1/blob" }];
|
||||
expect(stagedIdsReferencedByUnsavedContent(rewrites, [
|
||||
{ contentJson: { type: "doc", content: [{ type: "image", attrs: { src: "edgeever-staged://stage-1" } }] } },
|
||||
])).toEqual(new Set(["stage-1"]));
|
||||
expect(stagedIdsReferencedByUnsavedContent(rewrites, ["edgeever-staged://stage-1"])).toEqual(new Set(["stage-1"]));
|
||||
expect(stagedIdsReferencedByUnsavedContent(rewrites, ["edgeever-staged://stage-10"])).toEqual(new Set());
|
||||
});
|
||||
|
||||
test("retains a temporary id mapping when a later sync phase fails", () => {
|
||||
const retained = mergeMemoIdMappings(new Map(), new Map([["memo_local_1", "memo_remote_1"]]));
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@edgeever/shared";
|
||||
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";
|
||||
|
||||
@@ -84,6 +85,41 @@ const stagedIdsReferencedByLocalMemos = async (rewrites: StagedResourceRewrite[]
|
||||
return referenced;
|
||||
};
|
||||
|
||||
export const stagedIdsReferencedByUnsavedContent = (
|
||||
rewrites: StagedResourceRewrite[],
|
||||
values: unknown[],
|
||||
) => new Set(rewrites
|
||||
.filter((rewrite) => isStagedResourceReferenced(values, rewrite.placeholder.slice("edgeever-staged://".length)))
|
||||
.map((rewrite) => rewrite.placeholder.slice("edgeever-staged://".length)));
|
||||
|
||||
const stagedIdsReferencedByRenderer = async (rewrites: StagedResourceRewrite[]) => {
|
||||
if (rewrites.length === 0) return new Set<string>();
|
||||
const memoIds = [...new Set(rewrites.map((rewrite) => rewrite.memoId))];
|
||||
try {
|
||||
const { localDb } = await import("@/lib/local-db");
|
||||
const [drafts, queued] = await Promise.all([
|
||||
localDb.drafts.bulkGet(memoIds),
|
||||
localDb.syncQueue.where("memoId").anyOf(memoIds).toArray(),
|
||||
]);
|
||||
const visibleSources = typeof document === "undefined" ? [] : [
|
||||
...Array.from(document.querySelectorAll("[src], [href]"), (element) => (
|
||||
element.getAttribute("src") ?? element.getAttribute("href")
|
||||
)),
|
||||
...Array.from(document.querySelectorAll("textarea"), (element) => element.value),
|
||||
];
|
||||
return stagedIdsReferencedByUnsavedContent(rewrites, [
|
||||
...drafts,
|
||||
...queued.map((item) => item.payload),
|
||||
...memoIds.map((memoId) => readEmergencyDraft(memoId)),
|
||||
...visibleSources,
|
||||
]);
|
||||
} catch {
|
||||
// If drafts cannot be inspected, keep the bytes until a later sync can
|
||||
// prove that no editor can save the old staged URL again.
|
||||
return new Set(rewrites.map((rewrite) => rewrite.placeholder.slice("edgeever-staged://".length)));
|
||||
}
|
||||
};
|
||||
|
||||
const remapStagedResourceMemoIds = async (memoIdMappings: ReadonlyMap<string, string>) => {
|
||||
if (memoIdMappings.size === 0) return;
|
||||
// Keep sync compatible with a renderer hot-reload or an older native shell
|
||||
@@ -748,9 +784,10 @@ export const syncDesktopData = () => {
|
||||
const remainingReferenced = stillReferenced.size > 0
|
||||
? await stagedIdsReferencedByLocalMemos(stagedResources.rewrites)
|
||||
: stillReferenced;
|
||||
await removeSyncedStagedResources(
|
||||
stagedResources.stagedIds.filter((stagedId) => !remainingReferenced.has(stagedId)),
|
||||
);
|
||||
const rendererReferenced = await stagedIdsReferencedByRenderer(stagedResources.rewrites);
|
||||
await removeSyncedStagedResources(stagedResources.stagedIds.filter((stagedId) => (
|
||||
!remainingReferenced.has(stagedId) && !rendererReferenced.has(stagedId)
|
||||
)));
|
||||
}
|
||||
phase = "read_status";
|
||||
const remaining = await request("sync.status", {});
|
||||
|
||||
Reference in new issue
Block a user