fix(web): improve notebook and editor interactions

Fixes #275
This commit is contained in:
tianma-if committed 2026-08-18 12:11:18 +08:00
1 parent c175cba97b
commit e59ea58971
14 files changed
+244 -51

No files matched your search

+6 -1
View File
@@ -126,6 +126,7 @@ import {
EDITOR_LOCAL_SAVE_DELAY_MS,
getEditableMemoTitle,
getNotebookMoveOptions,
type EditorContentAlignment,
type MemoDocumentActionRequest,
} from "@/lib/app-helpers";
import { copyEditorToWeChat, copyMarkdownToWeChat } from "@/lib/wechat-copy";
@@ -604,6 +605,7 @@ type EditorPaneProps = {
repository: EdgeEverRepository;
desktopFocusMode: boolean;
onToggleDesktopFocusMode: () => void;
editorContentAlignment: EditorContentAlignment;
mobileDefaultEditMemoId: string | null;
preserveUnsavedContentFromMemoId?: string | null;
saveBlocked?: boolean;
@@ -670,6 +672,7 @@ const RichEditorPane = ({
repository,
desktopFocusMode,
onToggleDesktopFocusMode,
editorContentAlignment,
mobileDefaultEditMemoId,
preserveUnsavedContentFromMemoId: _preserveUnsavedContentFromMemoId,
saveBlocked: _saveBlocked = false,
@@ -4118,7 +4121,9 @@ const RichEditorPane = ({
: "min-h-full items-start px-6 py-6 sm:px-10",
desktopFocusMode
? "mx-auto w-full max-w-[1400px] justify-center"
: "w-full justify-center"
: editorContentAlignment === "center"
? "w-full justify-center"
: "w-full justify-start"
)}
>
<div
@@ -0,0 +1,13 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
describe("memo context menu", () => {
test("uses a Radix submenu for notebook moves so opening the picker does not close the menu", () => {
const source = readFileSync(new URL("./MemoListPane.tsx", import.meta.url), "utf8");
expect(source).toContain("<DropdownMenuSub>");
expect(source).toContain("<DropdownMenuSubTrigger");
expect(source).toContain("<DropdownMenuSubContent");
expect(source).not.toContain("contextMoveOpen");
});
});
+19 -26
View File
@@ -15,7 +15,6 @@ import * as m from "motion/react-m";
import {
X,
ChevronLeft,
ChevronRight,
ChevronDown,
Search,
MoreHorizontal,
@@ -54,6 +53,9 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";
@@ -473,7 +475,6 @@ export const MemoListPane = ({
}) => {
const { t } = useTranslation();
const [memoContextMenu, setMemoContextMenu] = useState<MemoContextMenuState | null>(null);
const [contextMoveOpen, setContextMoveOpen] = useState(false);
const [listDensity, setListDensity] = useState<MemoListDensity>(() => readMemoListDensityPreference());
const [lastSelectedMemoId, setLastSelectedMemoId] = useState<string | null>(null);
const [moveTargetNotebookId, setMoveTargetNotebookId] = useState("");
@@ -740,7 +741,6 @@ export const MemoListPane = ({
setDesktopActionsOpen(false);
setDesktopFilterOpen(false);
setDesktopSortOpen(false);
setContextMoveOpen(false);
setMemoContextMenu(null);
setMobileListActionsOpen(false);
setMobileMoveOpen(false);
@@ -786,7 +786,6 @@ export const MemoListPane = ({
const x = Math.min(clientX, Math.max(12, window.innerWidth - menuWidth - 12));
const y = Math.min(clientY, Math.max(12, window.innerHeight - menuHeight - 12));
setContextMoveOpen(false);
setMemoContextMenu({ memo, x, y });
};
@@ -804,7 +803,6 @@ export const MemoListPane = ({
}
event.preventDefault();
setContextMoveOpen(false);
setMemoContextMenu(null);
};
@@ -817,7 +815,6 @@ export const MemoListPane = ({
handleToggleMemo(memo.id);
}
setContextMoveOpen(false);
setMemoContextMenu(null);
};
@@ -1522,40 +1519,36 @@ export const MemoListPane = ({
</>
) : (
<>
<DropdownMenuItem
className="flex h-9 w-full items-center gap-2 px-3 text-left text-sm text-slate-700 hover:bg-slate-50 cursor-pointer outline-none"
disabled={moveNotebookOptions.length === 0}
onClick={() => setContextMoveOpen((value) => !value)}
>
<Folder className="h-4 w-4" />
<span className="min-w-0 flex-1 truncate">{t("memoList.moveToNotebook")}</span>
<ChevronRight className={cn("h-4 w-4 transition-transform duration-200", contextMoveOpen && "rotate-90")} />
</DropdownMenuItem>
{contextMoveOpen && (
<div className="max-h-52 overflow-y-auto border-y border-slate-100 bg-slate-50/60 py-1">
{moveNotebookOptions.map((option: any) => (
<button
<DropdownMenuSub>
<DropdownMenuSubTrigger
className="flex h-9 w-full items-center gap-2 px-3 text-left text-sm text-slate-700 hover:bg-slate-50 cursor-pointer outline-none"
disabled={moveNotebookOptions.length === 0}
>
<Folder className="h-4 w-4" />
<span className="min-w-0 flex-1 truncate">{t("memoList.moveToNotebook")}</span>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="max-h-64 w-56 overflow-y-auto">
{moveNotebookOptions.map((option) => (
<DropdownMenuItem
key={option.id}
className={cn(
"flex h-9 w-full items-center gap-2 px-3 text-left text-sm transition hover:bg-white",
"flex h-9 items-center gap-2 px-3 text-sm",
option.id === memoContextMenu.memo.notebookId ? "font-semibold text-slate-950" : "text-slate-700"
)}
style={{ paddingLeft: `${12 + option.depth * 14}px` }}
type="button"
disabled={option.id === memoContextMenu.memo.notebookId}
onClick={() => {
onSelect={() => {
const { memo } = memoContextMenu;
setContextMoveOpen(false);
setMemoContextMenu(null);
onMoveMemo(memo.id, option.id);
}}
>
<NotebookIcon className="h-4 w-4 shrink-0" />
<span className="min-w-0 flex-1 truncate">{option.name}</span>
</button>
</DropdownMenuItem>
))}
</div>
)}
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator className="my-1 h-px bg-slate-100" />
<DropdownMenuItem
className="flex h-9 w-full items-center gap-2 px-3 text-left text-sm text-slate-700 hover:bg-slate-50 cursor-pointer outline-none"
+24 -11
View File
@@ -353,6 +353,11 @@ export const NotebookPane = ({
const [expandSiblingsRequest, setExpandSiblingsRequest] = useState<{ parentId: string | null; token: number } | null>(null);
const [notebookSortMode, setNotebookSortMode] = useState<NotebookSortMode>(readNotebookSortPreference);
const handleMoveNotebook = useCallback((notebookId: string, targetNotebookId: string, position: NotebookDropPosition) => {
setNotebookSortMode("custom");
onMoveNotebook(notebookId, targetNotebookId, position);
}, [onMoveNotebook]);
const stopNotebookDragAutoScroll = useCallback(() => {
if (notebookDragScrollFrameRef.current === null) {
return;
@@ -550,16 +555,24 @@ export const NotebookPane = ({
<BookPlus className="h-3.5 w-3.5" />
</button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className="flex h-6 w-6 items-center justify-center rounded-md text-slate-500 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/70"
type="button"
title={t("notebookPane.sortTitle", { label: activeNotebookSortLabel })}
aria-label={t("notebookPane.sortTitle", { label: activeNotebookSortLabel })}
>
<ArrowDownWideNarrow className="h-3.5 w-3.5" />
</button>
</DropdownMenuTrigger>
<TooltipProvider delayDuration={0} skipDelayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<button
className="flex h-6 w-6 items-center justify-center rounded-md text-slate-500 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/70"
type="button"
aria-label={t("notebookPane.sortTitle", { label: activeNotebookSortLabel })}
>
<ArrowDownWideNarrow className="h-3.5 w-3.5" />
</button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">
{t("notebookPane.sortTitle", { label: activeNotebookSortLabel })}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DropdownMenuContent align="end" className="w-36">
{notebookSortOptions.map((option) => (
<DropdownMenuCheckboxItem
@@ -589,7 +602,7 @@ export const NotebookPane = ({
onCreateNotebook={onCreateNotebook}
onRenameNotebook={onRenameNotebook}
onDeleteNotebook={onDeleteNotebook}
onMoveNotebook={onMoveNotebook}
onMoveNotebook={handleMoveNotebook}
onMoveMemos={onMoveMemos}
onDragScroll={handleNotebookScrollDragOver}
expandSiblingsRequest={expandSiblingsRequest}
+7 -1
View File
@@ -16,7 +16,7 @@ import { useTranslation } from "react-i18next";
import * as m from "motion/react-m";
import { SystemInfoDialog } from "@/components/SystemInfoDialog";
import { Button } from "@/components/ui/button";
import type { ShortcutSettings } from "@/lib/app-helpers";
import type { EditorContentAlignment, ShortcutSettings } from "@/lib/app-helpers";
import { WORKSPACE_PAGE_TITLE_CLASSNAME } from "@/lib/workspace-ui";
import { cn } from "@/lib/utils";
import { AdvancedPlayCard } from "./settings/AdvancedPlayCard";
@@ -52,6 +52,8 @@ interface SettingsPaneProps {
onSyncIntervalChange: (intervalMs: number | null) => void;
shortcutSettings: ShortcutSettings;
onShortcutSettingsChange: (settings: ShortcutSettings) => void;
editorContentAlignment: EditorContentAlignment;
onEditorContentAlignmentChange: (alignment: EditorContentAlignment) => void;
onLogout: () => void;
isLoggingOut: boolean;
authRequired: boolean;
@@ -93,6 +95,8 @@ export const SettingsPane = ({
onSyncIntervalChange,
shortcutSettings,
onShortcutSettingsChange,
editorContentAlignment,
onEditorContentAlignmentChange,
onLogout,
isLoggingOut,
authRequired,
@@ -214,6 +218,8 @@ export const SettingsPane = ({
onSyncIntervalChange={onSyncIntervalChange}
shortcutSettings={shortcutSettings}
onShortcutSettingsChange={onShortcutSettingsChange}
editorContentAlignment={editorContentAlignment}
onEditorContentAlignmentChange={onEditorContentAlignmentChange}
/>
<FeedbackLink className="hidden lg:flex" />
</SettingsGroup>
+5
View File
@@ -761,10 +761,12 @@ export const WorkspaceApp = ({
const [multiSelectKeyDown, setMultiSelectKeyDown] = useState(false);
const {
desktopFocusMode,
editorContentAlignment,
imageCompressionEnabled,
memoListWidth,
resetMemoListWidth,
setDesktopFocusMode,
setEditorContentAlignment,
setImageCompressionEnabled,
setMemoListWidth,
setShortcutSettings,
@@ -2965,6 +2967,8 @@ export const WorkspaceApp = ({
onSyncIntervalChange={setSyncIntervalMs}
shortcutSettings={shortcutSettings}
onShortcutSettingsChange={setShortcutSettings}
editorContentAlignment={editorContentAlignment}
onEditorContentAlignmentChange={setEditorContentAlignment}
onLogout={onLogout}
isLoggingOut={isLoggingOut}
authRequired={authRequired}
@@ -3011,6 +3015,7 @@ export const WorkspaceApp = ({
onOpenAiPrompts={handleOpenAiPrompts}
desktopFocusMode={desktopFocusModeActive}
onToggleDesktopFocusMode={toggleDesktopFocusMode}
editorContentAlignment={editorContentAlignment}
mobileDefaultEditMemoId={createdMemoEditId}
isTrashView={memoView === "trash"}
notebooks={notebooks}
@@ -0,0 +1,13 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
describe("editor content alignment preference", () => {
test("is configured from settings instead of the per-note toolbar", () => {
const preferenceCard = readFileSync(new URL("./PreferenceCard.tsx", import.meta.url), "utf8");
const editorPane = readFileSync(new URL("../EditorPane.tsx", import.meta.url), "utf8");
expect(preferenceCard).toContain('t("settings.editorContentAlignmentTitle")');
expect(preferenceCard).toContain('onEditorContentAlignmentChange(value as EditorContentAlignment)');
expect(editorPane).not.toContain("onToggleEditorContentAlignment");
});
});
@@ -1,7 +1,7 @@
import { ChartNoAxesCombined, Image, Languages, MousePointerClick, Palette, RefreshCw, Sparkles } from "lucide-react";
import { AlignHorizontalJustifyCenter, ChartNoAxesCombined, Image, Languages, MousePointerClick, Palette, RefreshCw, Sparkles } from "lucide-react";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { writeSyncIntervalPreference, type ShortcutSettings, type SyncIntervalPreference } from "@/lib/app-helpers";
import { writeSyncIntervalPreference, type EditorContentAlignment, type ShortcutSettings, type SyncIntervalPreference } from "@/lib/app-helpers";
import {
EDITOR_LINK_OPEN_MODE_CHANGED_EVENT,
getStoredEditorLinkOpenMode,
@@ -42,6 +42,8 @@ interface PreferenceCardProps {
onSyncIntervalChange: (intervalMs: number | null) => void;
shortcutSettings: ShortcutSettings;
onShortcutSettingsChange: (settings: ShortcutSettings) => void;
editorContentAlignment: EditorContentAlignment;
onEditorContentAlignmentChange: (alignment: EditorContentAlignment) => void;
}
export const PreferenceCard = ({
@@ -51,6 +53,8 @@ export const PreferenceCard = ({
onSyncIntervalChange,
shortcutSettings,
onShortcutSettingsChange,
editorContentAlignment,
onEditorContentAlignmentChange,
}: PreferenceCardProps) => {
const { t } = useTranslation();
const {
@@ -194,6 +198,30 @@ export const PreferenceCard = ({
</div>
</div>
<div className="hidden min-h-16 flex-col items-start gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4 lg:flex">
<div className="flex min-w-0 items-start gap-3">
<AlignHorizontalJustifyCenter className="mt-0.5 h-4 w-4 shrink-0 text-emerald-700" />
<div className="min-w-0">
<div className="text-sm font-semibold text-slate-900">{t("settings.editorContentAlignmentTitle")}</div>
<div className="mt-0.5 text-xs leading-4 text-slate-500">{t("settings.editorContentAlignmentDescription")}</div>
</div>
</div>
<div className="w-full shrink-0 sm:w-44">
<Select
value={editorContentAlignment}
onValueChange={(value) => onEditorContentAlignmentChange(value as EditorContentAlignment)}
>
<SelectTrigger aria-label={t("settings.editorContentAlignmentTitle")} className="h-9 bg-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="start">{t("settings.editorContentAlignments.start")}</SelectItem>
<SelectItem value="center">{t("settings.editorContentAlignments.center")}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex min-h-16 flex-col items-start gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="flex min-w-0 items-start gap-3">
<Palette className="mt-0.5 h-4 w-4 shrink-0 text-emerald-700" />
+10 -8
View File
@@ -36,14 +36,16 @@ const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName;
@@ -3,16 +3,19 @@ import {
DEFAULT_MEMO_LIST_WIDTH_PX,
clampMemoListWidth,
readDesktopFocusModePreference,
readEditorContentAlignmentPreference,
readImageCompressionPreference,
readMemoListWidthPreference,
readShortcutSettingsPreference,
readSyncIntervalPreference,
writeDesktopFocusModePreference,
writeEditorContentAlignmentPreference,
writeImageCompressionPreference,
writeMemoListWidthPreference,
writeShortcutSettingsPreference,
writeSyncIntervalPreference,
type ShortcutSettings,
type EditorContentAlignment,
} from "@/lib/app-helpers";
export type SyncIntervalPreference = "off" | "30s" | "5m" | "15m" | "30m" | "1h" | "2h";
@@ -33,6 +36,7 @@ export const useWorkspacePreferences = () => {
const [imageCompressionEnabled, setImageCompressionEnabled] = useState(readImageCompressionPreference);
const [syncIntervalMs, setSyncIntervalMs] = useState(readSyncIntervalPreference);
const [desktopFocusMode, setDesktopFocusModeState] = useState(readDesktopFocusModePreference);
const [editorContentAlignment, setEditorContentAlignmentState] = useState(readEditorContentAlignmentPreference);
const [shortcutSettings, setShortcutSettings] = useState<ShortcutSettings>(readShortcutSettingsPreference);
const [memoListWidth, setMemoListWidthState] = useState(readMemoListWidthPreference);
@@ -45,6 +49,11 @@ export const useWorkspacePreferences = () => {
writeDesktopFocusModePreference(enabled);
}, []);
const setEditorContentAlignment = useCallback((alignment: EditorContentAlignment) => {
setEditorContentAlignmentState(alignment);
writeEditorContentAlignmentPreference(alignment);
}, []);
const setMemoListWidth = useCallback((width: number) => {
const nextWidth = clampMemoListWidth(width);
setMemoListWidthState(nextWidth);
@@ -57,10 +66,12 @@ export const useWorkspacePreferences = () => {
return {
desktopFocusMode,
editorContentAlignment,
imageCompressionEnabled,
memoListWidth,
resetMemoListWidth,
setDesktopFocusMode,
setEditorContentAlignment,
setImageCompressionEnabled,
setMemoListWidth,
setShortcutSettings,
+67
View File
@@ -2,9 +2,16 @@ import { afterEach, describe, expect, test } from "bun:test";
import {
DEFAULT_SYNC_INTERVAL_MS,
DESKTOP_FOCUS_MODE_STORAGE_KEY,
EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY,
NOTEBOOK_SORT_STORAGE_KEY,
SYNC_INTERVAL_STORAGE_KEY,
getNotebookSortComparator,
readEditorContentAlignmentPreference,
readNotebookSortPreference,
readSyncIntervalPreference,
readDesktopFocusModePreference,
writeEditorContentAlignmentPreference,
writeNotebookSortPreference,
writeSyncIntervalPreference,
writeDesktopFocusModePreference,
} from "./app-helpers.ts";
@@ -70,6 +77,66 @@ describe("desktop focus mode preference", () => {
});
});
describe("editor content alignment preference", () => {
test("defaults to left aligned and persists both supported alignments", () => {
const values = installLocalStorage();
expect(readEditorContentAlignmentPreference()).toBe("start");
writeEditorContentAlignmentPreference("start");
expect(values.get(EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY)).toBe("start");
expect(readEditorContentAlignmentPreference()).toBe("start");
writeEditorContentAlignmentPreference("center");
expect(values.get(EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY)).toBe("center");
expect(readEditorContentAlignmentPreference()).toBe("center");
});
test("falls back to left aligned for unknown or unavailable storage", () => {
const values = installLocalStorage();
values.set(EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY, "unexpected");
expect(readEditorContentAlignmentPreference()).toBe("start");
globalThis.window = {
localStorage: {
getItem: () => {
throw new Error("blocked");
},
},
};
expect(readEditorContentAlignmentPreference()).toBe("start");
});
});
describe("custom notebook sorting", () => {
test("persists the custom sort mode", () => {
const values = installLocalStorage();
writeNotebookSortPreference("custom");
expect(values.get(NOTEBOOK_SORT_STORAGE_KEY)).toBe("custom");
expect(readNotebookSortPreference()).toBe("custom");
});
test("orders notebooks by persisted sort order with a stable name fallback", () => {
const compare = getNotebookSortComparator("custom");
const base = {
parentId: null,
slug: null,
icon: null,
color: null,
memoCount: 0,
lastMemoUpdatedAt: null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
};
const notebooks = [
{ ...base, id: "third", name: "C", sortOrder: 30 },
{ ...base, id: "second", name: "B", sortOrder: 20 },
{ ...base, id: "first", name: "A", sortOrder: 20 },
];
expect(notebooks.sort(compare).map((item) => item.id)).toEqual(["first", "second", "third"]);
});
});
describe("automatic sync interval preference", () => {
test("defaults to 30 seconds", () => {
installLocalStorage();
+25 -2
View File
@@ -10,7 +10,8 @@ export type Pane = "notebooks" | "memos" | "editor";
export type MemoView = "notebook" | "trash";
export type MemoFilterMode = "all" | "tagged" | "untagged" | "pinned";
export type MemoSortMode = "updated-desc" | "created-desc" | "title-asc";
export type NotebookSortMode = "name-asc" | "memo-count-desc" | "updated-desc";
export type NotebookSortMode = "custom" | "name-asc" | "memo-count-desc" | "updated-desc";
export type EditorContentAlignment = "start" | "center";
export type MemoListDensity = "preview" | "compact";
export type ShortcutAction = "createMemo" | "createNotebook" | "focusSearch" | "focusReplace";
export type ShortcutBinding = {
@@ -109,6 +110,7 @@ export const IMAGE_COMPRESSION_STORAGE_KEY = "edgeever.imageCompressionEnabled";
export const SYNC_INTERVAL_STORAGE_KEY = "edgeever.syncInterval";
const LEGACY_AUTO_SAVE_INTERVAL_STORAGE_KEY = "edgeever.autoSaveInterval";
export const DESKTOP_FOCUS_MODE_STORAGE_KEY = "edgeever.desktopFocusMode";
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";
export const NOTEBOOK_SORT_STORAGE_KEY = "edgeever.notebookSort";
@@ -139,9 +141,10 @@ export const getMemoSortOptions = (t: TFunction): Array<{ value: MemoSortMode; l
{ value: "title-asc", label: t("options.memoSort.titleAsc") },
];
const NOTEBOOK_SORT_VALUES: NotebookSortMode[] = ["name-asc", "memo-count-desc", "updated-desc"];
const NOTEBOOK_SORT_VALUES: NotebookSortMode[] = ["custom", "name-asc", "memo-count-desc", "updated-desc"];
export const getNotebookSortOptions = (t: TFunction): Array<{ value: NotebookSortMode; label: string }> => [
{ value: "custom", label: t("options.notebookSort.custom") },
{ value: "name-asc", label: t("options.notebookSort.nameAsc") },
{ value: "memo-count-desc", label: t("options.notebookSort.memoCountDesc") },
{ value: "updated-desc", label: t("options.notebookSort.updatedDesc") },
@@ -275,6 +278,22 @@ export const writeDesktopFocusModePreference = (enabled: boolean) => {
}
};
export const readEditorContentAlignmentPreference = (): EditorContentAlignment => {
try {
return window.localStorage.getItem(EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY) === "center" ? "center" : "start";
} catch {
return "start";
}
};
export const writeEditorContentAlignmentPreference = (alignment: EditorContentAlignment) => {
try {
window.localStorage.setItem(EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY, alignment);
} catch {
// Local storage can be unavailable in private or restricted browser contexts.
}
};
export const readMemoListDensityPreference = (): MemoListDensity => {
try {
const density = window.localStorage.getItem(MEMO_LIST_DENSITY_STORAGE_KEY);
@@ -457,6 +476,10 @@ const compareNotebookUpdatedDesc = (first: Notebook, second: Notebook) => {
};
export const getNotebookSortComparator = (sortMode: NotebookSortMode): NotebookNodeComparator => {
if (sortMode === "custom") {
return (first, second) => first.sortOrder - second.sortOrder || compareNotebookNameAsc(first, second);
}
if (sortMode === "memo-count-desc") {
return (first, second) => second.memoCount - first.memoCount || compareNotebookNameAsc(first, second);
}
+7
View File
@@ -204,6 +204,12 @@ export const enUS = {
mermaidThemeDescription: "Choose the visual theme for diagrams in notes.",
editorThemeTitle: "Editor theme",
editorThemeDescription: "Choose a writing style for the note editor. Saved in this browser.",
editorContentAlignmentTitle: "Note content layout",
editorContentAlignmentDescription: "Choose how note content is displayed across all notes on desktop. Saved in this browser.",
editorContentAlignments: {
center: "Optimize readability (centered)",
start: "Align left",
},
editorThemes: {
default: "System default",
"minimal-emerald": "Minimal Emerald",
@@ -695,6 +701,7 @@ export const enUS = {
titleAsc: "Title A-Z",
},
notebookSort: {
custom: "Custom",
nameAsc: "Name",
memoCountDesc: "Note count",
updatedDesc: "Updated date",
+7
View File
@@ -204,6 +204,12 @@ export const zhCN = {
mermaidThemeDescription: "选择笔记中图表的视觉主题。",
editorThemeTitle: "编辑器主题",
editorThemeDescription: "选择正文编辑区的排版风格,偏好会保存在当前浏览器中。",
editorContentAlignmentTitle: "笔记正文布局",
editorContentAlignmentDescription: "选择桌面端所有笔记的正文显示方式,偏好会保存在当前浏览器中。",
editorContentAlignments: {
center: "优化可读性(居中)",
start: "靠左显示",
},
editorThemes: {
default: "系统默认",
"minimal-emerald": "minimal品牌绿",
@@ -695,6 +701,7 @@ export const zhCN = {
titleAsc: "标题 A-Z",
},
notebookSort: {
custom: "自定义",
nameAsc: "名称",
memoCountDesc: "笔记数量",
updatedDesc: "更新日期",