跳到主要内容

menuItem

2024年07月05日
柏拉文
越努力,越幸运

一、boldMenu.tsx


import { Editor } from "@tiptap/core";

export type BoldMenuProps = {
editor: Editor;
};

function BoldMenu(props: BoldMenuProps) {
const { editor } = props;

const isEmpty = editor.isEmpty;
const isFocused = editor.isFocused;

const onBold = () => {
if(isEmpty && !isFocused){
console.log("kkk")
return;
}

editor.chain().focus().toggleBold().run();
};

return (
<div className="menu-item-container menu-item__bold-menu">
<div
onClick={onBold}
className={`menu-item bold-menu_item ${
editor.isActive("bold") ? "menu-item-active" : ""
}`}
>
加粗
</div>
</div>
);
}

export default BoldMenu;

二、bulletListMenu.tsx


import { Editor } from "@tiptap/core";

export type BulletListMenuMenuProps = {
editor: Editor;
};

function BulletListMenuMenu(props: BulletListMenuMenuProps) {
const { editor } = props;

const onbulletListMenu = () => {
editor.chain().focus().toggleBulletList().run();
};

return (
<div className="menu-item-container menu-item__bullet-list-menu">
<div
onClick={onbulletListMenu}
className={`menu-item bullet-list-menu_item ${
editor.isActive("bulletList") ? "menu-item-active" : ""
}`}
>
符号列表
</div>
</div>
);
}

export default BulletListMenuMenu;

三、codeMenu.tsx


import { Editor } from "@tiptap/core";

export type CodeMenuProps = {
editor: Editor;
};

function CodeMenu(props: CodeMenuProps) {
const { editor } = props;

const onCode = () => {
editor.chain().focus().toggleCode().run();
};

return (
<div className="menu-item-container menu-item__code-menu">
<div className={`menu-item code-menu_item`} onClick={onCode}>
代码块
</div>
</div>
);
}

export default CodeMenu;

四、doMenu.tsx


import { Editor } from "@tiptap/core";

export type UndoMenuProps = {
editor: Editor;
};

function DoMenu(props: UndoMenuProps) {
const { editor } = props;

const onUndo = () => {
if (!editor.can().undo()) {
return;
}
editor.chain().focus().undo().run();
};

const onRedo = () => {
if (!editor.can().redo()) {
return;
}
editor.chain().focus().redo().run();
};

return (
<div className="menu-item-container menu-item__do-menu">
<div className="do-menu__list">
<div
className={`menu-item do-menu__item ${
!editor.can().undo() ? "menu-item-disabled" : ""
}`}
onClick={onUndo}
>
撤销
</div>
<div
className={`menu-item do-menu__item ${
!editor.can().redo() ? "menu-item-disabled" : ""
}`}
onClick={onRedo}
>
重做
</div>
</div>
</div>
);
}

export default DoMenu;

五、fontColorMenu.tsx


import { Editor } from "@tiptap/react";

type FontColorMenuProps = {
editor: Editor;
};

function FontColorMenu(props: FontColorMenuProps) {
const { editor } = props;
const textStyle = editor.getAttributes("textStyle") || {};
const color = textStyle.color || "#222222";

const onInputChange = (event: any) => {
const value = event.target.value;
editor.chain().focus().setColor(value).run();
editor.commands.focus();
};

return (
<div className="menu-item-container menu-item__font-color-menu">
<label
className="menu-item font-color-menu__label"
htmlFor="font-color-menu__select-panel__uniqueId"
>
字体颜色
<input
hidden
type="color"
value={color}
onChange={onInputChange}
className="font-color-menu__select-panel"
id="font-color-menu__select-panel__uniqueId"
/>
</label>
</div>
);
}

export default FontColorMenu;

六、fontSizeMenu.tsx


import { Editor } from "@tiptap/react";
import { useEffect, useState } from "react";

type FontSizeMenuProps = {
editor: Editor;
};

const sizeList = [12, 14, 15, 16, 17, 18, 20, 24];
const normalizedSizeList = sizeList.map((size) => ({
id: `${size}`,
label: size + "px",
}));

