tetrons 0.1.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17177 -0
- package/dist/index.mjs +17190 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +77 -61
- package/.hintrc +0 -13
- package/dist/.next/types/app/api/export/route.d.ts +0 -12
- package/dist/.next/types/app/api/export/route.js +0 -52
- package/dist/.next/types/app/api/save/route.d.ts +0 -12
- package/dist/.next/types/app/api/save/route.js +0 -52
- package/dist/.next/types/app/layout.d.ts +0 -12
- package/dist/.next/types/app/layout.js +0 -22
- package/dist/.next/types/app/page.d.ts +0 -12
- package/dist/.next/types/app/page.js +0 -22
- package/dist/next.config.d.ts +0 -3
- package/dist/next.config.js +0 -4
- package/dist/src/app/api/export/route.d.ts +0 -1
- package/dist/src/app/api/export/route.js +0 -4
- package/dist/src/app/api/save/route.d.ts +0 -6
- package/dist/src/app/api/save/route.js +0 -15
- package/dist/src/app/layout.d.ts +0 -6
- package/dist/src/app/layout.jsx +0 -34
- package/dist/src/app/page.d.ts +0 -1
- package/dist/src/app/page.jsx +0 -10
- package/dist/src/components/UI/Button.d.ts +0 -0
- package/dist/src/components/UI/Button.jsx +0 -1
- package/dist/src/components/UI/Dropdown.d.ts +0 -0
- package/dist/src/components/UI/Dropdown.jsx +0 -1
- package/dist/src/components/tetrons/EditorContent.d.ts +0 -1
- package/dist/src/components/tetrons/EditorContent.jsx +0 -158
- package/dist/src/components/tetrons/ResizableImage.d.ts +0 -1
- package/dist/src/components/tetrons/ResizableImage.js +0 -40
- package/dist/src/components/tetrons/ResizableImageComponent.d.ts +0 -11
- package/dist/src/components/tetrons/ResizableImageComponent.jsx +0 -37
- package/dist/src/components/tetrons/ResizableVideo.d.ts +0 -12
- package/dist/src/components/tetrons/ResizableVideo.js +0 -61
- package/dist/src/components/tetrons/ResizableVideoComponent.d.ts +0 -4
- package/dist/src/components/tetrons/ResizableVideoComponent.jsx +0 -32
- package/dist/src/components/tetrons/helpers.d.ts +0 -0
- package/dist/src/components/tetrons/helpers.js +0 -1
- package/dist/src/components/tetrons/toolbar/ActionGroup.d.ts +0 -6
- package/dist/src/components/tetrons/toolbar/ActionGroup.jsx +0 -165
- package/dist/src/components/tetrons/toolbar/ClipboardGroup.d.ts +0 -4
- package/dist/src/components/tetrons/toolbar/ClipboardGroup.jsx +0 -35
- package/dist/src/components/tetrons/toolbar/FileGroup.d.ts +0 -6
- package/dist/src/components/tetrons/toolbar/FileGroup.jsx +0 -40
- package/dist/src/components/tetrons/toolbar/FontStyleGroup.d.ts +0 -6
- package/dist/src/components/tetrons/toolbar/FontStyleGroup.jsx +0 -104
- package/dist/src/components/tetrons/toolbar/InsertGroup.d.ts +0 -5
- package/dist/src/components/tetrons/toolbar/InsertGroup.jsx +0 -162
- package/dist/src/components/tetrons/toolbar/ListAlignGroup.d.ts +0 -4
- package/dist/src/components/tetrons/toolbar/ListAlignGroup.jsx +0 -15
- package/dist/src/components/tetrons/toolbar/MiscGroup.d.ts +0 -6
- package/dist/src/components/tetrons/toolbar/MiscGroup.jsx +0 -30
- package/dist/src/components/tetrons/toolbar/TableContextMenu.d.ts +0 -6
- package/dist/src/components/tetrons/toolbar/TableContextMenu.jsx +0 -52
- package/dist/src/components/tetrons/toolbar/TetronsToolbar.d.ts +0 -4
- package/dist/src/components/tetrons/toolbar/TetronsToolbar.jsx +0 -46
- package/dist/src/components/tetrons/toolbar/ToolbarButton.d.ts +0 -12
- package/dist/src/components/tetrons/toolbar/ToolbarButton.jsx +0 -8
- package/dist/src/components/tetrons/toolbar/extensions/Comment.d.ts +0 -17
- package/dist/src/components/tetrons/toolbar/extensions/Comment.js +0 -45
- package/dist/src/components/tetrons/toolbar/extensions/Embed.d.ts +0 -2
- package/dist/src/components/tetrons/toolbar/extensions/Embed.js +0 -90
- package/dist/src/components/tetrons/toolbar/extensions/FontFamily.d.ts +0 -9
- package/dist/src/components/tetrons/toolbar/extensions/FontFamily.js +0 -28
- package/dist/src/components/tetrons/toolbar/extensions/FontSize.d.ts +0 -9
- package/dist/src/components/tetrons/toolbar/extensions/FontSize.js +0 -28
- package/dist/src/components/tetrons/toolbar/extensions/ResizableTable.d.ts +0 -1
- package/dist/src/components/tetrons/toolbar/extensions/ResizableTable.js +0 -11
- package/dist/src/components/tetrons/toolbar/marks/Subscript.d.ts +0 -2
- package/dist/src/components/tetrons/toolbar/marks/Subscript.js +0 -35
- package/dist/src/components/tetrons/toolbar/marks/Superscript.d.ts +0 -2
- package/dist/src/components/tetrons/toolbar/marks/Superscript.js +0 -35
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +0 -2
- package/dist/src/lib/export.d.ts +0 -0
- package/dist/src/lib/export.js +0 -1
- package/dist/src/lib/tiptap-extensions.d.ts +0 -0
- package/dist/src/lib/tiptap-extensions.js +0 -1
- package/dist/src/utils/loadEmojiPicker.d.ts +0 -1
- package/dist/src/utils/loadEmojiPicker.js +0 -12
- package/eslint.config.mjs +0 -16
- package/next.config.ts +0 -7
- package/postcss.config.mjs +0 -5
- package/public/editor-content.json +0 -27
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-64x64.png +0 -0
- package/public/favicon-768x768.png +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/next.svg +0 -1
- package/public/site.webmanifest +0 -20
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- package/src/app/api/export/route.ts +0 -4
- package/src/app/api/save/route.ts +0 -18
- package/src/app/favicon-16x16.png +0 -0
- package/src/app/favicon-32x32.png +0 -0
- package/src/app/favicon-64x64.png +0 -0
- package/src/app/favicon-768x768.png +0 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +0 -207
- package/src/app/layout.tsx +0 -47
- package/src/app/page.tsx +0 -11
- package/src/components/UI/Button.tsx +0 -0
- package/src/components/UI/Dropdown.tsx +0 -0
- package/src/components/tetrons/EditorContent.tsx +0 -210
- package/src/components/tetrons/ResizableImage.ts +0 -39
- package/src/components/tetrons/ResizableImageComponent.tsx +0 -77
- package/src/components/tetrons/ResizableVideo.ts +0 -66
- package/src/components/tetrons/ResizableVideoComponent.tsx +0 -56
- package/src/components/tetrons/helpers.ts +0 -0
- package/src/components/tetrons/toolbar/ActionGroup.tsx +0 -222
- package/src/components/tetrons/toolbar/ClipboardGroup.tsx +0 -57
- package/src/components/tetrons/toolbar/FileGroup.tsx +0 -70
- package/src/components/tetrons/toolbar/FontStyleGroup.tsx +0 -198
- package/src/components/tetrons/toolbar/InsertGroup.tsx +0 -268
- package/src/components/tetrons/toolbar/ListAlignGroup.tsx +0 -69
- package/src/components/tetrons/toolbar/MiscGroup.tsx +0 -70
- package/src/components/tetrons/toolbar/TableContextMenu.tsx +0 -91
- package/src/components/tetrons/toolbar/TetronsToolbar.tsx +0 -60
- package/src/components/tetrons/toolbar/ToolbarButton.tsx +0 -38
- package/src/components/tetrons/toolbar/extensions/Comment.ts +0 -72
- package/src/components/tetrons/toolbar/extensions/Embed.ts +0 -113
- package/src/components/tetrons/toolbar/extensions/FontFamily.ts +0 -43
- package/src/components/tetrons/toolbar/extensions/FontSize.ts +0 -43
- package/src/components/tetrons/toolbar/extensions/ResizableTable.ts +0 -16
- package/src/components/tetrons/toolbar/marks/Subscript.ts +0 -45
- package/src/components/tetrons/toolbar/marks/Superscript.ts +0 -45
- package/src/index.ts +0 -3
- package/src/lib/export.ts +0 -0
- package/src/lib/tiptap-extensions.ts +0 -0
- package/src/types/dom-to-pdf.d.ts +0 -13
- package/src/types/editor.d.ts +0 -0
- package/src/types/emoji-picker.d.ts +0 -18
- package/src/types/global.d.ts +0 -13
- package/src/types/html2pdf.d.ts +0 -1
- package/src/types/tiptap-extensions.d.ts +0 -23
- package/src/utils/loadEmojiPicker.ts +0 -12
- package/tsconfig.json +0 -57
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React, { useRef, useState } from "react";
|
|
3
|
-
import { MdTableChart, MdInsertPhoto, MdInsertLink, MdInsertComment, MdInsertEmoticon, MdHorizontalRule, MdVideoLibrary, MdOutlineOndemandVideo, } from "react-icons/md";
|
|
4
|
-
import ToolbarButton from "./ToolbarButton";
|
|
5
|
-
import Picker from "@emoji-mart/react";
|
|
6
|
-
export default function InsertGroup({ editor }) {
|
|
7
|
-
const [showTableGrid, setShowTableGrid] = useState(false);
|
|
8
|
-
const [selectedRows, setSelectedRows] = useState(1);
|
|
9
|
-
const [selectedCols, setSelectedCols] = useState(1);
|
|
10
|
-
const imageInputRef = useRef(null);
|
|
11
|
-
const videoInputRef = useRef(null);
|
|
12
|
-
const [showPicker, setShowPicker] = useState(false);
|
|
13
|
-
const addEmoji = (emoji) => {
|
|
14
|
-
editor.chain().focus().insertContent(emoji.native).run();
|
|
15
|
-
setShowPicker(false);
|
|
16
|
-
};
|
|
17
|
-
const handleTableCellHover = (row, col) => {
|
|
18
|
-
setSelectedRows(row);
|
|
19
|
-
setSelectedCols(col);
|
|
20
|
-
};
|
|
21
|
-
const handleTableInsert = (rows, cols) => {
|
|
22
|
-
editor
|
|
23
|
-
.chain()
|
|
24
|
-
.focus()
|
|
25
|
-
.insertTable({
|
|
26
|
-
rows: rows || 1,
|
|
27
|
-
cols: cols || 1,
|
|
28
|
-
withHeaderRow: true,
|
|
29
|
-
})
|
|
30
|
-
.run();
|
|
31
|
-
setShowTableGrid(false);
|
|
32
|
-
setSelectedRows(1);
|
|
33
|
-
setSelectedCols(1);
|
|
34
|
-
};
|
|
35
|
-
const handleImageUpload = (e) => {
|
|
36
|
-
var _a;
|
|
37
|
-
const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
38
|
-
if (file) {
|
|
39
|
-
const reader = new FileReader();
|
|
40
|
-
reader.onload = () => {
|
|
41
|
-
editor
|
|
42
|
-
.chain()
|
|
43
|
-
.focus()
|
|
44
|
-
.setImage({ src: reader.result })
|
|
45
|
-
.run();
|
|
46
|
-
};
|
|
47
|
-
reader.readAsDataURL(file);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const handleVideoUpload = (e) => {
|
|
51
|
-
var _a;
|
|
52
|
-
const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
53
|
-
if (file) {
|
|
54
|
-
const reader = new FileReader();
|
|
55
|
-
reader.onload = () => {
|
|
56
|
-
editor
|
|
57
|
-
.chain()
|
|
58
|
-
.focus()
|
|
59
|
-
.setVideo({ src: reader.result, controls: true })
|
|
60
|
-
.run();
|
|
61
|
-
};
|
|
62
|
-
reader.readAsDataURL(file);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
function normalizeEmbedUrl(url) {
|
|
66
|
-
try {
|
|
67
|
-
const u = new URL(url);
|
|
68
|
-
const hostname = u.hostname.replace("www.", "").toLowerCase();
|
|
69
|
-
if (hostname === "youtube.com" || hostname === "youtu.be") {
|
|
70
|
-
let videoId = u.searchParams.get("v");
|
|
71
|
-
if (!videoId && hostname === "youtu.be") {
|
|
72
|
-
videoId = u.pathname.slice(1);
|
|
73
|
-
}
|
|
74
|
-
if (videoId) {
|
|
75
|
-
return `https://www.youtube.com/embed/${videoId}`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (hostname === "vimeo.com") {
|
|
79
|
-
const videoId = u.pathname.split("/")[1];
|
|
80
|
-
if (videoId) {
|
|
81
|
-
return `https://player.vimeo.com/video/${videoId}`;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (hostname === "google.com" ||
|
|
85
|
-
hostname === "maps.google.com" ||
|
|
86
|
-
hostname === "goo.gl") {
|
|
87
|
-
if (url.includes("/maps/embed")) {
|
|
88
|
-
return url;
|
|
89
|
-
}
|
|
90
|
-
return url.replace("/maps/", "/maps/embed/");
|
|
91
|
-
}
|
|
92
|
-
return url;
|
|
93
|
-
}
|
|
94
|
-
catch (_a) {
|
|
95
|
-
return url;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return (<div className="flex gap-1 border-r pr-3 relative">
|
|
99
|
-
<input type="file" accept="image/*" ref={imageInputRef} className="hidden" onChange={handleImageUpload} aria-label="Upload Image" title="Upload Image"/>
|
|
100
|
-
<input type="file" accept="video/*" ref={videoInputRef} className="hidden" onChange={handleVideoUpload} aria-label="Upload Video" title="Upload Video"/>
|
|
101
|
-
|
|
102
|
-
<ToolbarButton icon={MdTableChart} label="Insert Table" onClick={() => setShowTableGrid(!showTableGrid)}/>
|
|
103
|
-
{showTableGrid && (<div className="absolute top-10 left-0 bg-white border rounded shadow p-2 z-20" onMouseLeave={() => setShowTableGrid(false)}>
|
|
104
|
-
<div className="grid grid-cols-10 gap-[1px]">
|
|
105
|
-
{[...Array(10)].map((_, row) => [...Array(10)].map((_, col) => {
|
|
106
|
-
const isSelected = row < selectedRows && col < selectedCols;
|
|
107
|
-
return (<div key={`${row}-${col}`} className={`w-5 h-5 border cursor-pointer ${isSelected ? "bg-blue-500" : "bg-gray-100"}`} onMouseEnter={() => handleTableCellHover(row + 1, col + 1)} onClick={() => handleTableInsert(row + 1, col + 1)}/>);
|
|
108
|
-
}))}
|
|
109
|
-
</div>
|
|
110
|
-
<div className="text-sm mt-2 text-center text-gray-600">
|
|
111
|
-
{selectedRows} x {selectedCols}
|
|
112
|
-
</div>
|
|
113
|
-
</div>)}
|
|
114
|
-
|
|
115
|
-
<ToolbarButton icon={MdInsertPhoto} label="Insert Image" onClick={() => { var _a; return (_a = imageInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }}/>
|
|
116
|
-
<ToolbarButton icon={MdVideoLibrary} label="Insert Video" onClick={() => { var _a; return (_a = videoInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }}/>
|
|
117
|
-
<ToolbarButton icon={MdInsertLink} label="Insert Link" onClick={() => {
|
|
118
|
-
const url = prompt("Enter URL");
|
|
119
|
-
if (url)
|
|
120
|
-
editor
|
|
121
|
-
.chain()
|
|
122
|
-
.focus()
|
|
123
|
-
.extendMarkRange("link")
|
|
124
|
-
.setLink({ href: url })
|
|
125
|
-
.run();
|
|
126
|
-
}}/>
|
|
127
|
-
<ToolbarButton icon={MdInsertComment} label="Insert Comment" onClick={() => {
|
|
128
|
-
const comment = prompt("Enter your comment");
|
|
129
|
-
if (comment &&
|
|
130
|
-
editor.can().chain().focus().setComment(comment).run()) {
|
|
131
|
-
editor.chain().focus().setComment(comment).run();
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
console.warn("Cannot apply comment — maybe no selection?");
|
|
135
|
-
}
|
|
136
|
-
}}/>
|
|
137
|
-
<div className="relative">
|
|
138
|
-
<ToolbarButton icon={MdInsertEmoticon} label="Emoji" onClick={() => setShowPicker(!showPicker)}/>
|
|
139
|
-
{showPicker && (<div className="absolute z-50 top-10 left-0">
|
|
140
|
-
<Picker onEmojiSelect={addEmoji} theme="auto" emoji="point_up" showPreview={false} showSkinTones={true} emojiTooltip={true}/>
|
|
141
|
-
</div>)}
|
|
142
|
-
</div>
|
|
143
|
-
<ToolbarButton icon={MdHorizontalRule} label="Horizontal Line" onClick={() => editor.chain().focus().setHorizontalRule().run()}/>
|
|
144
|
-
<ToolbarButton icon={MdOutlineOndemandVideo} label="Embed" onClick={() => {
|
|
145
|
-
const url = prompt("Enter embed URL (YouTube, Vimeo, Google Maps, etc.)");
|
|
146
|
-
if (!url)
|
|
147
|
-
return;
|
|
148
|
-
const embedUrl = normalizeEmbedUrl(url);
|
|
149
|
-
const width = prompt("Enter width in pixels", "560");
|
|
150
|
-
const height = prompt("Enter height in pixels", "315");
|
|
151
|
-
editor
|
|
152
|
-
.chain()
|
|
153
|
-
.focus()
|
|
154
|
-
.setEmbed({
|
|
155
|
-
src: embedUrl,
|
|
156
|
-
width: width ? parseInt(width) : 560,
|
|
157
|
-
height: height ? parseInt(height) : 315,
|
|
158
|
-
})
|
|
159
|
-
.run();
|
|
160
|
-
}}/>
|
|
161
|
-
</div>);
|
|
162
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { MdFormatListBulleted, MdFormatListNumbered, MdFormatIndentDecrease, MdFormatIndentIncrease, MdFormatAlignLeft, MdFormatAlignCenter, MdFormatAlignRight, MdFormatAlignJustify, } from "react-icons/md";
|
|
3
|
-
import ToolbarButton from "./ToolbarButton";
|
|
4
|
-
export default function ListAlignGroup({ editor }) {
|
|
5
|
-
return (<div className="flex gap-1 border-r pr-3">
|
|
6
|
-
<ToolbarButton icon={MdFormatListBulleted} title="Bulleted List" onClick={() => editor.chain().focus().toggleBulletList().run()} disabled={!editor.can().toggleBulletList()}/>
|
|
7
|
-
<ToolbarButton icon={MdFormatListNumbered} title="Numbered List" onClick={() => editor.chain().focus().toggleOrderedList().run()} disabled={!editor.can().toggleOrderedList()}/>
|
|
8
|
-
<ToolbarButton icon={MdFormatIndentIncrease} title="Increase Indent" onClick={() => editor.chain().focus().sinkListItem("listItem").run()} disabled={!editor.can().sinkListItem("listItem")}/>
|
|
9
|
-
<ToolbarButton icon={MdFormatIndentDecrease} title="Decrease Indent" onClick={() => editor.chain().focus().liftListItem("listItem").run()} disabled={!editor.can().liftListItem("listItem")}/>
|
|
10
|
-
<ToolbarButton icon={MdFormatAlignLeft} title="Align Left" onClick={() => editor.chain().focus().setTextAlign("left").run()} disabled={!editor.can().setTextAlign("left")}/>
|
|
11
|
-
<ToolbarButton icon={MdFormatAlignCenter} title="Align Center" onClick={() => editor.chain().focus().setTextAlign("center").run()} disabled={!editor.can().setTextAlign("center")}/>
|
|
12
|
-
<ToolbarButton icon={MdFormatAlignRight} title="Align Right" onClick={() => editor.chain().focus().setTextAlign("right").run()} disabled={!editor.can().setTextAlign("right")}/>
|
|
13
|
-
<ToolbarButton icon={MdFormatAlignJustify} title="Justify" onClick={() => editor.chain().focus().setTextAlign("justify").run()} disabled={!editor.can().setTextAlign("justify")}/>
|
|
14
|
-
</div>);
|
|
15
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { MdUndo, MdRedo, MdRefresh, MdVisibility, MdCode, } from "react-icons/md";
|
|
2
|
-
import ToolbarButton from "./ToolbarButton";
|
|
3
|
-
export default function MiscGroup({ editor }) {
|
|
4
|
-
const handlePreview = () => {
|
|
5
|
-
const html = editor.getHTML();
|
|
6
|
-
const previewWindow = window.open("", "_blank");
|
|
7
|
-
if (previewWindow) {
|
|
8
|
-
previewWindow.document.open();
|
|
9
|
-
previewWindow.document.write(`
|
|
10
|
-
<html>
|
|
11
|
-
<head>
|
|
12
|
-
<title>Preview</title>
|
|
13
|
-
<style>
|
|
14
|
-
body { font-family: sans-serif; padding: 2rem; line-height: 1.6; }
|
|
15
|
-
</style>
|
|
16
|
-
</head>
|
|
17
|
-
<body>${html}</body>
|
|
18
|
-
</html>
|
|
19
|
-
`);
|
|
20
|
-
previewWindow.document.close();
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
return (<div className="flex gap-1 items-center border-r pr-3">
|
|
24
|
-
<ToolbarButton icon={MdUndo} label="Undo" onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().undo()}/>
|
|
25
|
-
<ToolbarButton icon={MdRedo} label="Redo" onClick={() => editor.chain().focus().redo().run()} disabled={!editor.can().redo()}/>
|
|
26
|
-
<ToolbarButton icon={MdRefresh} label="Reset Formatting" onClick={() => editor.chain().focus().unsetAllMarks().clearNodes().run()}/>
|
|
27
|
-
<ToolbarButton icon={MdCode} label="Toggle Code Block" onClick={() => editor.chain().focus().toggleCodeBlock().run()} isActive={editor.isActive("codeBlock")}/>
|
|
28
|
-
<ToolbarButton icon={MdVisibility} label="Preview" onClick={handlePreview}/>
|
|
29
|
-
</div>);
|
|
30
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
export default function TableContextMenu({ editor }) {
|
|
4
|
-
const [menuPosition, setMenuPosition] = useState(null);
|
|
5
|
-
useEffect(() => {
|
|
6
|
-
const handleContextMenu = (event) => {
|
|
7
|
-
const target = event.target;
|
|
8
|
-
if (target.closest("td") || target.closest("th")) {
|
|
9
|
-
event.preventDefault();
|
|
10
|
-
setMenuPosition({ x: event.pageX, y: event.pageY });
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
setMenuPosition(null);
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
const handleClick = () => setMenuPosition(null);
|
|
17
|
-
document.addEventListener("contextmenu", handleContextMenu);
|
|
18
|
-
document.addEventListener("click", handleClick);
|
|
19
|
-
return () => {
|
|
20
|
-
document.removeEventListener("contextmenu", handleContextMenu);
|
|
21
|
-
document.removeEventListener("click", handleClick);
|
|
22
|
-
};
|
|
23
|
-
}, []);
|
|
24
|
-
const insertRowAbove = () => editor.chain().focus().addRowBefore().run();
|
|
25
|
-
const insertRowBelow = () => editor.chain().focus().addRowAfter().run();
|
|
26
|
-
const insertColLeft = () => editor.chain().focus().addColumnBefore().run();
|
|
27
|
-
const insertColRight = () => editor.chain().focus().addColumnAfter().run();
|
|
28
|
-
const deleteRow = () => editor.chain().focus().deleteRow().run();
|
|
29
|
-
const deleteCol = () => editor.chain().focus().deleteColumn().run();
|
|
30
|
-
if (!menuPosition)
|
|
31
|
-
return null;
|
|
32
|
-
return (<ul className="absolute bg-white shadow border rounded text-sm z-50" style={{ top: menuPosition.y, left: menuPosition.x }}>
|
|
33
|
-
<li className="px-3 py-1 hover:bg-gray-100 cursor-pointer" onClick={insertRowAbove}>
|
|
34
|
-
Insert Row Above
|
|
35
|
-
</li>
|
|
36
|
-
<li className="px-3 py-1 hover:bg-gray-100 cursor-pointer" onClick={insertRowBelow}>
|
|
37
|
-
Insert Row Below
|
|
38
|
-
</li>
|
|
39
|
-
<li className="px-3 py-1 hover:bg-gray-100 cursor-pointer" onClick={insertColLeft}>
|
|
40
|
-
Insert Column Left
|
|
41
|
-
</li>
|
|
42
|
-
<li className="px-3 py-1 hover:bg-gray-100 cursor-pointer" onClick={insertColRight}>
|
|
43
|
-
Insert Column Right
|
|
44
|
-
</li>
|
|
45
|
-
<li className="px-3 py-1 hover:bg-red-100 cursor-pointer" onClick={deleteRow}>
|
|
46
|
-
Delete Row
|
|
47
|
-
</li>
|
|
48
|
-
<li className="px-3 py-1 hover:bg-red-100 cursor-pointer" onClick={deleteCol}>
|
|
49
|
-
Delete Column
|
|
50
|
-
</li>
|
|
51
|
-
</ul>);
|
|
52
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useState, useEffect } from "react";
|
|
3
|
-
import ActionGroup from "./ActionGroup";
|
|
4
|
-
import ClipboardGroup from "./ClipboardGroup";
|
|
5
|
-
import FontStyleGroup from "./FontStyleGroup";
|
|
6
|
-
import InsertGroup from "./InsertGroup";
|
|
7
|
-
import ListAlignGroup from "./ListAlignGroup";
|
|
8
|
-
import MiscGroup from "./MiscGroup";
|
|
9
|
-
import FileGroup from "./FileGroup";
|
|
10
|
-
export default function TetronsToolbar({ editor }) {
|
|
11
|
-
const [autoSave, setAutoSave] = useState(false);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (!editor)
|
|
14
|
-
return;
|
|
15
|
-
const handleUpdate = () => {
|
|
16
|
-
if (!autoSave)
|
|
17
|
-
return;
|
|
18
|
-
const content = editor.getJSON();
|
|
19
|
-
fetch("/api/save", {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: { "Content-Type": "application/json" },
|
|
22
|
-
body: JSON.stringify(content),
|
|
23
|
-
}).catch((err) => console.error("Auto-save failed:", err));
|
|
24
|
-
};
|
|
25
|
-
editor.on("update", handleUpdate);
|
|
26
|
-
return () => {
|
|
27
|
-
editor.off("update", handleUpdate);
|
|
28
|
-
};
|
|
29
|
-
}, [autoSave, editor]);
|
|
30
|
-
return (<div className="flex flex-wrap items-center gap-4 p-3 border-b bg-white shadow-sm relative z-10">
|
|
31
|
-
<div className="flex items-center gap-2 border-r pr-3">
|
|
32
|
-
<input type="checkbox" id="autoSave" checked={autoSave} onChange={(e) => setAutoSave(e.target.checked)} className="w-4 h-4"/>
|
|
33
|
-
<label htmlFor="autoSave" className="text-sm select-none">
|
|
34
|
-
Auto Save
|
|
35
|
-
</label>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<FileGroup editor={editor}/>
|
|
39
|
-
<ClipboardGroup editor={editor}/>
|
|
40
|
-
<FontStyleGroup editor={editor}/>
|
|
41
|
-
<ListAlignGroup editor={editor}/>
|
|
42
|
-
<InsertGroup editor={editor}/>
|
|
43
|
-
<MiscGroup editor={editor}/>
|
|
44
|
-
<ActionGroup editor={editor}/>
|
|
45
|
-
</div>);
|
|
46
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { IconType } from "react-icons";
|
|
3
|
-
export type ToolbarButtonProps = {
|
|
4
|
-
icon: IconType;
|
|
5
|
-
onClick: () => void;
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
title?: string;
|
|
8
|
-
label?: string;
|
|
9
|
-
isActive?: boolean;
|
|
10
|
-
};
|
|
11
|
-
declare const ToolbarButton: React.ForwardRefExoticComponent<ToolbarButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
12
|
-
export default ToolbarButton;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
const ToolbarButton = React.forwardRef(({ icon: Icon, onClick, disabled = false, title, label, isActive = false }, ref) => {
|
|
3
|
-
return (<button type="button" ref={ref} onClick={onClick} disabled={disabled} title={title !== null && title !== void 0 ? title : label} aria-label={title !== null && title !== void 0 ? title : label} className={`p-2 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${isActive ? "bg-gray-300" : ""}`}>
|
|
4
|
-
<Icon size={20}/>
|
|
5
|
-
</button>);
|
|
6
|
-
});
|
|
7
|
-
ToolbarButton.displayName = "ToolbarButton";
|
|
8
|
-
export default ToolbarButton;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Mark } from "@tiptap/core";
|
|
2
|
-
export interface CommentOptions {
|
|
3
|
-
HTMLAttributes: {
|
|
4
|
-
class?: string;
|
|
5
|
-
style?: string;
|
|
6
|
-
[key: string]: unknown;
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
declare module "@tiptap/core" {
|
|
10
|
-
interface Commands<ReturnType> {
|
|
11
|
-
comment: {
|
|
12
|
-
setComment: (comment: string) => ReturnType;
|
|
13
|
-
unsetComment: () => ReturnType;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export declare const Comment: Mark<CommentOptions, any>;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Mark, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
export const Comment = Mark.create({
|
|
3
|
-
name: "comment",
|
|
4
|
-
addOptions() {
|
|
5
|
-
return {
|
|
6
|
-
HTMLAttributes: {},
|
|
7
|
-
};
|
|
8
|
-
},
|
|
9
|
-
addAttributes() {
|
|
10
|
-
return {
|
|
11
|
-
comment: {
|
|
12
|
-
default: "",
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
},
|
|
16
|
-
parseHTML() {
|
|
17
|
-
return [
|
|
18
|
-
{
|
|
19
|
-
tag: "span[data-comment]",
|
|
20
|
-
},
|
|
21
|
-
];
|
|
22
|
-
},
|
|
23
|
-
renderHTML({ HTMLAttributes }) {
|
|
24
|
-
return [
|
|
25
|
-
"span",
|
|
26
|
-
mergeAttributes(HTMLAttributes, {
|
|
27
|
-
"data-comment": HTMLAttributes.comment,
|
|
28
|
-
class: "comment-highlight",
|
|
29
|
-
title: HTMLAttributes.comment,
|
|
30
|
-
style: "background-color: rgba(255, 230, 0, 0.3);",
|
|
31
|
-
}),
|
|
32
|
-
0,
|
|
33
|
-
];
|
|
34
|
-
},
|
|
35
|
-
addCommands() {
|
|
36
|
-
return {
|
|
37
|
-
setComment: (comment) => ({ commands }) => {
|
|
38
|
-
return commands.setMark(this.name, { comment });
|
|
39
|
-
},
|
|
40
|
-
unsetComment: () => ({ commands }) => {
|
|
41
|
-
return commands.unsetMark(this.name);
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
export const Embed = Node.create({
|
|
3
|
-
name: "embed",
|
|
4
|
-
group: "block",
|
|
5
|
-
atom: true,
|
|
6
|
-
addAttributes() {
|
|
7
|
-
return {
|
|
8
|
-
src: { default: null },
|
|
9
|
-
width: { default: 560 },
|
|
10
|
-
height: { default: 315 },
|
|
11
|
-
};
|
|
12
|
-
},
|
|
13
|
-
parseHTML() {
|
|
14
|
-
return [{ tag: "iframe[src]" }];
|
|
15
|
-
},
|
|
16
|
-
renderHTML({ HTMLAttributes }) {
|
|
17
|
-
return ["iframe", mergeAttributes(HTMLAttributes)];
|
|
18
|
-
},
|
|
19
|
-
addCommands() {
|
|
20
|
-
return {
|
|
21
|
-
setEmbed: ((attributes) => {
|
|
22
|
-
return ({ chain }) => {
|
|
23
|
-
return chain()
|
|
24
|
-
.insertContent({
|
|
25
|
-
type: this.name,
|
|
26
|
-
attrs: attributes,
|
|
27
|
-
})
|
|
28
|
-
.run();
|
|
29
|
-
};
|
|
30
|
-
}).bind(this),
|
|
31
|
-
};
|
|
32
|
-
},
|
|
33
|
-
addNodeView() {
|
|
34
|
-
return ({ node, getPos, editor }) => {
|
|
35
|
-
const container = document.createElement('div');
|
|
36
|
-
container.style.position = 'relative';
|
|
37
|
-
container.style.display = 'inline-block';
|
|
38
|
-
container.style.width = node.attrs.width + 'px';
|
|
39
|
-
container.style.height = node.attrs.height + 'px';
|
|
40
|
-
const iframe = document.createElement('iframe');
|
|
41
|
-
iframe.setAttribute('src', node.attrs.src);
|
|
42
|
-
iframe.setAttribute('frameborder', '0');
|
|
43
|
-
iframe.setAttribute('allowfullscreen', 'true');
|
|
44
|
-
iframe.style.width = '100%';
|
|
45
|
-
iframe.style.height = '100%';
|
|
46
|
-
container.appendChild(iframe);
|
|
47
|
-
const handle = document.createElement('div');
|
|
48
|
-
handle.style.position = 'absolute';
|
|
49
|
-
handle.style.width = '16px';
|
|
50
|
-
handle.style.height = '16px';
|
|
51
|
-
handle.style.right = '0';
|
|
52
|
-
handle.style.bottom = '0';
|
|
53
|
-
handle.style.cursor = 'se-resize';
|
|
54
|
-
handle.style.background = 'rgba(0,0,0,0.5)';
|
|
55
|
-
handle.style.borderRadius = '2px';
|
|
56
|
-
container.appendChild(handle);
|
|
57
|
-
let startX, startY, startWidth, startHeight;
|
|
58
|
-
const onMouseDown = (event) => {
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
startX = event.clientX;
|
|
61
|
-
startY = event.clientY;
|
|
62
|
-
startWidth = container.offsetWidth;
|
|
63
|
-
startHeight = container.offsetHeight;
|
|
64
|
-
window.addEventListener('mousemove', onMouseMove);
|
|
65
|
-
window.addEventListener('mouseup', onMouseUp);
|
|
66
|
-
};
|
|
67
|
-
const onMouseMove = (event) => {
|
|
68
|
-
const newWidth = Math.max(100, startWidth + (event.clientX - startX));
|
|
69
|
-
const newHeight = Math.max(100, startHeight + (event.clientY - startY));
|
|
70
|
-
container.style.width = newWidth + 'px';
|
|
71
|
-
container.style.height = newHeight + 'px';
|
|
72
|
-
};
|
|
73
|
-
const onMouseUp = () => {
|
|
74
|
-
window.removeEventListener("mousemove", onMouseMove);
|
|
75
|
-
window.removeEventListener("mouseup", onMouseUp);
|
|
76
|
-
editor.commands.command(({ tr }) => {
|
|
77
|
-
tr.setNodeMarkup(getPos(), undefined, Object.assign(Object.assign({}, node.attrs), { width: container.offsetWidth, height: container.offsetHeight }));
|
|
78
|
-
return true;
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
handle.addEventListener('mousedown', onMouseDown);
|
|
82
|
-
return {
|
|
83
|
-
dom: container,
|
|
84
|
-
destroy() {
|
|
85
|
-
handle.removeEventListener('mousedown', onMouseDown);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Mark, mergeAttributes } from '@tiptap/core';
|
|
2
|
-
export const FontFamily = Mark.create({
|
|
3
|
-
name: 'fontFamily',
|
|
4
|
-
addAttributes() {
|
|
5
|
-
return {
|
|
6
|
-
font: {
|
|
7
|
-
default: null,
|
|
8
|
-
parseHTML: element => element.style.fontFamily.replace(/['"]/g, ''),
|
|
9
|
-
renderHTML: attributes => {
|
|
10
|
-
if (!attributes.font)
|
|
11
|
-
return {};
|
|
12
|
-
return { style: `font-family: ${attributes.font}` };
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
},
|
|
17
|
-
parseHTML() {
|
|
18
|
-
return [{ style: 'font-family' }];
|
|
19
|
-
},
|
|
20
|
-
renderHTML({ HTMLAttributes }) {
|
|
21
|
-
return ['span', mergeAttributes(HTMLAttributes), 0];
|
|
22
|
-
},
|
|
23
|
-
addCommands() {
|
|
24
|
-
return {
|
|
25
|
-
setFontFamily: font => ({ commands }) => commands.setMark(this.name, { font }),
|
|
26
|
-
};
|
|
27
|
-
},
|
|
28
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Mark, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
export const FontSize = Mark.create({
|
|
3
|
-
name: "fontSize",
|
|
4
|
-
addAttributes() {
|
|
5
|
-
return {
|
|
6
|
-
size: {
|
|
7
|
-
default: null,
|
|
8
|
-
parseHTML: (element) => element.style.fontSize,
|
|
9
|
-
renderHTML: (attributes) => {
|
|
10
|
-
if (!attributes.size)
|
|
11
|
-
return {};
|
|
12
|
-
return { style: `font-size: ${attributes.size}` };
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
},
|
|
17
|
-
parseHTML() {
|
|
18
|
-
return [{ style: "font-size" }];
|
|
19
|
-
},
|
|
20
|
-
renderHTML({ HTMLAttributes }) {
|
|
21
|
-
return ["span", mergeAttributes(HTMLAttributes), 0];
|
|
22
|
-
},
|
|
23
|
-
addCommands() {
|
|
24
|
-
return {
|
|
25
|
-
setFontSize: (size) => ({ commands }) => commands.setMark(this.name, { size }),
|
|
26
|
-
};
|
|
27
|
-
},
|
|
28
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const ResizableTable: import("@tiptap/core").Node<import("@tiptap/extension-table").TableOptions, any>;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Table } from "@tiptap/extension-table";
|
|
2
|
-
import { columnResizing, tableEditing } from "prosemirror-tables";
|
|
3
|
-
export const ResizableTable = Table.extend({
|
|
4
|
-
addOptions() {
|
|
5
|
-
var _a;
|
|
6
|
-
return Object.assign(Object.assign({}, (_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)), { resizable: true });
|
|
7
|
-
},
|
|
8
|
-
addProseMirrorPlugins() {
|
|
9
|
-
return [columnResizing({ handleWidth: 5 }), tableEditing()];
|
|
10
|
-
},
|
|
11
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Mark, markInputRule, markPasteRule } from "@tiptap/core";
|
|
2
|
-
export const Subscript = Mark.create({
|
|
3
|
-
name: "subscript",
|
|
4
|
-
excludes: "superscript",
|
|
5
|
-
parseHTML() {
|
|
6
|
-
return [{ tag: "sub" }, { style: "vertical-align: sub" }];
|
|
7
|
-
},
|
|
8
|
-
renderHTML() {
|
|
9
|
-
return ["sub", 0];
|
|
10
|
-
},
|
|
11
|
-
addCommands() {
|
|
12
|
-
return {
|
|
13
|
-
toggleSubscript: () => ({ chain }) => chain()
|
|
14
|
-
.unsetMark("superscript")
|
|
15
|
-
.toggleMark(this.name)
|
|
16
|
-
.run(),
|
|
17
|
-
};
|
|
18
|
-
},
|
|
19
|
-
addInputRules() {
|
|
20
|
-
return [
|
|
21
|
-
markInputRule({
|
|
22
|
-
find: /~([^~]+)~/,
|
|
23
|
-
type: this.type,
|
|
24
|
-
}),
|
|
25
|
-
];
|
|
26
|
-
},
|
|
27
|
-
addPasteRules() {
|
|
28
|
-
return [
|
|
29
|
-
markPasteRule({
|
|
30
|
-
find: /~([^~]+)~/g,
|
|
31
|
-
type: this.type,
|
|
32
|
-
}),
|
|
33
|
-
];
|
|
34
|
-
},
|
|
35
|
-
});
|