mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
Prevent unintentional trashing of non-empty untitled drafts on editor unmount (#12418)
* Initial plan * Fix draft auto-delete check for non-empty untitled docs --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { shouldAutoDeleteDraftOnUnmount } from "./useDocumentSave";
|
||||
|
||||
describe("shouldAutoDeleteDraftOnUnmount", () => {
|
||||
const baseOptions = {
|
||||
title: "",
|
||||
createdById: "user-1",
|
||||
currentUserId: "user-1",
|
||||
isDraft: true,
|
||||
isActive: true,
|
||||
hasEmptyTitle: true,
|
||||
isPersistedOnce: true,
|
||||
};
|
||||
|
||||
it("does not auto delete drafts with non-empty editor content", () => {
|
||||
expect(
|
||||
shouldAutoDeleteDraftOnUnmount({
|
||||
...baseOptions,
|
||||
isEditorEmpty: false,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("auto deletes drafts that are still empty and untitled", () => {
|
||||
expect(
|
||||
shouldAutoDeleteDraftOnUnmount({
|
||||
...baseOptions,
|
||||
isEditorEmpty: true,
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -50,6 +50,36 @@ interface UseDocumentSaveResult {
|
||||
onFileUploadStop: () => void;
|
||||
}
|
||||
|
||||
export function shouldAutoDeleteDraftOnUnmount({
|
||||
isEditorEmpty,
|
||||
title,
|
||||
createdById,
|
||||
currentUserId,
|
||||
isDraft,
|
||||
isActive,
|
||||
hasEmptyTitle,
|
||||
isPersistedOnce,
|
||||
}: {
|
||||
isEditorEmpty: boolean;
|
||||
title: string;
|
||||
createdById?: string;
|
||||
currentUserId?: string;
|
||||
isDraft: boolean;
|
||||
isActive: boolean;
|
||||
hasEmptyTitle: boolean;
|
||||
isPersistedOnce: boolean;
|
||||
}) {
|
||||
return (
|
||||
isEditorEmpty &&
|
||||
title.trim() === "" &&
|
||||
createdById === currentUserId &&
|
||||
isDraft &&
|
||||
isActive &&
|
||||
hasEmptyTitle &&
|
||||
isPersistedOnce
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that encapsulates save, autosave, dirty-tracking, and template
|
||||
* insertion logic for the document editor scene.
|
||||
@@ -77,8 +107,6 @@ export function useDocumentSave({
|
||||
// Companion refs for stale closure avoidance
|
||||
const isEditorDirtyRef = useRef(isEditorDirty);
|
||||
isEditorDirtyRef.current = isEditorDirty;
|
||||
const isEmptyRef = useRef(isEmpty);
|
||||
isEmptyRef.current = isEmpty;
|
||||
const titleRef = useRef(title);
|
||||
titleRef.current = title;
|
||||
|
||||
@@ -89,7 +117,6 @@ export function useDocumentSave({
|
||||
isEditorDirtyRef.current = dirty;
|
||||
const empty = (!doc || ProsemirrorHelper.isEmpty(doc)) && !titleRef.current;
|
||||
setIsEmpty(empty);
|
||||
isEmptyRef.current = empty;
|
||||
}, [document, editorRef]);
|
||||
|
||||
const updateIsDirtyRef = useRef(updateIsDirty);
|
||||
@@ -313,14 +340,20 @@ export function useDocumentSave({
|
||||
useEffect(
|
||||
() => () => {
|
||||
autosave.cancel();
|
||||
const currentDoc = editorRef.current?.view.state.doc;
|
||||
const isEditorEmpty = !currentDoc || ProsemirrorHelper.isEmpty(currentDoc);
|
||||
|
||||
if (
|
||||
isEmptyRef.current &&
|
||||
document.createdBy?.id === auth.user?.id &&
|
||||
document.isDraft &&
|
||||
document.isActive &&
|
||||
document.hasEmptyTitle &&
|
||||
document.isPersistedOnce
|
||||
shouldAutoDeleteDraftOnUnmount({
|
||||
isEditorEmpty,
|
||||
title: titleRef.current,
|
||||
createdById: document.createdBy?.id,
|
||||
currentUserId: auth.user?.id,
|
||||
isDraft: document.isDraft,
|
||||
isActive: document.isActive,
|
||||
hasEmptyTitle: document.hasEmptyTitle,
|
||||
isPersistedOnce: document.isPersistedOnce,
|
||||
})
|
||||
) {
|
||||
void document.delete();
|
||||
} else if (document.isDirty()) {
|
||||
|
||||
Reference in New Issue
Block a user