function FontSizeMenu(props: FontSizeMenuProps) {
const { editor } = props;
const [showModal, setShowModal] = useState(false);
const [fontSize, setFontSize] = useState(normalizedSizeList[0].id);

const onSelect = (item: { id: string; label: string }) => {
const size = item.id;
setShowModal(false);
setFontSize(size);
editor.commands.setFontSize(size);
};

useEffect(() => {
editor.on("selectionUpdate", () => {
const fontSize = editor.getAttributes("textStyle").fontSize;
setFontSize(fontSize || normalizedSizeList[0].id);
});
}, []);

return (
<div className="menu-item-container menu-item__font-size-menu">
<div
className="menu-item font-size-menu__trigger"
onClick={() => setShowModal(true)}
>
{fontSize + "px"}
</div>
{showModal && (
<div className="font-size-menu__select-panel">
<div className="font-size-menu__font-size-list">
{normalizedSizeList.map((item) => (
<div
key={item.id}
onClick={() => onSelect(item)}
className={`font-size-menu__font-size-item ${
item.id === fontSize ? "active" : ""
}`}
>
<div style={{ fontSize: item.id + "px" }}>{item.label}</div>
</div>
))}
</div>
</div>
)}
</div>
);
}

export default FontSizeMenu;

七、highlightMenu.tsx


import { Editor } from "@tiptap/react";

type HighlightMenuProps = {
editor: Editor;
};

function HighlightMenu(props: HighlightMenuProps) {
const { editor } = props;
const textStyle = editor.getAttributes("textStyle") || {};
const highlightStyle = editor.getAttributes("highlight") || {};
const color = highlightStyle.color || textStyle.backgroundColor || "#FFFFFF";

const onInputChange = (event: any) => {
const value = event.target.value;
editor.chain().focus().setHighlight({ color: value }).run();
editor.commands.focus();
};

return (
<div className="menu-item-container menu-item__highlight-menu">
<label
className="menu-item highlight-menu__label"
htmlFor="highlight-menu__select-panel__uniqueId"
>
背景颜色
<input
hidden
type="color"
value={color}
onChange={onInputChange}
className="highlight-menu__select-panel"
id="highlight-menu__select-panel__uniqueId"
/>
</label>
</div>
);
}

export default HighlightMenu;

八、hMenu.tsx


import { Editor } from "@tiptap/core";

export type HProps = {
editor: Editor;
};

function HMenu(props: HProps) {
const { editor } = props;

const onClick = (level: any) => {
editor.chain().focus().toggleHeading({ level }).run();
};

return (
<div className="menu-item-container menu-item__h-menu">
<div className="h-menu__list">
<div
onClick={() => onClick(1)}
className={`menu-item h-menu__item ${
editor.isActive("heading", { level: 1 }) ? "menu-item-active" : ""
}`}
>
h1
</div>
<div
className={`menu-item h-menu__item ${
editor.isActive("heading", { level: 2 }) ? "menu-item-active" : ""
}`}
onClick={() => onClick(2)}
>
h2
</div>
</div>
</div>
);
}

export default HMenu;

九、iframeMenu.tsx


import { Editor } from "@tiptap/core";

type IframeMenuProps = {
editor: Editor;
};

function IframeMenu(props: IframeMenuProps) {
const { editor } = props;

const onClick = () => {
const url = window.prompt("Enter Iframe URL");

if (url) {
editor.commands.setIframe(url, { width: "100%" });
}
};

return (
<div className="menu-item-container menu-item__iframe-menu">
<div className={`menu-item iframe-menu_item`} onClick={onClick}>
Iframe
</div>
</div>
);
}

export default IframeMenu;

十、imgMenu.tsx


import { Editor } from "@tiptap/react";

type ImgMenuProps = {
editor: Editor;
};

function ImgMenu(props: ImgMenuProps) {
const { editor } = props;

const onInputChange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
const url = reader.result;
editor.commands.setImage({ src: url as string });
};
};

return (
<div className="menu-item-container menu-item__img-menu">
<label
className="menu-item img-menu__label"
htmlFor="img-menu__upload__input__uniqueId"
>
图片
<input
hidden
type="file"
accept="image/*"
onChange={onInputChange}
className="img-menu__select-panel"
id="img-menu__upload__input__uniqueId"
/>
</label>
</div>
);
}

export default ImgMenu;

十一、italicMenu.tsx


import { Editor } from "@tiptap/core";

export type ItalicMenuProps = {
editor: Editor;
};

