feat: 接入 mnote web tree shell 与主页链路整理
- 增加 mnote-web tree/command 支持与前端 MnoteWebTreeShell 集成 - 调整 sidebar、documents、runtime config 与 dev/prod server 配套逻辑 - 补充 homepage/tree shell smoke 脚本并更新 harness 进度文件
This commit is contained in:
@@ -3,14 +3,10 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||
import type { BooleanPageOptionKey, DocumentStats, PageOptionsState, PageFont, PageLayoutDensity } from "@/types/page-options";
|
||||
import { PageOptionsSidebar } from "@/components/editor/page-options-sidebar";
|
||||
import { PageBacklinksPanel } from "@/components/editor/page-backlinks-panel";
|
||||
import { useEditorBridgeStore } from "@/store/editor-bridge";
|
||||
import { usePageLayoutStore } from "@/store/page-layout";
|
||||
import { useCurrentDocumentStore } from "@/store/current-document";
|
||||
import type { Json } from "@/types/supabase";
|
||||
import { DocumentHistoryDrawer } from "@/components/editor/document-history-drawer";
|
||||
import { DocumentCommentsDrawer } from "@/components/editor/document-comments-drawer";
|
||||
import type { DocumentSnapshot } from "@/types/document";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { ImagePickerProvider } from "@/components/media/image-picker-context";
|
||||
@@ -25,6 +21,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { DocumentToc } from "@/components/editor/document-toc";
|
||||
import { DocumentReadView } from "@/components/editor/document-read-view";
|
||||
import { buildPageSubtreeProjection, extractPageBlocks, type PageSubtreeProjection } from "@/lib/documents/page-subtree";
|
||||
import { moveDocumentCommand, renameDocumentCommand } from "@/lib/documents/tree-command-client";
|
||||
|
||||
const BlockNoteEditor = dynamic(
|
||||
() => import("@/components/editor/blocknote-editor").then((mod) => mod.BlockNoteEditor),
|
||||
@@ -36,6 +33,45 @@ const BlockNoteEditor = dynamic(
|
||||
},
|
||||
);
|
||||
|
||||
// 说明:这些组件都不是“进入文档页首屏”所必需。
|
||||
// 先拆成独立 chunk,避免 /documents/[id] 首次编译时把评论、历史、弹层、检查器等整串依赖一并拉进来。
|
||||
const PageOptionsSidebar = dynamic(
|
||||
() => import("@/components/editor/page-options-sidebar").then((mod) => mod.PageOptionsSidebar),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const PageBacklinksPanel = dynamic(
|
||||
() => import("@/components/editor/page-backlinks-panel").then((mod) => mod.PageBacklinksPanel),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
},
|
||||
);
|
||||
|
||||
const DocumentHistoryDrawer = dynamic(
|
||||
() => import("@/components/editor/document-history-drawer").then((mod) => mod.DocumentHistoryDrawer),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
},
|
||||
);
|
||||
|
||||
const MoveEmbedPickerHost = dynamic(
|
||||
() => import("@/components/documents/move-embed-picker-host").then((mod) => mod.MoveEmbedPickerHost),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
},
|
||||
);
|
||||
|
||||
const DocumentCommentsDrawer = dynamic(
|
||||
() => import("@/components/editor/document-comments-drawer").then((mod) => mod.DocumentCommentsDrawer),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
},
|
||||
);
|
||||
|
||||
export interface DocumentContentProps {
|
||||
documentId: string;
|
||||
workspaceId: string;
|
||||
@@ -358,11 +394,11 @@ export function DocumentContent({
|
||||
async (nextTitle: string) => {
|
||||
if (readOnly) return;
|
||||
const payload = nextTitle.trim() || "无标题";
|
||||
await fetch("/api/documents/title", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ documentId, workspaceId, title: payload }),
|
||||
});
|
||||
try {
|
||||
await renameDocumentCommand({ documentId, workspaceId, title: payload });
|
||||
} catch (error) {
|
||||
console.error("更新页面标题失败", error);
|
||||
}
|
||||
},
|
||||
[documentId, readOnly, workspaceId],
|
||||
);
|
||||
@@ -590,14 +626,15 @@ export function DocumentContent({
|
||||
excludeIds: [documentId],
|
||||
onPick: async (mode, targetId) => {
|
||||
if (mode === "move") {
|
||||
const resp = await fetch("/api/documents/move", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ documentId, parentId: targetId ?? null, position: 999999 }),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const payload = await resp.json().catch(() => ({}));
|
||||
window.alert(payload?.error ?? "移动失败");
|
||||
try {
|
||||
await moveDocumentCommand({
|
||||
documentId,
|
||||
parentId: targetId ?? null,
|
||||
position: 999999,
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
window.alert(error instanceof Error ? error.message : "移动失败");
|
||||
return;
|
||||
}
|
||||
window.alert("移动成功");
|
||||
@@ -640,7 +677,18 @@ export function DocumentContent({
|
||||
|
||||
const formattedUpdatedAt = useMemo(() => {
|
||||
if (!updatedAt) return "";
|
||||
return new Date(updatedAt).toLocaleString();
|
||||
const date = new Date(updatedAt);
|
||||
if (Number.isNaN(date.getTime())) return "";
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
timeZone: "Asia/Shanghai",
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
}).format(date);
|
||||
}, [updatedAt]);
|
||||
|
||||
const handleExport = useCallback(() => {
|
||||
@@ -924,6 +972,7 @@ export function DocumentContent({
|
||||
history={history}
|
||||
onRestore={handleRestoreSnapshot}
|
||||
/>
|
||||
<MoveEmbedPickerHost />
|
||||
<DocumentCommentsDrawer />
|
||||
<DocumentAiAgentPanel
|
||||
documentId={documentId}
|
||||
|
||||
Reference in New Issue
Block a user