From da4d9155d9b8389dcbc9d9513d3948d6129b419a Mon Sep 17 00:00:00 2001 From: tianma-if Date: Tue, 21 Jul 2026 11:48:18 +0800 Subject: [PATCH] feat: add desktop focus mode (#57) --- README.md | 2 +- README.zh-CN.md | 2 +- apps/mobile/app.json | 4 +- apps/web/src/components/EditorPane.tsx | 19 ++++++- apps/web/src/components/WorkspaceApp.tsx | 48 ++++++++++++++--- apps/web/src/lib/app-helpers.test.mjs | 67 ++++++++++++++++++++++++ apps/web/src/lib/app-helpers.ts | 17 ++++++ packages/shared/src/i18n/en-US.ts | 2 + packages/shared/src/i18n/zh-CN.ts | 2 + 9 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 apps/web/src/lib/app-helpers.test.mjs diff --git a/README.md b/README.md index a858ca9b..aeff28e6 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The public demo resets every Monday at 1:00 AM (China Standard Time) and restore - EdgeEver ZIP import and export: one archive combines human-readable Markdown, Front Matter, nested notebook structure, and relative-path attachments with versioned structured data and revision history for complete recovery between EdgeEver instances. - AI Agent friendly: built-in MCP support lets tools such as Codex, Claude Code, and Antigravity read, organize, and maintain notes, while enabling integrations with Notion databases and Feishu Bitable. - Uncapped multi-device sync: self-hosted API means no restrictive commercial limits on the number of active login devices, supporting seamless synchronization across PC, tablet, and mobile (via PWA or browser). -- Three-pane layout: notebook tree, note list, and main editor. +- Three-pane layout: notebook tree, note list, and main editor, with a desktop focus mode that expands the selected note across the workspace. - Unlimited nested notebooks. - Rich text editing. - Switch between Markdown source and rich text views on desktop. diff --git a/README.zh-CN.md b/README.zh-CN.md index df24e520..9ed1b821 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -34,7 +34,7 @@ EdgeEver 是一个开源、自托管、Cloudflare-native 的现代笔记工作 - EdgeEver ZIP 导入与导出:同一份档案既包含便于直接阅读和迁移的 Markdown、Front Matter、嵌套笔记本结构与相对路径附件,也包含用于 EdgeEver 实例间完整恢复的版本化结构数据和历史版本。 - AI Agent 友好:原生支持 MCP,可让 Codex、Claude Code、Antigravity 等工具读取、整理和维护笔记,并可借助 MCP 与 Notion Database、飞书多维表格进行联动。 - 多端无缝同步且不限设备数:基于自建的 API 个人独享数据,摆脱商业笔记平台对登录设备数量的强制限制(如免费版只允许登录 2 台设备等),支持 PC、平板与手机无缝多端同步。 -- 三栏布局:笔记本树、笔记列表、主编辑区。 +- 三栏布局:笔记本树、笔记列表、主编辑区;桌面端支持专注模式,可让当前笔记铺满工作区。 - 无限级嵌套笔记本。 - 支持富文本编辑。 - 桌面端支持 Markdown 源码与富文本视图切换。 diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 79cef9d7..dcc54dc1 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -3,7 +3,7 @@ "name": "EdgeEver", "slug": "edgeever", "scheme": "edgeever", - "version": "1.1.0", + "version": "1.2.0", "runtimeVersion": { "policy": "appVersion" }, @@ -28,7 +28,7 @@ }, "android": { "package": "org.edgeever.mobile", - "versionCode": 26, + "versionCode": 27, "adaptiveIcon": { "backgroundColor": "#d1fae5", "backgroundImage": "./assets/icon.png", diff --git a/apps/web/src/components/EditorPane.tsx b/apps/web/src/components/EditorPane.tsx index 744bb0d3..0be95e22 100644 --- a/apps/web/src/components/EditorPane.tsx +++ b/apps/web/src/components/EditorPane.tsx @@ -17,6 +17,8 @@ import { Save, ReplaceAll, MoreHorizontal, + Maximize2, + Minimize2, Paperclip, Pencil, Sparkles, @@ -451,6 +453,8 @@ const MobileNotebookSelectSheet = ({ type EditorPaneProps = { memo: MemoDetail | null; + desktopFocusMode: boolean; + onToggleDesktopFocusMode: () => void; mobileDefaultEditMemoId: string | null; preserveUnsavedContentFromMemoId?: string | null; saveBlocked?: boolean; @@ -1064,6 +1068,8 @@ export const EditorPane = (props: EditorPaneProps) => { const RichEditorPane = ({ memo, + desktopFocusMode, + onToggleDesktopFocusMode, mobileDefaultEditMemoId, preserveUnsavedContentFromMemoId: _preserveUnsavedContentFromMemoId, saveBlocked: _saveBlocked = false, @@ -1086,6 +1092,7 @@ const RichEditorPane = ({ selectionActionBar, onRequestMobileNativeEdit, }: RichEditorPaneProps) => { + const { t } = useTranslation(); const queryClient = useQueryClient(); const isSelectionMode = Boolean(selectionActionBar); const [title, setTitle] = useState(""); @@ -2306,6 +2313,16 @@ const RichEditorPane = ({
+ @@ -2481,7 +2498,7 @@ const RichEditorPane = ({
-
+
(null); const [multiSelectKeyDown, setMultiSelectKeyDown] = useState(false); const [imageCompressionEnabled, setImageCompressionEnabled] = useState(readImageCompressionPreference); + const [desktopFocusMode, setDesktopFocusMode] = useState(readDesktopFocusModePreference); const [shortcutSettings, setShortcutSettings] = useState(readShortcutSettingsPreference); const [rightView, setRightView] = useState<"editor" | "settings" | "assets" | "tags" | "evernote-migration">(() => isInitialSettingsRoute ? "settings" : "editor" @@ -1546,6 +1549,9 @@ export const WorkspaceApp = ({ ? queryClient.getQueryData<{ memo: MemoDetail }>(memoDetailQueryKey(selectedMemoId, memoView))?.memo ?? null : null; const selectedMemo = memoQuery.data?.memo ?? cachedSelectedMemo; + const desktopFocusModeActive = Boolean( + isDesktop && desktopFocusMode && rightView === "editor" && selectedMemo && !memoSelectionModeActive + ); const selectionMoveNotebookOptions = useMemo(() => getNotebookMoveOptions(notebooks), [notebooks]); const selectedMemosInCurrentList = useMemo( () => memos.filter((memo) => selectedMemoIds.has(memo.id)), @@ -1973,6 +1979,15 @@ export const WorkspaceApp = ({ } }; + const updateDesktopFocusMode = useCallback((enabled: boolean) => { + setDesktopFocusMode(enabled); + writeDesktopFocusModePreference(enabled); + }, []); + + const toggleDesktopFocusMode = useCallback(() => { + updateDesktopFocusMode(!desktopFocusModeActive); + }, [desktopFocusModeActive, updateDesktopFocusMode]); + const handleWorkspaceBackRequest = useCallback(() => { if (appNoticeDialog) { setAppNoticeDialog(null); @@ -2037,6 +2052,11 @@ export const WorkspaceApp = ({ return true; } + if (desktopFocusModeActive) { + updateDesktopFocusMode(false); + return true; + } + if (rightView === "settings") { handleCloseSettings(); return true; @@ -2066,6 +2086,7 @@ export const WorkspaceApp = ({ return false; }, [ visibleActivePane, + desktopFocusModeActive, appNoticeDialog, rightView, clearMemoSelection, @@ -2090,6 +2111,7 @@ export const WorkspaceApp = ({ notebookNameDialog, templatesOpen, updateNotebookMutation.isPending, + updateDesktopFocusMode, ]); useBrowserBackLayer(workspaceBackTargetActive, handleWorkspaceBackRequest); @@ -2324,16 +2346,22 @@ export const WorkspaceApp = ({