fix(web): remember document outline visibility
Default the outline to collapsed and persist the user's last choice across reloads.\n\nRefs #345
This commit is contained in:
1 parent
c7bee0afc9
commit
ae464e88c4
3 files changed
+78
-4
No files matched your search
@@ -158,7 +158,9 @@ import {
|
||||
getEditableMemoTitle,
|
||||
getNotebookMoveOptions,
|
||||
readDesktopReadingProtectionPreference,
|
||||
readEditorOutlineCollapsedPreference,
|
||||
writeDesktopReadingProtectionPreference,
|
||||
writeEditorOutlineCollapsedPreference,
|
||||
type EditorContentAlignment,
|
||||
type MemoDocumentActionRequest,
|
||||
type ShortcutSettings,
|
||||
@@ -640,7 +642,7 @@ const RichEditorPane = ({
|
||||
const [markdownSource, setMarkdownSource] = useState("");
|
||||
const [isMarkdownMode, setIsMarkdownMode] = useState(false);
|
||||
const [mobileToolbarOpen, setMobileToolbarOpen] = useState(false);
|
||||
const [editorOutlineCollapsed, setEditorOutlineCollapsed] = useState(false);
|
||||
const [editorOutlineCollapsed, setEditorOutlineCollapsed] = useState(readEditorOutlineCollapsedPreference);
|
||||
const [wechatCopyState, setWechatCopyState] = useState<"idle" | "copying" | "copied" | "error">("idle");
|
||||
const [memoIdCopyNotice, setMemoIdCopyNotice] = useState<{ status: "copied" | "error"; id: string } | null>(null);
|
||||
const handledSaveAndSyncTokenRef = useRef(saveAndSyncToken);
|
||||
@@ -714,6 +716,19 @@ const RichEditorPane = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleEditorOutlineCollapsedChange = useCallback((collapsed: boolean) => {
|
||||
setEditorOutlineCollapsed(collapsed);
|
||||
writeEditorOutlineCollapsedPreference(collapsed);
|
||||
}, []);
|
||||
|
||||
const toggleEditorOutline = useCallback(() => {
|
||||
setEditorOutlineCollapsed((collapsed) => {
|
||||
const nextCollapsed = !collapsed;
|
||||
writeEditorOutlineCollapsedPreference(nextCollapsed);
|
||||
return nextCollapsed;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMobileViewport && mobileDefaultEditRequested && desktopReadingProtection) {
|
||||
setDesktopReadingProtection(false);
|
||||
@@ -3121,8 +3136,8 @@ const RichEditorPane = ({
|
||||
return;
|
||||
}
|
||||
|
||||
setEditorOutlineCollapsed((current) => !current);
|
||||
}, [editorShortcutBlocked, isMobileViewport, outlineToggleToken, useMarkdownSourceEditor, useMobilePlainTextEditor]);
|
||||
toggleEditorOutline();
|
||||
}, [editorShortcutBlocked, isMobileViewport, outlineToggleToken, toggleEditorOutline, useMarkdownSourceEditor, useMobilePlainTextEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (handledSaveAndSyncTokenRef.current === saveAndSyncToken || saveMutationPending) {
|
||||
@@ -4492,7 +4507,7 @@ const RichEditorPane = ({
|
||||
scrollContainer={editorScrollContainer}
|
||||
collapsed={editorOutlineCollapsed}
|
||||
shortcutLabel={formatShortcutBinding(shortcutSettings.toggleOutline)}
|
||||
onCollapsedChange={setEditorOutlineCollapsed}
|
||||
onCollapsedChange={handleEditorOutlineCollapsedChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DEFAULT_SHORTCUT_SETTINGS,
|
||||
DESKTOP_FOCUS_MODE_STORAGE_KEY,
|
||||
DESKTOP_READING_PROTECTION_STORAGE_KEY,
|
||||
EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY,
|
||||
EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY,
|
||||
NOTEBOOK_SORT_STORAGE_KEY,
|
||||
SHORTCUT_SETTINGS_STORAGE_KEY,
|
||||
@@ -13,11 +14,13 @@ import {
|
||||
readNotebookSortPreference,
|
||||
readDesktopFocusModePreference,
|
||||
readDesktopReadingProtectionPreference,
|
||||
readEditorOutlineCollapsedPreference,
|
||||
readShortcutSettingsPreference,
|
||||
writeEditorContentAlignmentPreference,
|
||||
writeNotebookSortPreference,
|
||||
writeDesktopFocusModePreference,
|
||||
writeDesktopReadingProtectionPreference,
|
||||
writeEditorOutlineCollapsedPreference,
|
||||
} from "./app-helpers.ts";
|
||||
|
||||
const originalWindow = globalThis.window;
|
||||
@@ -130,6 +133,45 @@ describe("desktop reading protection preference", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("editor outline preference", () => {
|
||||
test("defaults to collapsed and only expands for an explicit false value", () => {
|
||||
const values = installLocalStorage();
|
||||
expect(readEditorOutlineCollapsedPreference()).toBe(true);
|
||||
|
||||
values.set(EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY, "true");
|
||||
expect(readEditorOutlineCollapsedPreference()).toBe(true);
|
||||
|
||||
values.set(EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY, "false");
|
||||
expect(readEditorOutlineCollapsedPreference()).toBe(false);
|
||||
});
|
||||
|
||||
test("persists collapsed and expanded states", () => {
|
||||
const values = installLocalStorage();
|
||||
|
||||
writeEditorOutlineCollapsedPreference(false);
|
||||
expect(values.get(EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY)).toBe("false");
|
||||
|
||||
writeEditorOutlineCollapsedPreference(true);
|
||||
expect(values.get(EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY)).toBe("true");
|
||||
});
|
||||
|
||||
test("falls back to collapsed when local storage is unavailable", () => {
|
||||
globalThis.window = {
|
||||
localStorage: {
|
||||
getItem: () => {
|
||||
throw new Error("blocked");
|
||||
},
|
||||
setItem: () => {
|
||||
throw new Error("blocked");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(readEditorOutlineCollapsedPreference()).toBe(true);
|
||||
expect(() => writeEditorOutlineCollapsedPreference(false)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("editor content alignment preference", () => {
|
||||
test("defaults to left aligned and persists both supported alignments", () => {
|
||||
const values = installLocalStorage();
|
||||
|
||||
@@ -131,6 +131,7 @@ export { DEFAULT_MEMO_TITLE };
|
||||
export const IMAGE_COMPRESSION_STORAGE_KEY = "edgeever.imageCompressionEnabled";
|
||||
export const DESKTOP_FOCUS_MODE_STORAGE_KEY = "edgeever.desktopFocusMode";
|
||||
export const DESKTOP_READING_PROTECTION_STORAGE_KEY = "edgeever.desktopReadingProtection";
|
||||
export const EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY = "edgeever.editorOutlineCollapsed";
|
||||
export const EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY = "edgeever.editorContentAlignment";
|
||||
export const MEMO_LIST_DENSITY_STORAGE_KEY = "edgeever.memoListDensity";
|
||||
export const MEMO_LIST_WIDTH_STORAGE_KEY = "edgeever.memoListWidth";
|
||||
@@ -355,6 +356,22 @@ export const writeDesktopReadingProtectionPreference = (enabled: boolean) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const readEditorOutlineCollapsedPreference = () => {
|
||||
try {
|
||||
return window.localStorage.getItem(EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY) !== "false";
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export const writeEditorOutlineCollapsedPreference = (collapsed: boolean) => {
|
||||
try {
|
||||
window.localStorage.setItem(EDITOR_OUTLINE_COLLAPSED_STORAGE_KEY, collapsed ? "true" : "false");
|
||||
} catch {
|
||||
// Local storage can be unavailable in private or restricted browser contexts.
|
||||
}
|
||||
};
|
||||
|
||||
export const readEditorContentAlignmentPreference = (): EditorContentAlignment => {
|
||||
try {
|
||||
return window.localStorage.getItem(EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY) === "center" ? "center" : "start";
|
||||
|
||||
Reference in new issue
Block a user