@@ -142,14 +147,14 @@ const EditorToolbar = ({ editor }: { editor: Editor }) => {
/>
- );
-};
+ )
+}
-interface RichTextEditorInnerProps {
- value: string;
- onChange: (value: string) => void;
- placeholder?: string;
- className?: string;
+export interface RichTextEditorProps {
+ value: string
+ onChange: (value: string) => void
+ placeholder?: string
+ className?: string
}
export function RichTextEditorInner({
@@ -157,7 +162,7 @@ export function RichTextEditorInner({
onChange,
placeholder = "Start writing...",
className,
-}: RichTextEditorInnerProps): React.ReactElement {
+}: RichTextEditorProps) {
const editor = useEditor({
immediatelyRender: false,
extensions: [
@@ -169,8 +174,7 @@ export function RichTextEditorInner({
Markdown,
Placeholder.configure({
placeholder,
- emptyEditorClass:
- "is-editor-empty before:content-[attr(data-placeholder)] before:text-muted-foreground before:float-left before:pointer-events-none before:h-0",
+ emptyEditorClass: "is-editor-empty before:content-[attr(data-placeholder)] before:text-muted-foreground before:float-left before:pointer-events-none before:h-0",
}),
],
editorProps: {
@@ -181,27 +185,22 @@ export function RichTextEditorInner({
content: value,
onUpdate: ({ editor }) => {
// Get markdown content
- const markdown = (editor.storage as any).markdown.getMarkdown();
- onChange(markdown);
+ const markdown = (editor.storage as any).markdown.getMarkdown()
+ onChange(markdown)
},
- });
+ })
// Sync value changes from outside (e.g. when switching chapters)
React.useEffect(() => {
if (editor && value !== (editor.storage as any).markdown.getMarkdown()) {
- editor.commands.setContent(value);
+ editor.commands.setContent(value)
}
- }, [value, editor]);
+ }, [value, editor])
return (
-
+
- );
+ )
}
diff --git a/src/shared/components/ui/rich-text-editor.tsx b/src/shared/components/ui/rich-text-editor.tsx
index 80d36d8..8d90882 100644
--- a/src/shared/components/ui/rich-text-editor.tsx
+++ b/src/shared/components/ui/rich-text-editor.tsx
@@ -1,201 +1,29 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
"use client"
-import * as React from "react"
-import { useEditor, EditorContent, type Editor } from "@tiptap/react"
-import StarterKit from "@tiptap/starter-kit"
-import { Markdown } from "tiptap-markdown"
-import Placeholder from "@tiptap/extension-placeholder"
-import {
- Bold,
- Italic,
- Strikethrough,
- Code,
- Heading1,
- Heading2,
- Heading3,
- List,
- ListOrdered,
- Quote,
- Undo,
- Redo
-} from "lucide-react"
+/**
+ * 通用富文本编辑器 —— 懒加载入口(Phase 2.1)
+ *
+ * 实际实现位于 rich-text-editor-inner.tsx,通过 next/dynamic 以 ssr:false 懒加载,
+ * 使 @tiptap/* 重型依赖(约 180-220 KB)被打入独立 chunk,避免污染首屏 bundle。
+ * 加载完成前展示 Skeleton 占位骨架。
+ */
-import { cn } from "@/shared/lib/utils"
-import { Button } from "@/shared/components/ui/button"
-import { Separator } from "@/shared/components/ui/separator"
+import dynamic from "next/dynamic"
+import { Skeleton } from "@/shared/components/ui/skeleton"
-// Since we don't have Toggle component yet, let's create a local one or use Button
-// We will use Button for simplicity and to avoid dependency issues if Radix Toggle isn't installed
-const ToolbarButton = ({
- isActive,
- onClick,
- icon: Icon,
- title,
-}: {
- isActive?: boolean
- onClick: () => void
- icon: React.ElementType
- title: string
-}) => (
-
+export type { RichTextEditorProps } from "./rich-text-editor-inner"
+
+const LazyInner = dynamic(
+ () =>
+ import("./rich-text-editor-inner").then((m) => m.RichTextEditorInner),
+ {
+ ssr: false,
+ loading: () =>
,
+ },
)
-const EditorToolbar = ({ editor }: { editor: Editor }) => {
- if (!editor) return null
-
- return (
-
-
editor.chain().focus().toggleBold().run()}
- isActive={editor.isActive("bold")}
- icon={Bold}
- title="Bold"
- />
- editor.chain().focus().toggleItalic().run()}
- isActive={editor.isActive("italic")}
- icon={Italic}
- title="Italic"
- />
- editor.chain().focus().toggleStrike().run()}
- isActive={editor.isActive("strike")}
- icon={Strikethrough}
- title="Strikethrough"
- />
- editor.chain().focus().toggleCode().run()}
- isActive={editor.isActive("code")}
- icon={Code}
- title="Code"
- />
-
-
-
- editor.chain().focus().toggleHeading({ level: 1 }).run()}
- isActive={editor.isActive("heading", { level: 1 })}
- icon={Heading1}
- title="Heading 1"
- />
- editor.chain().focus().toggleHeading({ level: 2 }).run()}
- isActive={editor.isActive("heading", { level: 2 })}
- icon={Heading2}
- title="Heading 2"
- />
- editor.chain().focus().toggleHeading({ level: 3 }).run()}
- isActive={editor.isActive("heading", { level: 3 })}
- icon={Heading3}
- title="Heading 3"
- />
-
-
-
- editor.chain().focus().toggleBulletList().run()}
- isActive={editor.isActive("bulletList")}
- icon={List}
- title="Bullet List"
- />
- editor.chain().focus().toggleOrderedList().run()}
- isActive={editor.isActive("orderedList")}
- icon={ListOrdered}
- title="Ordered List"
- />
- editor.chain().focus().toggleBlockquote().run()}
- isActive={editor.isActive("blockquote")}
- icon={Quote}
- title="Blockquote"
- />
-
-
- editor.chain().focus().undo().run()}
- isActive={false}
- icon={Undo}
- title="Undo"
- />
- editor.chain().focus().redo().run()}
- isActive={false}
- icon={Redo}
- title="Redo"
- />
-
-
- )
-}
-
-interface RichTextEditorProps {
- value: string
- onChange: (value: string) => void
- placeholder?: string
- className?: string
-}
-
-export function RichTextEditor({
- value,
- onChange,
- placeholder = "Start writing...",
- className,
-}: RichTextEditorProps) {
- const editor = useEditor({
- immediatelyRender: false,
- extensions: [
- StarterKit.configure({
- heading: {
- levels: [1, 2, 3],
- },
- }),
- Markdown,
- Placeholder.configure({
- placeholder,
- emptyEditorClass: "is-editor-empty before:content-[attr(data-placeholder)] before:text-muted-foreground before:float-left before:pointer-events-none before:h-0",
- }),
- ],
- editorProps: {
- attributes: {
- class: "prose prose-sm dark:prose-invert max-w-none min-h-[150px] p-4 focus:outline-none",
- },
- },
- content: value,
- onUpdate: ({ editor }) => {
- // Get markdown content
- const markdown = (editor.storage as any).markdown.getMarkdown()
- onChange(markdown)
- },
- })
-
- // Sync value changes from outside (e.g. when switching chapters)
- React.useEffect(() => {
- if (editor && value !== (editor.storage as any).markdown.getMarkdown()) {
- editor.commands.setContent(value)
- }
- }, [value, editor])
-
- return (
-
-
-
-
- )
+export function RichTextEditor(
+ props: React.ComponentProps
,
+): React.ReactElement {
+ return
}