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 = ({