diff --git a/apps/web/src/components/EditorPane.tsx b/apps/web/src/components/EditorPane.tsx
index d46ec464..fa4c9d54 100644
--- a/apps/web/src/components/EditorPane.tsx
+++ b/apps/web/src/components/EditorPane.tsx
@@ -50,6 +50,7 @@ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@
import { EditorToolbar } from "./EditorToolbar";
import { RevisionHistoryDialog } from "./dialogs/RevisionHistoryDialog";
import { api } from "@/lib/api";
+import { consumeStandaloneMobileEditorReturn, openStandaloneMobileEditor } from "@/lib/mobile-editor";
import { cn, formatDateTime, parseTagsText } from "@/lib/utils";
import { docToMarkdown, markdownToDoc, type Notebook, type MemoDetail, type TiptapDoc } from "@edgeever/shared";
import { compressImageForUpload } from "@/lib/image-compression";
@@ -930,7 +931,96 @@ const MobileNativeEditorPane = ({
};
export const EditorPane = (props: EditorPaneProps) => {
- return ;
+ const [isMobileViewport, setIsMobileViewport] = useState(() =>
+ typeof window === "undefined" ? false : window.matchMedia(MOBILE_EDITOR_QUERY).matches
+ );
+ const [mobileNativeEditMemoId, setMobileNativeEditMemoId] = useState(null);
+ const standaloneOpenMemoIdRef = useRef(null);
+ const readOnly = props.isTrashView || Boolean(props.memo?.isDeleted);
+ const mobileDefaultEditRequested = Boolean(
+ props.memo?.id && props.memo.id === props.mobileDefaultEditMemoId && !readOnly
+ );
+ const mobileNativeEditingActive = Boolean(
+ isMobileViewport &&
+ props.memo &&
+ !readOnly &&
+ (mobileDefaultEditRequested || mobileNativeEditMemoId === props.memo.id)
+ );
+
+ useEffect(() => {
+ if (isMobileViewport && mobileDefaultEditRequested && props.memo?.id) {
+ if (consumeStandaloneMobileEditorReturn(props.memo.id)) {
+ props.onMobileDefaultEditConsumed();
+ setMobileNativeEditMemoId(null);
+ props.onBackToList();
+ return;
+ }
+
+ if (standaloneOpenMemoIdRef.current === props.memo.id) {
+ return;
+ }
+
+ standaloneOpenMemoIdRef.current = props.memo.id;
+ props.onMobileDefaultEditConsumed();
+ openStandaloneMobileEditor(props.memo.id);
+ }
+ }, [isMobileViewport, mobileDefaultEditRequested, props.memo?.id, props.onBackToList, props.onMobileDefaultEditConsumed]);
+
+ useEffect(() => {
+ const clearReturnedStandaloneEditor = () => {
+ if (!consumeStandaloneMobileEditorReturn(props.memo?.id ?? null)) {
+ return;
+ }
+
+ props.onMobileDefaultEditConsumed();
+ setMobileNativeEditMemoId(null);
+ props.onBackToList();
+ };
+
+ clearReturnedStandaloneEditor();
+ window.addEventListener("pageshow", clearReturnedStandaloneEditor);
+ document.addEventListener("visibilitychange", clearReturnedStandaloneEditor);
+
+ return () => {
+ window.removeEventListener("pageshow", clearReturnedStandaloneEditor);
+ document.removeEventListener("visibilitychange", clearReturnedStandaloneEditor);
+ };
+ }, [props.memo?.id, props.onBackToList, props.onMobileDefaultEditConsumed]);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia(MOBILE_EDITOR_QUERY);
+ const updateMobileViewport = () => setIsMobileViewport(mediaQuery.matches);
+
+ updateMobileViewport();
+ mediaQuery.addEventListener("change", updateMobileViewport);
+
+ return () => mediaQuery.removeEventListener("change", updateMobileViewport);
+ }, []);
+
+ useEffect(() => {
+ setMobileNativeEditMemoId(null);
+ }, [props.memo?.id]);
+
+ if (mobileNativeEditingActive) {
+ return (
+
+ 打开编辑器
+
+ );
+ }
+
+ return (
+ {
+ if (props.memo?.id && !readOnly) {
+ setMobileNativeEditMemoId(props.memo.id);
+ openStandaloneMobileEditor(props.memo.id);
+ }
+ }}
+ />
+ );
};
const RichEditorPane = ({
diff --git a/apps/web/src/components/MobileStandaloneTiptapEditor.tsx b/apps/web/src/components/MobileStandaloneTiptapEditor.tsx
index f3b8cb1b..e19b1d07 100644
--- a/apps/web/src/components/MobileStandaloneTiptapEditor.tsx
+++ b/apps/web/src/components/MobileStandaloneTiptapEditor.tsx
@@ -14,7 +14,7 @@ import {
import { getNotebookMoveOptions } from "@/lib/app-helpers";
import { compressImageForUpload } from "@/lib/image-compression";
import { localDb, type LocalDraft } from "@/lib/local-db";
-import { markStandaloneMobileEditorReturning } from "@/lib/mobile-editor";
+import { getStandaloneMobileEditorReturnPath, markStandaloneMobileEditorReturning } from "@/lib/mobile-editor";
import {
DEFAULT_MOBILE_EDITOR_MEMO_TITLE,
MOBILE_EDITOR_AUTO_SAVE_DELAY_MS,
@@ -27,7 +27,6 @@ import {
normalizeMobileEditorDoc,
parseMobileEditorTags,
requestMobileEditorJson,
- safeMobileEditorReturnPath,
uploadMobileEditorResource,
type MobileEditorDraft,
type MobileEditorMemoResponse,
@@ -41,18 +40,15 @@ type ListNotebooksResponse = {
type MobileStandaloneTiptapEditorProps = {
memoId?: string | null;
- returnTo?: string;
onLeave?: () => void;
};
export const MobileStandaloneTiptapEditor = ({
memoId: memoIdProp,
- returnTo: returnToProp,
onLeave,
}: MobileStandaloneTiptapEditorProps = {}) => {
const params = useMemo(() => getMobileEditorParams(), []);
const memoId = memoIdProp ?? params.get("memoId");
- const returnTo = safeMobileEditorReturnPath(returnToProp ?? params.get("returnTo"));
const draftKey = getMobileEditorDraftKey(memoId);
const [memo, setMemo] = useState(null);
const memoRef = useRef(null);
@@ -440,8 +436,8 @@ export const MobileStandaloneTiptapEditor = ({
return;
}
- window.location.replace(returnTo);
- }, [memoId, onLeave, returnTo]);
+ window.location.replace(memoId ? getStandaloneMobileEditorReturnPath(memoId) : "/");
+ }, [memoId, onLeave]);
const leavePage = useCallback(async () => {
if (leavingRef.current) {
diff --git a/apps/web/src/components/WorkspaceApp.tsx b/apps/web/src/components/WorkspaceApp.tsx
index 2d91c466..746f0fd1 100644
--- a/apps/web/src/components/WorkspaceApp.tsx
+++ b/apps/web/src/components/WorkspaceApp.tsx
@@ -27,7 +27,7 @@ import {
import { MemoListPane, MemoSelectionActionBar } from "./MemoListPane";
import { AppConfirmDialog, MemoDeleteConfirmDialog, NotebookNameDialog } from "./dialogs/ConfirmDialogs";
import { api } from "@/lib/api";
-import { MOBILE_EDITOR_RETURN_PARAM } from "@/lib/mobile-editor";
+import { MOBILE_EDITOR_RETURN_PARAM, openStandaloneMobileEditor } from "@/lib/mobile-editor";
import { cn } from "@/lib/utils";
import { createExcerpt, docToText, type Notebook, type AuthUser, type MemoSummary, type MemoDetail } from "@edgeever/shared";
import type {
@@ -1296,6 +1296,9 @@ export const WorkspaceApp = ({
setSelectedMemoId(data.memo.id);
setActivePane("editor");
+ if (!isDesktopViewport()) {
+ openStandaloneMobileEditor(data.memo.id);
+ }
},
});
diff --git a/apps/web/src/mobile-edit.tsx b/apps/web/src/mobile-edit.tsx
index ccbdc3b5..a37b0696 100644
--- a/apps/web/src/mobile-edit.tsx
+++ b/apps/web/src/mobile-edit.tsx
@@ -1,4 +1,16 @@
-const params = new URLSearchParams(window.location.hash ? window.location.hash.slice(1) : window.location.search);
-const returnTo = params.get("returnTo");
+import React from "react";
+import { createRoot } from "react-dom/client";
+import { MobileStandaloneTiptapEditor } from "@/components/MobileStandaloneTiptapEditor";
+import "./styles/mobile-markdown-editor.css";
-window.location.replace(returnTo?.startsWith("/") ? returnTo : "/");
+const root = document.getElementById("mobile-editor-root");
+
+if (!root) {
+ throw new Error("Mobile editor root not found");
+}
+
+createRoot(root).render(
+
+
+
+);