UNPKG

@rtdui/editor

Version:

React rich text editor based on tiptap

322 lines (319 loc) 13.3 kB
'use client'; import { jsxs, Fragment, jsx } from 'react/jsx-runtime'; import { forwardRef, useImperativeHandle } from 'react'; import clsx from 'clsx'; import { useEditor, FloatingMenu, findParentNodeClosestToPos, posToDOMRect, EditorContent } from '@tiptap/react'; import { IconTable, IconColumnInsertLeft, IconColumnInsertRight, IconContainerOff, IconRowInsertTop, IconRowInsertBottom, IconTableOff, IconViewportNarrow, IconArrowBarBoth, IconSquareToggle, IconBoxAlignLeft, IconBoxAlignTop, IconBoxAlignTopLeft } from '@tabler/icons-react'; import { StarterKit } from '@tiptap/starter-kit'; import { TableCell } from '@tiptap/extension-table-cell'; import { TableHeader } from '@tiptap/extension-table-header'; import { TableRow } from '@tiptap/extension-table-row'; import { Link } from '@tiptap/extension-link'; import { TextAlign } from '@tiptap/extension-text-align'; import { Highlight } from '@tiptap/extension-highlight'; import SuperScript from '@tiptap/extension-superscript'; import SubScript from '@tiptap/extension-subscript'; import { Placeholder } from '@tiptap/extension-placeholder'; import { TaskList } from '@tiptap/extension-task-list'; import { TaskItem } from '@tiptap/extension-task-item'; import { UploadImageWithResizable } from './tiptap_extensions/extension-image-upload/uploadImageWithResizable.mjs'; import { MarkdownPaste } from './tiptap_extensions/extension-markdown-paste/markdownPaste.mjs'; import { MathKatexInline } from './tiptap_extensions/extension-math/math-katex-inline.mjs'; import { MathKatexBlock } from './tiptap_extensions/extension-math/math-katex-block.mjs'; import { CustomTable } from './tiptap_extensions/extension-table/table.mjs'; import { CodeBlockShiki } from './tiptap_extensions/extension-code-block-shiki/code-block-shiki.mjs'; import { EditorControl } from './tiptap_controls/EditorControl.mjs'; import { HelperControl } from './tiptap_controls/HelperControl.mjs'; const RichTextEditor = forwardRef( (props, ref) => { const { slots, editable = true, placeholder = "\u8F93\u5165\u5185\u5BB9\u6216\u8005\u4ECE\u526A\u8D34\u677F\u7C98\u8D34, \u590D\u5236\u7C98\u8D34\u652F\u6301Markdown", uploadImageUrl, imageResizable = true, ...other } = props; const editorOptions = { extensions: [ StarterKit.configure({ // history: false, // The Collaboration extension comes with its own history handling codeBlock: false // 会使用CodeBlockHighlight替代 }), CustomTable.configure({ resizable: true }), TableRow, TableHeader, TableCell, Link, TaskList, TaskItem.configure({ nested: true }), SuperScript, SubScript, Highlight, Placeholder.configure({ placeholder }), TextAlign.configure({ types: ["heading", "paragraph", "tableHeader", "tableCell"] }), CodeBlockShiki, // Register the document with Tiptap // Collaboration.configure({ // document: ydoc, // }), // // Register the collaboration cursor extension // CollaborationCursor.configure({ // provider, // user: { // name, // color, // }, // }), UploadImageWithResizable.configure({ resizable: imageResizable, inline: true, url: uploadImageUrl, method: "post" }), MarkdownPaste, MathKatexInline, MathKatexBlock ], editable }; const editor = useEditor(editorOptions); useImperativeHandle(ref, () => ({ getJSON: () => { if (editor) { return editor.getJSON(); } return null; }, setContent: (jsonOrHtml) => { if (editor) { editor.commands.setContent(jsonOrHtml); } } })); if (!editor) { return null; } return /* @__PURE__ */ jsxs("div", { ...other, children: [ editor && editable && /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx(EditorControl, { editor, children: /* @__PURE__ */ jsxs(EditorControl.Toolbar, { sticky: true, className: clsx(slots?.toolbar), children: [ /* @__PURE__ */ jsxs(EditorControl.ControlsGroup, { children: [ /* @__PURE__ */ jsx(EditorControl.Bold, {}), /* @__PURE__ */ jsx(EditorControl.Italic, {}), /* @__PURE__ */ jsx(EditorControl.Strikethrough, {}), /* @__PURE__ */ jsx(EditorControl.Highlight, {}), /* @__PURE__ */ jsx(EditorControl.Code, {}), /* @__PURE__ */ jsx(EditorControl.ClearFormatting, {}) ] }), /* @__PURE__ */ jsxs(EditorControl.ControlsGroup, { children: [ /* @__PURE__ */ jsx(EditorControl.H1, {}), /* @__PURE__ */ jsx(EditorControl.H2, {}), /* @__PURE__ */ jsx(EditorControl.H3, {}), /* @__PURE__ */ jsx(EditorControl.H4, {}) ] }), /* @__PURE__ */ jsxs(EditorControl.ControlsGroup, { children: [ /* @__PURE__ */ jsx(EditorControl.Blockquote, {}), /* @__PURE__ */ jsx(EditorControl.Hr, {}), /* @__PURE__ */ jsx(EditorControl.BulletList, {}), /* @__PURE__ */ jsx(EditorControl.OrderedList, {}), /* @__PURE__ */ jsx(EditorControl.Subscript, {}), /* @__PURE__ */ jsx(EditorControl.Superscript, {}) ] }), /* @__PURE__ */ jsxs(EditorControl.ControlsGroup, { children: [ /* @__PURE__ */ jsx(EditorControl.AlignLeft, {}), /* @__PURE__ */ jsx(EditorControl.AlignCenter, {}), /* @__PURE__ */ jsx(EditorControl.AlignRight, {}), /* @__PURE__ */ jsx(EditorControl.AlignJustify, {}) ] }), /* @__PURE__ */ jsxs(EditorControl.ControlsGroup, { children: [ /* @__PURE__ */ jsx(EditorControl.Image, {}), /* @__PURE__ */ jsx(EditorControl.Table, {}) ] }), /* @__PURE__ */ jsx(EditorControl.ControlsGroup, { children: /* @__PURE__ */ jsx(HelperControl, {}) }) ] }) }), /* @__PURE__ */ jsx( FloatingMenu, { editor, shouldShow: ({ editor: editor2 }) => editor2.isActive("tableCell") || editor2.isActive("tableHeader"), tippyOptions: { placement: "top", getReferenceClientRect: () => { const tableNode = findParentNodeClosestToPos( editor.view.state.selection.$from, (node) => node.type.name === "table" ); return posToDOMRect( editor.view, tableNode?.start, tableNode?.start ); } }, children: /* @__PURE__ */ jsxs("div", { className: "join", children: [ /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u63D2\u5165\u8868\u683C", onClick: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), children: /* @__PURE__ */ jsx(IconTable, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5DE6\u4FA7\u63D2\u5165\u5217", onClick: () => editor.chain().focus().addColumnBefore().run(), children: /* @__PURE__ */ jsx(IconColumnInsertLeft, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u53F3\u4FA7\u63D2\u5165\u5217", onClick: () => editor.chain().focus().addColumnAfter().run(), children: /* @__PURE__ */ jsx(IconColumnInsertRight, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5220\u9664\u5217", onClick: () => editor.chain().focus().deleteColumn().run(), children: /* @__PURE__ */ jsx(IconContainerOff, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u4E0A\u65B9\u63D2\u5165\u884C", onClick: () => editor.chain().focus().addRowBefore().run(), children: /* @__PURE__ */ jsx(IconRowInsertTop, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u4E0B\u65B9\u63D2\u5165\u884C", onClick: () => editor.chain().focus().addRowAfter().run(), children: /* @__PURE__ */ jsx(IconRowInsertBottom, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5220\u9664\u884C", onClick: () => editor.chain().focus().deleteRow().run(), children: /* @__PURE__ */ jsx(IconContainerOff, { className: "rotate-90", stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5220\u9664\u8868\u683C", onClick: () => editor.chain().focus().deleteTable().run(), children: /* @__PURE__ */ jsx(IconTableOff, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5408\u5E76\u5355\u5143\u683C", onClick: () => editor.chain().focus().mergeCells().run(), children: /* @__PURE__ */ jsx(IconViewportNarrow, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u62C6\u5206\u5355\u5143\u683C", onClick: () => editor.chain().focus().splitCell().run(), children: /* @__PURE__ */ jsx(IconArrowBarBoth, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5408\u5E76\u6216\u62C6\u5206", onClick: () => editor.chain().focus().mergeOrSplit().run(), children: /* @__PURE__ */ jsx(IconSquareToggle, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u9996\u5217\u5207\u6362", onClick: () => editor.chain().focus().toggleHeaderColumn().run(), children: /* @__PURE__ */ jsx(IconBoxAlignLeft, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u9996\u884C\u5207\u6362", onClick: () => editor.chain().focus().toggleHeaderRow().run(), children: /* @__PURE__ */ jsx(IconBoxAlignTop, { stroke: 1 }) } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "join-item btn btn-sm px-1", title: "\u5355\u5143\u683C\u5207\u6362\u884C\u5934\u6837\u5F0F", onClick: () => editor.chain().focus().toggleHeaderCell().run(), children: /* @__PURE__ */ jsx(IconBoxAlignTopLeft, { stroke: 1 }) } ) ] }) } ) ] }), /* @__PURE__ */ jsx( EditorContent, { editor, className: clsx("editor-content prose max-w-none!", slots?.content) } ) ] }); } ); RichTextEditor.displayName = "@rtdui/RichTextEditor"; export { RichTextEditor }; //# sourceMappingURL=RichTextEditor.mjs.map