feat: 收口 mindmap Phase 6 Leptos UI shell
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import "simple-mind-map/dist/simpleMindMap.esm.css";
|
||||
"use client";
|
||||
|
||||
// 说明:本文件仅保留为 legacy/compat/reference 实现,用于独立导图页、
|
||||
// 历史 BlockNote 导图块和 blob import/export 对照;3000 文档页 Phase 6
|
||||
// 默认主链是 leptos-tiptap NodeView + leptos-mindmap adapter。
|
||||
import "simple-mind-map/dist/simpleMindMap.esm.css";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
import { createPortal } from "react-dom";
|
||||
import type { MindMapNode } from "./mindmapTypes";
|
||||
|
||||
// 菜单项配置
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
|
||||
type CountProps = {
|
||||
mindmap: any;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
|
||||
type MiniMapProps = {
|
||||
mindmap: any;
|
||||
show: boolean;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
import { Minus, Plus, Maximize, Map, MapPin, Minimize, Eye, EyeOff } from "lucide-react";
|
||||
|
||||
type NavigatorProps = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import Image from "next/image";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
import Image from "next/image";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { sidebarTriggers, type SidebarPanel } from "./mindmapSidebarConfig";
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React from "react";
|
||||
import {
|
||||
import React from "react";
|
||||
|
||||
// 说明:legacy/reference/compat 组件,仅作为 Phase 6 UI shell 迁移素材。
|
||||
// 3000 文档页默认 mindmap 主链是 leptos-tiptap NodeView + Leptos/Rust shell。
|
||||
import {
|
||||
fileToolbarMeta,
|
||||
fileToolbarOrder,
|
||||
nodeToolbarMeta,
|
||||
|
||||
@@ -5,6 +5,12 @@ import {
|
||||
} from "@/components/editor/editor-host-config";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const phase6MindmapSuccessSelectors = [
|
||||
'[data-testid="mnote-mindmap-editor-root"]',
|
||||
'[data-testid="simple-mind-map-runtime"]',
|
||||
'[data-testid="mindmap-rust-shell"]',
|
||||
];
|
||||
|
||||
describe("editor-host-config", () => {
|
||||
it("保持 island 为默认正式 host", () => {
|
||||
expect(DEFAULT_EDITOR_HOST_KIND).toBe("leptos_tiptap_island");
|
||||
@@ -16,22 +22,34 @@ describe("editor-host-config", () => {
|
||||
expect(normalizeEditorHostKind("leptos_tiptap")).toBe("leptos_tiptap_island");
|
||||
});
|
||||
|
||||
it("保留 iframe debug 作为显式调试 host", () => {
|
||||
expect(normalizeEditorHostKind("leptos_tiptap_iframe_debug")).toBe(
|
||||
"leptos_tiptap_iframe_debug",
|
||||
);
|
||||
expect(normalizeEditorHostKind("iframe_debug")).toBe("leptos_tiptap_iframe_debug");
|
||||
expect(normalizeEditorHostKind("leptos_tiptap_debug")).toBe(
|
||||
"leptos_tiptap_iframe_debug",
|
||||
);
|
||||
});
|
||||
|
||||
it("优先尊重 query override 的 debug host 选择", () => {
|
||||
it("旧 iframe debug host 配置应折返到正式 island host", () => {
|
||||
expect(normalizeEditorHostKind("leptos_tiptap_iframe_debug")).toBe("leptos_tiptap_island");
|
||||
expect(normalizeEditorHostKind("iframe_debug")).toBe("leptos_tiptap_island");
|
||||
expect(normalizeEditorHostKind("leptos_tiptap_debug")).toBe("leptos_tiptap_island");
|
||||
expect(
|
||||
resolveEditorHostKind({
|
||||
override: "leptos_tiptap_iframe_debug",
|
||||
runtimeDefault: "leptos_tiptap_island",
|
||||
}),
|
||||
).toBe("leptos_tiptap_iframe_debug");
|
||||
).toBe("leptos_tiptap_island");
|
||||
});
|
||||
|
||||
it("不再允许通过 query 或 runtime 默认值切回 blocknote", () => {
|
||||
expect(
|
||||
resolveEditorHostKind({
|
||||
override: "blocknote",
|
||||
runtimeDefault: "leptos_tiptap_island",
|
||||
}),
|
||||
).toBe("leptos_tiptap_island");
|
||||
expect(normalizeEditorHostKind("blocknote")).toBe("leptos_tiptap_island");
|
||||
});
|
||||
|
||||
it("Phase 6 mindmap 成功条件不应依赖旧 React runtime 标记", () => {
|
||||
expect(phase6MindmapSuccessSelectors).not.toContain(
|
||||
".mnote-mindmap-react-mount",
|
||||
);
|
||||
expect(phase6MindmapSuccessSelectors).not.toContain(
|
||||
"window.__mindmapInstance",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
export type EditorHostKind =
|
||||
| "blocknote"
|
||||
| "leptos_tiptap_island"
|
||||
| "leptos_tiptap_iframe_debug";
|
||||
export type EditorHostKind = "leptos_tiptap_island";
|
||||
|
||||
export interface EditorHostConfig {
|
||||
kind: EditorHostKind;
|
||||
@@ -15,7 +12,7 @@ export function normalizeEditorHostKind(
|
||||
): EditorHostKind {
|
||||
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
if (normalized === "blocknote") {
|
||||
return "blocknote";
|
||||
return fallback;
|
||||
}
|
||||
if (
|
||||
normalized === "leptos_tiptap_runtime" ||
|
||||
@@ -24,15 +21,7 @@ export function normalizeEditorHostKind(
|
||||
) {
|
||||
return "leptos_tiptap_island";
|
||||
}
|
||||
// 说明:iframe 只保留给显式调试桥,不再混入正式 runtime 主链。
|
||||
if (
|
||||
normalized === "leptos_tiptap_iframe_debug" ||
|
||||
normalized === "leptos_tiptap_debug" ||
|
||||
normalized === "iframe_debug"
|
||||
) {
|
||||
return "leptos_tiptap_iframe_debug";
|
||||
}
|
||||
// 说明:保留历史 query 值兼容;未显式声明 debug 时,一律回到正式 runtime host。
|
||||
// 说明:历史 debug host 已退出文档页主路径,旧 query/runtime 值统一折返正式 island host。
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -53,7 +42,7 @@ export function resolveEditorHostKind(input: {
|
||||
}
|
||||
|
||||
export function isLeptosTiptapHostKind(kind: EditorHostKind): boolean {
|
||||
return kind !== "blocknote";
|
||||
return kind === "leptos_tiptap_island";
|
||||
}
|
||||
|
||||
export function getEditorHostKindFromEnv(value?: unknown): EditorHostKind {
|
||||
|
||||
@@ -17,19 +17,6 @@ const LeptosTiptapIslandEditor = dynamic(
|
||||
},
|
||||
) as ComponentType<DocumentEditorHostProps>;
|
||||
|
||||
const LeptosTiptapIframeDebugEditor = dynamic(
|
||||
() => import("@/components/editor/leptos-tiptap-editor-host").then((mod) => mod.LeptosTiptapEditorHost),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="flex h-64 items-center justify-center text-sm text-gray-400">编辑器加载中...</div>
|
||||
),
|
||||
},
|
||||
) as ComponentType<DocumentEditorHostProps>;
|
||||
|
||||
export function EditorHost(props: DocumentEditorHostProps) {
|
||||
if (props.hostKind === "leptos_tiptap_iframe_debug") {
|
||||
return <LeptosTiptapIframeDebugEditor {...props} />;
|
||||
}
|
||||
return <LeptosTiptapIslandEditor {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { act, useState } from "react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { LeptosTiptapIslandEditorHost } from "@/components/editor/leptos-tiptap-island-editor-host";
|
||||
import type { DocumentEditorHostProps } from "@/components/editor/editor-host-types";
|
||||
|
||||
@@ -135,6 +136,18 @@ describe("leptos-tiptap-island-editor-host", () => {
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
it("默认 island host 不加载旧 React mindmap 主链", () => {
|
||||
const source = readFileSync("src/components/editor/leptos-tiptap-island-editor-host.tsx", "utf8");
|
||||
const smokeSource = readFileSync("../scripts/task166-mindmap-phase6-block-smoke.js", "utf8");
|
||||
|
||||
expect(source).not.toContain("MindmapBlockView");
|
||||
expect(source).not.toContain("mnote-mindmap-react-mount");
|
||||
expect(source).not.toContain("mnoteMindmapData");
|
||||
expect(source).not.toContain("__mindmapInstance");
|
||||
expect(smokeSource).not.toContain("mnote-mindmap-react-mount");
|
||||
expect(smokeSource).not.toContain("__mindmapInstance");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
||||
true;
|
||||
|
||||
@@ -39,8 +39,21 @@ type IslandRuntimeModule = {
|
||||
default: (input?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module) => Promise<unknown>;
|
||||
mount: (container: Element, options: unknown) => number;
|
||||
unmount: (mountId: number) => void;
|
||||
mount_mindmap_shell?: (container: Element, options: unknown) => number;
|
||||
unmount_mindmap_shell?: (mountId: number) => void;
|
||||
};
|
||||
|
||||
type MindmapRustShellBridge = {
|
||||
mount: (container: Element, options: unknown) => number;
|
||||
unmount: (mountId: number) => void;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__MNOTE_MINDMAP_RUST_SHELL__?: MindmapRustShellBridge;
|
||||
}
|
||||
}
|
||||
|
||||
type RuntimeEnvelope<T = unknown> = {
|
||||
protocol?: string;
|
||||
runtime?: string;
|
||||
@@ -251,6 +264,15 @@ async function loadIslandRuntime(): Promise<{
|
||||
throw new Error("island entry 缺少 unmount 导出");
|
||||
}
|
||||
await runtimeModule.default(wasmAssetUrl ?? undefined);
|
||||
if (
|
||||
typeof runtimeModule.mount_mindmap_shell === "function" &&
|
||||
typeof runtimeModule.unmount_mindmap_shell === "function"
|
||||
) {
|
||||
window.__MNOTE_MINDMAP_RUST_SHELL__ = {
|
||||
mount: runtimeModule.mount_mindmap_shell,
|
||||
unmount: runtimeModule.unmount_mindmap_shell,
|
||||
};
|
||||
}
|
||||
return {
|
||||
runtimeModule,
|
||||
entryAssetUrl,
|
||||
@@ -778,9 +800,6 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
|
||||
);
|
||||
}
|
||||
},
|
||||
requestFallbackToBlockNote: () => {
|
||||
requestFallback("explicit_fallback", "通过页面壳显式切回 BlockNote");
|
||||
},
|
||||
};
|
||||
useEditorBridgeStore.getState().registerBridge(editorBridge);
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user