UNPKG

@rtdui/editor

Version:

React rich text editor based on tiptap

215 lines (213 loc) 8.13 kB
'use client'; 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 { CodeBlockShiki } from "./tiptap_extensions/extension-code-block-shiki/code-block-shiki.mjs"; import { CharacterCount, Highlight, Placeholder, StarterKit, Subscript, Superscript, TableKit, TaskItem, TaskList, TextAlign, TextStyleKit } from "./tiptap.mjs"; import { Toolbar } from "./toolbar/Toolbar.mjs"; import "./toolbar/index.mjs"; import { useImperativeHandle } from "react"; import clsx from "clsx"; import { EditorContent, useEditor, useEditorState } from "@tiptap/react"; import { FloatingMenu } from "@tiptap/react/menus"; import { IconArrowBarBoth, IconBoxAlignLeft, IconBoxAlignTop, IconBoxAlignTopLeft, IconColumnInsertLeft, IconColumnInsertRight, IconContainerOff, IconRowInsertBottom, IconRowInsertTop, IconSquareToggle, IconTable, IconTableOff, IconViewportNarrow } from "@tabler/icons-react"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; //#region packages/editor/src/RichTextEditor/RichTextEditor.tsx function RichTextEditor(props) { const { ref, slots, editable = true, placeholder = "输入内容或者从剪贴板粘贴, 复制粘贴支持Markdown", limit, uploadImageUrl, imageResizable = true, className, ...other } = props; const editorOptions = { extensions: [ StarterKit.configure({ codeBlock: false, link: { openOnClick: !editable } }), TableKit.configure({ table: { resizable: true }, tableCell: { HTMLAttributes: { class: "not-prose" } }, tableHeader: { HTMLAttributes: { class: "not-prose" } } }), TaskItem.configure({ nested: true }), TaskList, Superscript, Subscript, Highlight, Placeholder.configure({ placeholder }), TextAlign.configure({ types: [ "heading", "paragraph", "tableHeader", "tableCell" ] }), TextStyleKit, CodeBlockShiki, UploadImageWithResizable.configure({ resizable: imageResizable, inline: true, url: uploadImageUrl, method: "post" }), MarkdownPaste, MathKatexInline, MathKatexBlock ], editable }; if (limit) editorOptions.extensions?.push(CharacterCount.configure({ limit })); const editor = useEditor(editorOptions); const { charactersCount, wordsCount } = useEditorState({ editor, selector: (context) => ({ charactersCount: context.editor?.storage?.characterCount?.characters(), wordsCount: context.editor?.storage?.characterCount?.words() }) }); 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, className: clsx(className), children: [ editable && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Toolbar, { editor, className: clsx("editor-content", slots?.toolbar) }), /* @__PURE__ */ jsx(FloatingMenu, { editor, shouldShow: ({ editor }) => editor.isActive("tableCell") || editor.isActive("tableHeader"), options: { autoPlacement: { allowedPlacements: ["top-start", "top-end"] } }, children: /* @__PURE__ */ jsxs("div", { className: "join", children: [ /* @__PURE__ */ jsx("button", { type: "button", className: "join-item btn btn-sm px-1", title: "插入表格", 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: "左侧插入列", 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: "右侧插入列", 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: "删除列", 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: "上方插入行", 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: "下方插入行", 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: "删除行", 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: "删除表格", 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: "合并单元格", 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: "拆分单元格", 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: "合并或拆分", 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: "首列切换", 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: "首行切换", 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: "单元格切换行头样式", onClick: () => editor.chain().focus().toggleHeaderCell().run(), children: /* @__PURE__ */ jsx(IconBoxAlignTopLeft, { stroke: 1 }) }) ] }) })] }), /* @__PURE__ */ jsx(EditorContent, { editor, className: clsx("editor-content prose max-w-none! overflow-y-auto", slots?.content) }), limit && /* @__PURE__ */ jsxs("div", { className: "character-count text-xs flex flex-row-reverse px-1.5", children: [ charactersCount, " / ", limit ] }) ] }); } //#endregion export { RichTextEditor };