@rtdui/editor
Version:
React rich text editor based on tiptap
322 lines (319 loc) • 13.3 kB
JavaScript
'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