function ItalicMenu(props: ItalicMenuProps) {
const { editor } = props;

const onItalic = () => {
editor.chain().focus().toggleItalic().run();
};

return (
<div className="menu-item-container menu-item__italic-menu">
<div
onClick={onItalic}
className={`menu-item italic-menu_item ${
editor.isActive("italic") ? "menu-item-active" : ""
}`}
>
斜体
</div>
</div>
);
}

export default ItalicMenu;

十二、linkMenu.tsx


import { Editor } from "@tiptap/react";

type LinkMenuProps = {
editor: Editor;
};

function LinkMenu(props: LinkMenuProps) {
const { editor } = props;
const onClick = () => {
const selection = editor.state.selection;
const text = editor.state.doc.textBetween(selection.from, selection.to);

if (text) {
const url = window.prompt("URL", "");
if (url === null) {
return;
}

if (url === "") {
editor.chain().focus().extendMarkRange("link").unsetLink().run();

return;
}

editor
.chain()
.focus()
.extendMarkRange("link")
.setLink({ href: url })
.run();
} else {
const text = window.prompt("文本", "");
const url = window.prompt("URL", "");
if (url === null || text === null) {
return;
}
editor
.chain()
.focus()
.addLink({
text,
href: url,
title: text,
})
.run();
}
};

return (
<div className="menu-item-container menu-item__link-menu">
<div className={`menu-item link-menu_item`} onClick={onClick}>
插入链接
</div>
</div>
);
}

export default LinkMenu;

十三、orderedListMenu.tsx


import { Editor } from "@tiptap/core";

export type OrderedListMenuProps = {
editor: Editor;
};

function OrderedListMenu(props: OrderedListMenuProps) {
const { editor } = props;

const onOrderedList = () => {
editor.chain().focus().toggleOrderedList().run();
};

return (
<div className="menu-item-container menu-item__ordered-list-menu">
<div
onClick={onOrderedList}
className={`menu-item ordered-list-menu_item ${
editor.isActive("orderedList") ? "menu-item-active" : ""
}`}
>
序号列表
</div>
</div>
);
}

export default OrderedListMenu;

十四、strikeMenu.tsx


import { Editor } from "@tiptap/core";

export type StrikeMenuProps = {
editor: Editor;
};

function StrikeMenu(props: StrikeMenuProps) {
const { editor } = props;

const onStrike = () => {
editor.chain().focus().toggleStrike().run();
};

return (
<div className="menu-item-container menu-item__strike-menu">
<div
onClick={onStrike}
className={`menu-item strike-menu_item ${
editor.isActive("strike") ? "menu-item-active" : ""
}`}
>
删除线
</div>
</div>
);
}

export default StrikeMenu;

十五、tableMenu.tsx


import { Editor } from "@tiptap/core";

type TableMenuProps = {
editor: Editor;
};

function TableMenu(props: TableMenuProps) {
const { editor } = props;

const onInsertTable = () => {
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run();
};

const onAddColumnBefore = () => {
editor.chain().focus().addColumnBefore().run();
};

const onAddColumnAfter = () => {
editor.chain().focus().addColumnAfter().run();
};

const onRemoveColumn = () => {
editor.chain().focus().deleteColumn().run();
};

const onAddRowBefore = () => {
editor.chain().focus().addRowBefore().run();
};

const onAddRowAfter = () => {
editor.chain().focus().addRowAfter().run();
};

const onRemoveRow = () => {
editor.chain().focus().deleteRow().run();
};

const onRemoveTable = () => {
editor.chain().focus().deleteTable().run();
};

const onMergeCells = () => {
editor.chain().focus().mergeCells().run();
};

const onSplitCell = () => {
editor.chain().focus().splitCell().run();
};

return (
<div className="menu-item-container menu-item__table-menu">
<div className="table-menu__list">
<div className="menu-item table-menu__item" onClick={onInsertTable}>
插入表格
</div>
<div className="menu-item table-menu__item" onClick={onAddColumnBefore}>
之前插入列
</div>
<div className="menu-item table-menu__item" onClick={onAddColumnAfter}>
之后插入列
</div>
<div className="menu-item table-menu__item" onClick={onRemoveColumn}>
删除列
</div>
<div className="menu-item table-menu__item" onClick={onAddRowBefore}>
之前插入行
</div>
<div className="menu-item table-menu__item" onClick={onAddRowAfter}>
之后插入行
</div>
<div className="menu-item table-menu__item" onClick={onRemoveRow}>
删除行
</div>
<div className="menu-item table-menu__item" onClick={onRemoveTable}>
删除表格
</div>
<div className="menu-item table-menu__item" onClick={onMergeCells}>
合并单元格
</div>
<div className="menu-item table-menu__item" onClick={onSplitCell}>
分割单元格
</div>
</div>
</div>
);
}

