@rtdui/editor
Version:
React rich text editor based on tiptap
466 lines (464 loc) • 16.3 kB
JavaScript
'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 };