UNPKG

@rtdui/editor

Version:

React rich text editor based on tiptap

466 lines (464 loc) 16.3 kB
'use client'; import { menuBarStateSelector } from "./toolbarStateSelector.mjs"; import { ColorPickerPopover } from "./ColorPickerPopover.mjs"; import MDXContent from "./editorMan.mjs"; import { useState } from "react"; import clsx from "clsx"; import { useEditorState } from "@tiptap/react"; import { IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBlockquote, IconBold, IconCode, IconCodeblock, IconCornerDownLeft, IconEraser, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconHelp, IconItalic, IconLink, IconList, IconListNumbers, IconPaint, IconPhoto, IconSeparator, IconStrikethrough, IconSubscript, IconSuperscript, IconTable, IconUnderline, IconUnlink } from "@tabler/icons-react"; import { jsx, jsxs } from "react/jsx-runtime"; import { CloseButton } from "@rtdui/core"; //#region packages/editor/src/RichTextEditor/toolbar/Toolbar.tsx const FONT_SIZES = [ "8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72" ]; const Toolbar = (props) => { const { editor, className, ...other } = props; if (!editor) return null; const editorState = useEditorState({ editor, selector: menuBarStateSelector }); const [color, setColor] = useState("auto"); const [bgColor, setBgColor] = useState("auto"); return /* @__PURE__ */ jsxs("div", { ...other, className: clsx("flex gap-1 p-1", className), children: [ /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsx("button", { title: "撤销", onClick: () => editor.chain().focus().undo().run(), disabled: !editorState.canUndo, className: clsx("join-item btn btn-xs btn-square"), children: /* @__PURE__ */ jsx(IconArrowBackUp, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "重做", onClick: () => editor.chain().focus().redo().run(), disabled: !editorState.canRedo, className: clsx("join-item btn btn-xs btn-square"), children: /* @__PURE__ */ jsx(IconArrowForwardUp, { stroke: 1.5, size: 18 }) })] }), /* @__PURE__ */ jsx("div", { className: "join", children: /* @__PURE__ */ jsx("select", { value: editorState.fontSize, onChange: (ev) => { const val = ev.target.value; if (val === "16") editor.chain().focus().unsetFontSize().run(); else editor.chain().focus().setFontSize(`${val}px`).run(); }, className: "select select-xs w-16 join-item", children: FONT_SIZES.map((d) => /* @__PURE__ */ jsx("option", { children: d }, d)) }) }), /* @__PURE__ */ jsxs("div", { className: "join", children: [ /* @__PURE__ */ jsx("button", { title: "粗体", onClick: () => editor.chain().focus().toggleBold().run(), disabled: !editorState.canBold, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isBold }), children: /* @__PURE__ */ jsx(IconBold, { stroke: 2, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "斜体", onClick: () => editor.chain().focus().toggleItalic().run(), disabled: !editorState.canItalic, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isItalic }), children: /* @__PURE__ */ jsx(IconItalic, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "下划线", onClick: () => editor.chain().focus().toggleUnderline().run(), disabled: !editorState.canUnderline, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isUnderline }), children: /* @__PURE__ */ jsx(IconUnderline, { stroke: 1.5, size: 20 }) }), /* @__PURE__ */ jsx("button", { title: "删除线", onClick: () => editor.chain().focus().toggleStrike().run(), disabled: !editorState.canStrike, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isStrike }), children: /* @__PURE__ */ jsx(IconStrikethrough, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "上标", onClick: () => editor.chain().focus().toggleSuperscript().run(), disabled: !editorState.canSuperscript, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isSuperscript }), children: /* @__PURE__ */ jsx(IconSuperscript, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "下标", onClick: () => editor.chain().focus().toggleSubscript().run(), disabled: !editorState.canSubscript, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isSubscript }), children: /* @__PURE__ */ jsx(IconSubscript, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "代码", onClick: () => editor.chain().focus().toggleCode().run(), disabled: !editorState.canCode, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isCode }), children: /* @__PURE__ */ jsx(IconCode, { stroke: 1.5, size: 18 }) }) ] }), /* @__PURE__ */ jsx("div", { className: "join", children: /* @__PURE__ */ jsx("button", { title: "高亮", onClick: () => editor.chain().focus().toggleHighlight().run(), disabled: !editorState.canHighlight, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHighlight }), children: /* @__PURE__ */ jsx("div", { className: "size-4 flex items-center justify-center bg-warning text-sm font-normal", children: "A" }) }) }), /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsxs("button", { title: "文本颜色", onClick: () => { if (color === "auto") editor.chain().focus().unsetColor().run(); else editor.chain().focus().setColor(color).run(); }, disabled: !editorState.canColor, className: clsx("join-item btn btn-xs btn-square relative text-sm font-normal"), children: ["A", /* @__PURE__ */ jsx("div", { className: "absolute left-1 right-1 bottom-0.5 z-1 h-1", style: { backgroundColor: color === "auto" ? "black" : color } })] }), /* @__PURE__ */ jsx(ColorPickerPopover, { onChangeEnd: (val) => setColor(val) })] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsxs("button", { title: "背景颜色", onClick: () => { if (bgColor === "auto") editor.chain().focus().unsetBackgroundColor().run(); else editor.chain().focus().setBackgroundColor(bgColor).run(); }, disabled: !editorState.canBgColor, className: clsx("join-item btn btn-xs btn-square relative text-sm font-normal"), children: [/* @__PURE__ */ jsx(IconPaint, { stroke: 1.5, size: 14 }), /* @__PURE__ */ jsx("div", { className: "absolute left-1 right-1 bottom-0.5 z-1 h-1", style: { backgroundColor: bgColor === "auto" ? "black" : bgColor } })] }), /* @__PURE__ */ jsx(ColorPickerPopover, { onChangeEnd: (val) => setBgColor(val) })] }), /* @__PURE__ */ jsx("div", { className: "join", children: /* @__PURE__ */ jsx("button", { title: "清除格式", onClick: () => editor.chain().focus().unsetAllMarks().clearNodes().run(), className: clsx("join-item btn btn-xs btn-square"), children: /* @__PURE__ */ jsx(IconEraser, { stroke: 1.5, size: 18 }) }) }), /* @__PURE__ */ jsxs("div", { className: "join", children: [ /* @__PURE__ */ jsx("button", { title: "H1", onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHeading1 }), children: /* @__PURE__ */ jsx(IconH1, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "H2", onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHeading2 }), children: /* @__PURE__ */ jsx(IconH2, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "H3", onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHeading3 }), children: /* @__PURE__ */ jsx(IconH3, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "H4", onClick: () => editor.chain().focus().toggleHeading({ level: 4 }).run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHeading4 }), children: /* @__PURE__ */ jsx(IconH4, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "H5", onClick: () => editor.chain().focus().toggleHeading({ level: 5 }).run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHeading5 }), children: /* @__PURE__ */ jsx(IconH5, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "H6", onClick: () => editor.chain().focus().toggleHeading({ level: 6 }).run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHeading6 }), children: /* @__PURE__ */ jsx(IconH6, { stroke: 1.5, size: 18 }) }) ] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [ /* @__PURE__ */ jsx("button", { title: "符号列表", onClick: () => editor.chain().focus().toggleBulletList().run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isBulletList }), children: /* @__PURE__ */ jsx(IconList, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "序号列表", onClick: () => editor.chain().focus().toggleOrderedList().run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isOrderedList }), children: /* @__PURE__ */ jsx(IconListNumbers, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "代码块", onClick: () => editor.chain().focus().toggleCodeBlock().run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isCodeBlock }), children: /* @__PURE__ */ jsx(IconCodeblock, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "引用", onClick: () => editor.chain().focus().toggleBlockquote().run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isBlockquote }), children: /* @__PURE__ */ jsx(IconBlockquote, { stroke: 1.5, size: 18 }) }) ] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [ /* @__PURE__ */ jsx("button", { title: "左对齐", onClick: () => editor.chain().focus().toggleTextAlign("left").run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isLeftAlign }), children: /* @__PURE__ */ jsx(IconAlignLeft, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "居中对齐", onClick: () => editor.chain().focus().toggleTextAlign("center").run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isCenterAlign }), children: /* @__PURE__ */ jsx(IconAlignCenter, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "右对齐", onClick: () => editor.chain().focus().toggleTextAlign("right").run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isRightAlign }), children: /* @__PURE__ */ jsx(IconAlignRight, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "两端对齐", onClick: () => editor.chain().focus().toggleTextAlign("justify").run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isJustifyAlign }), children: /* @__PURE__ */ jsx(IconAlignJustified, { stroke: 1.5, size: 18 }) }) ] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsx("button", { title: "超链接", onClick: () => { const previousUrl = editor.getAttributes("link").href; const url = window.prompt("URL", previousUrl); if (url === null) return; if (url === "") { editor.chain().focus().extendMarkRange("link").unsetLink().run(); return; } try { editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); } catch (e) { alert(e.message); } }, className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isLink }), children: /* @__PURE__ */ jsx(IconLink, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "取消超链接", onClick: () => editor.chain().focus().unsetLink().run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isLink }), children: /* @__PURE__ */ jsx(IconUnlink, { stroke: 1.5, size: 18 }) })] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsx("button", { title: "水平线", onClick: () => editor.chain().focus().setHorizontalRule().run(), className: clsx("join-item btn btn-xs btn-square"), children: /* @__PURE__ */ jsx(IconSeparator, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "断行", onClick: () => editor.chain().focus().setHardBreak().run(), className: clsx("join-item btn btn-xs btn-square", { "btn-active": editorState.isHardBreak }), children: /* @__PURE__ */ jsx(IconCornerDownLeft, { stroke: 1.5, size: 18 }) })] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsx("button", { title: "插入图片", onClick: () => editor.chain().focus().uploadImage({}).run(), className: clsx("join-item btn btn-xs btn-square"), children: /* @__PURE__ */ jsx(IconPhoto, { stroke: 1.5, size: 18 }) }), /* @__PURE__ */ jsx("button", { title: "插入表格", onClick: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), className: clsx("join-item btn btn-xs btn-square"), children: /* @__PURE__ */ jsx(IconTable, { stroke: 1.5, size: 18 }) })] }), /* @__PURE__ */ jsxs("div", { className: "join", children: [/* @__PURE__ */ jsx("button", { type: "button", className: "join-item btn btn-xs btn-square", onClick: () => document.getElementById("edit_helper_dialog")?.showModal(), children: /* @__PURE__ */ jsx(IconHelp, { size: 20, stroke: 1.5 }) }), /* @__PURE__ */ jsx("dialog", { id: "edit_helper_dialog", className: "modal", children: /* @__PURE__ */ jsxs("div", { className: clsx("modal-box flex flex-col p-6 w-11/12 max-w-5xl", className), children: [/* @__PURE__ */ jsx("form", { method: "dialog", children: /* @__PURE__ */ jsx("div", { className: "sticky top-0 flex justify-end mb-1", children: /* @__PURE__ */ jsx(CloseButton, { type: "submit" }) }) }), /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto prose max-w-none", ...other, children: /* @__PURE__ */ jsx(MDXContent, {}) })] }) })] }) ] }); }; //#endregion export { Toolbar };