export default TableMenu;

十六、textAlignMenu.tsx


import { Editor } from "@tiptap/react";

type TextAlignMenuProps = {
editor: Editor;
};

function TextAlignMenu(props: TextAlignMenuProps) {
const { editor } = props;

const onClick = (type: string) => {
editor.chain().focus().setTextAlign(type).run();
};

return (
<div className="menu-item-container menu-item__text-align-menu">
<div className="text-align-menu__list">
<div
onClick={() => onClick("left")}
className={`menu-item text-align-menu__item ${
editor.isActive({ textAlign: "left" }) ? "menu-item-active" : ""
}`}
>
左对齐
</div>
<div
onClick={() => onClick("center")}
className={`menu-item text-align-menu__item ${
editor.isActive({ textAlign: "center" }) ? "menu-item-active" : ""
}`}
>
居中
</div>
<div
onClick={() => onClick("right")}
className={`menu-item text-align-menu__item ${
editor.isActive({ textAlign: "right" }) ? "menu-item-active" : ""
}`}
>
右对齐
</div>
</div>
</div>
);
}

export default TextAlignMenu;

十七、underlineMenu.tsx


import { Editor } from "@tiptap/core";

export type UnderlineMenuProps = {
editor: Editor;
};

function UnderlineMenu(props: UnderlineMenuProps) {
const { editor } = props;

const onUnderline = () => {
editor.chain().focus().toggleUnderline().run();
};

return (
<div className="menu-item-container menu-item__underline-menu">
<div
onClick={onUnderline}
className={`menu-item underline-menu_item ${
editor.isActive("underline") ? "menu-item-active" : ""
}`}
>
下划线
</div>
</div>
);
}

export default UnderlineMenu;

十八、unsetAllMarksMenu.tsx


import { Editor } from "@tiptap/core";

export type UnsetAllMarksMenuProps = {
editor: Editor;
};

function UnsetAllMarksMenu(props: UnsetAllMarksMenuProps) {
const { editor } = props;

const onUnsetAllMarks = () => {
editor.chain().focus().unsetAllMarks().run();
};

return (
<div className="menu-item-container menu-item__unset-all-marks-menu">
<div
onClick={onUnsetAllMarks}
className={`menu-item unset-all-marks-menu_item`}
>
清除全部样式
</div>
</div>
);
}

export default UnsetAllMarksMenu;

十九、videoMenu.tsx


import { Editor } from "@tiptap/react";

type VideoMenuProps = {
editor: Editor;
};

function VideoMenu(props: VideoMenuProps) {
const { editor } = props;

const onInputChange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (event: any) {
const videoBlob = new Blob([event.target.result], { type: file.type });
const blobUrl = URL.createObjectURL(videoBlob);
editor.commands.setVideo(blobUrl, { height: "auto", width: "100%" });
};
};

return (
<div className="menu-item-container menu-item__video-menu">
<label
className="menu-item img-menu__label"
htmlFor="video-menu__upload__input__uniqueId"
>
视频
<input
hidden
type="file"
accept="video/*"
onChange={onInputChange}
className="video-menu__select-panel"
id="video-menu__upload__input__uniqueId"
/>
</label>
</div>
);
}

export default VideoMenu;

二十、youtubeVideoMenu.tsx


import { Editor } from "@tiptap/core";

export type YoutubeVideoMenuProps = {
editor: Editor;
};

function YoutubeVideoMenu(props: YoutubeVideoMenuProps) {
const { editor } = props;

const onClick = () => {
const url = window.prompt("Enter YouTube URL");

if (url) {
editor.commands.setYoutubeVideo({
src: url,
width: 640,
height: 480,
});
}
};

return (
<div className="menu-item-container menu-item__youtube-video-menu">
<div className={`menu-item youtube-video-menu_item`} onClick={onClick}>
Youtube 视频
</div>
</div>
);
}

export default YoutubeVideoMenu;