tetrons 2.1.8 → 2.2.0
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/README.md +36 -36
- package/dist/app/layout.d.ts +1 -0
- package/dist/app/layout.jsx +4 -4
- package/dist/app/page.d.ts +1 -0
- package/dist/app/page.jsx +4 -4
- package/dist/components/tetrons/EditorContent.jsx +21 -21
- package/dist/components/tetrons/ResizableImageComponent.jsx +3 -3
- package/dist/components/tetrons/ResizableVideoComponent.jsx +2 -2
- package/dist/components/tetrons/toolbar/ActionGroup.jsx +31 -31
- package/dist/components/tetrons/toolbar/ClipboardGroup.jsx +5 -5
- package/dist/components/tetrons/toolbar/FileGroup.jsx +4 -4
- package/dist/components/tetrons/toolbar/FontStyleGroup.jsx +42 -42
- package/dist/components/tetrons/toolbar/InsertGroup.jsx +26 -26
- package/dist/components/tetrons/toolbar/ListAlignGroup.jsx +9 -9
- package/dist/components/tetrons/toolbar/MiscGroup.jsx +16 -16
- package/dist/components/tetrons/toolbar/TableContextMenu.jsx +19 -19
- package/dist/components/tetrons/toolbar/TetronsToolbar.jsx +17 -17
- package/dist/components/tetrons/toolbar/ToolbarButton.jsx +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +17217 -17214
- package/package.json +77 -77
- package/dist/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
-
|
|
3
|
-
## Getting Started
|
|
4
|
-
|
|
5
|
-
First, run the development server:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm run dev
|
|
9
|
-
# or
|
|
10
|
-
yarn dev
|
|
11
|
-
# or
|
|
12
|
-
pnpm dev
|
|
13
|
-
# or
|
|
14
|
-
bun dev
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
-
|
|
19
|
-
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
-
|
|
21
|
-
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
-
|
|
23
|
-
## Learn More
|
|
24
|
-
|
|
25
|
-
To learn more about Next.js, take a look at the following resources:
|
|
26
|
-
|
|
27
|
-
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
-
|
|
30
|
-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
-
|
|
32
|
-
## Deploy on Vercel
|
|
33
|
-
|
|
34
|
-
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
-
|
|
36
|
-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
package/dist/app/layout.d.ts
CHANGED
package/dist/app/layout.jsx
CHANGED
|
@@ -22,9 +22,9 @@ export const metadata = {
|
|
|
22
22
|
manifest: "/site.webmanifest",
|
|
23
23
|
};
|
|
24
24
|
export default function RootLayout({ children, }) {
|
|
25
|
-
return (<html lang="en">
|
|
26
|
-
<body suppressHydrationWarning className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased bg-gray-50 text-gray-900`}>
|
|
27
|
-
<main className="max-w-screen-xl mx-auto p-4">{children}</main>
|
|
28
|
-
</body>
|
|
25
|
+
return (<html lang="en">
|
|
26
|
+
<body suppressHydrationWarning className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased bg-gray-50 text-gray-900`}>
|
|
27
|
+
<main className="max-w-screen-xl mx-auto p-4">{children}</main>
|
|
28
|
+
</body>
|
|
29
29
|
</html>);
|
|
30
30
|
}
|
package/dist/app/page.d.ts
CHANGED
package/dist/app/page.jsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import EditorContent from "../components/tetrons/EditorContent";
|
|
2
2
|
export default function Home() {
|
|
3
|
-
return (<main className="flex flex-col h-screen overflow-hidden">
|
|
4
|
-
<div className="flex-1 overflow-auto flex flex-col">
|
|
5
|
-
<EditorContent />
|
|
6
|
-
</div>
|
|
3
|
+
return (<main className="flex flex-col h-screen overflow-hidden">
|
|
4
|
+
<div className="flex-1 overflow-auto flex flex-col">
|
|
5
|
+
<EditorContent />
|
|
6
|
+
</div>
|
|
7
7
|
</main>);
|
|
8
8
|
}
|
|
@@ -133,28 +133,28 @@ export default function EditorContent() {
|
|
|
133
133
|
setCurrentVersionIndex(index);
|
|
134
134
|
}
|
|
135
135
|
};
|
|
136
|
-
return (<div className="flex flex-col h-full">
|
|
137
|
-
<div className="p-2 border-b border-gray-300 bg-gray-50 flex flex-wrap items-center gap-3">
|
|
138
|
-
<button onClick={saveVersion} disabled={!editor} className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50">
|
|
139
|
-
Save Version
|
|
140
|
-
</button>
|
|
141
|
-
|
|
142
|
-
<div className="flex items-center gap-2 overflow-x-auto max-w-full">
|
|
143
|
-
{versions.length === 0 && (<span className="text-gray-500 text-sm">No saved versions</span>)}
|
|
136
|
+
return (<div className="flex flex-col h-full">
|
|
137
|
+
<div className="p-2 border-b border-gray-300 bg-gray-50 flex flex-wrap items-center gap-3">
|
|
138
|
+
<button onClick={saveVersion} disabled={!editor} className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50">
|
|
139
|
+
Save Version
|
|
140
|
+
</button>
|
|
141
|
+
|
|
142
|
+
<div className="flex items-center gap-2 overflow-x-auto max-w-full">
|
|
143
|
+
{versions.length === 0 && (<span className="text-gray-500 text-sm">No saved versions</span>)}
|
|
144
144
|
{versions.map((_, idx) => (<button key={idx} onClick={() => restoreVersion(idx)} className={`px-2 py-1 rounded border ${idx === currentVersionIndex
|
|
145
145
|
? "border-blue-600 font-semibold text-blue-600"
|
|
146
|
-
: "border-gray-300 text-gray-700 hover:border-gray-600"}`} title={`Restore Version ${idx + 1}`}>
|
|
147
|
-
{`V${idx + 1}`}
|
|
148
|
-
</button>))}
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
{editor && <TetronsToolbar editor={editor}/>}
|
|
153
|
-
<div ref={wrapperRef} className="flex-grow p-4 md:p-6 bg-white border border-gray-300 rounded shadow-sm overflow-auto min-h-0 prose relative" onClick={handleEditorClick}>
|
|
154
|
-
{editor ? (<>
|
|
155
|
-
<TiptapEditorContent editor={editor}/>
|
|
156
|
-
{editor && <TableContextMenu editor={editor}/>}
|
|
157
|
-
</>) : (<div className="text-gray-500">Loading editor...</div>)}
|
|
158
|
-
</div>
|
|
146
|
+
: "border-gray-300 text-gray-700 hover:border-gray-600"}`} title={`Restore Version ${idx + 1}`}>
|
|
147
|
+
{`V${idx + 1}`}
|
|
148
|
+
</button>))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{editor && <TetronsToolbar editor={editor}/>}
|
|
153
|
+
<div ref={wrapperRef} className="flex-grow p-4 md:p-6 bg-white border border-gray-300 rounded shadow-sm overflow-auto min-h-0 prose relative" onClick={handleEditorClick}>
|
|
154
|
+
{editor ? (<>
|
|
155
|
+
<TiptapEditorContent editor={editor}/>
|
|
156
|
+
{editor && <TableContextMenu editor={editor}/>}
|
|
157
|
+
</>) : (<div className="text-gray-500">Loading editor...</div>)}
|
|
158
|
+
</div>
|
|
159
159
|
</div>);
|
|
160
160
|
}
|
|
@@ -23,15 +23,15 @@ const ResizableImageComponent = ({ node, updateAttributes, selected, }) => {
|
|
|
23
23
|
padding: 2,
|
|
24
24
|
display: "inline-block",
|
|
25
25
|
maxWidth: "100%",
|
|
26
|
-
}}>
|
|
27
|
-
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
26
|
+
}}>
|
|
27
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
28
28
|
<img ref={imgRef} src={src} alt={alt !== null && alt !== void 0 ? alt : ""} title={title !== null && title !== void 0 ? title : ""} loading="lazy" style={{
|
|
29
29
|
width: width ? `${width}px` : "auto",
|
|
30
30
|
height: height ? `${height}px` : "auto",
|
|
31
31
|
display: "block",
|
|
32
32
|
userSelect: "none",
|
|
33
33
|
pointerEvents: "auto",
|
|
34
|
-
}} draggable={false}/>
|
|
34
|
+
}} draggable={false}/>
|
|
35
35
|
</NodeViewWrapper>);
|
|
36
36
|
};
|
|
37
37
|
export default ResizableImageComponent;
|
|
@@ -22,11 +22,11 @@ const ResizableVideoComponent = ({ node, updateAttributes, selected, }) => {
|
|
|
22
22
|
border: "1px solid #ccc",
|
|
23
23
|
padding: "2px",
|
|
24
24
|
display: "inline-block",
|
|
25
|
-
}}>
|
|
25
|
+
}}>
|
|
26
26
|
<video ref={videoRef} src={src} controls={controls} style={{
|
|
27
27
|
width: width ? `${width}px` : "auto",
|
|
28
28
|
height: height ? `${height}px` : "auto",
|
|
29
|
-
}}/>
|
|
29
|
+
}}/>
|
|
30
30
|
</NodeViewWrapper>);
|
|
31
31
|
};
|
|
32
32
|
export default ResizableVideoComponent;
|
|
@@ -42,13 +42,13 @@ export default function ActionGroup({ editor }) {
|
|
|
42
42
|
const html = editor.getHTML();
|
|
43
43
|
const printWindow = window.open("", "_blank");
|
|
44
44
|
if (printWindow) {
|
|
45
|
-
printWindow.document.write(`
|
|
46
|
-
<html>
|
|
47
|
-
<head>
|
|
48
|
-
<title>Print</title>
|
|
49
|
-
</head>
|
|
50
|
-
<body>${html}</body>
|
|
51
|
-
</html>
|
|
45
|
+
printWindow.document.write(`
|
|
46
|
+
<html>
|
|
47
|
+
<head>
|
|
48
|
+
<title>Print</title>
|
|
49
|
+
</head>
|
|
50
|
+
<body>${html}</body>
|
|
51
|
+
</html>
|
|
52
52
|
`);
|
|
53
53
|
printWindow.document.close();
|
|
54
54
|
printWindow.focus();
|
|
@@ -128,38 +128,38 @@ export default function ActionGroup({ editor }) {
|
|
|
128
128
|
link.click();
|
|
129
129
|
document.body.removeChild(link);
|
|
130
130
|
};
|
|
131
|
-
return (<div className="relative flex items-center gap-1" role="group" aria-label="Editor actions">
|
|
132
|
-
<ToolbarButton icon={MdZoomIn} onClick={zoomIn} title="Zoom In"/>
|
|
133
|
-
<ToolbarButton icon={MdZoomOut} onClick={zoomOut} title="Zoom Out"/>
|
|
134
|
-
<ToolbarButton icon={MdPrint} onClick={handlePrint} title="Print"/>
|
|
135
|
-
<ToolbarButton icon={MdSave} onClick={handleSave} title="Save"/>
|
|
136
|
-
|
|
137
|
-
<div className="relative" ref={dropdownRef}>
|
|
138
|
-
<button type="button" onClick={() => setDropdownOpen((open) => !open)} aria-haspopup="menu" aria-expanded={dropdownOpen ? "true" : "false"} className="flex items-center gap-1 px-2 py-1 rounded hover:bg-gray-100 focus:outline-none" title="Export">
|
|
139
|
-
<MdDownload />
|
|
140
|
-
<span className="text-sm"></span>
|
|
141
|
-
</button>
|
|
142
|
-
|
|
143
|
-
{dropdownOpen && (<div className="absolute z-10 mt-2 w-40 bg-white border rounded shadow-md">
|
|
131
|
+
return (<div className="relative flex items-center gap-1" role="group" aria-label="Editor actions">
|
|
132
|
+
<ToolbarButton icon={MdZoomIn} onClick={zoomIn} title="Zoom In"/>
|
|
133
|
+
<ToolbarButton icon={MdZoomOut} onClick={zoomOut} title="Zoom Out"/>
|
|
134
|
+
<ToolbarButton icon={MdPrint} onClick={handlePrint} title="Print"/>
|
|
135
|
+
<ToolbarButton icon={MdSave} onClick={handleSave} title="Save"/>
|
|
136
|
+
|
|
137
|
+
<div className="relative" ref={dropdownRef}>
|
|
138
|
+
<button type="button" onClick={() => setDropdownOpen((open) => !open)} aria-haspopup="menu" aria-expanded={dropdownOpen ? "true" : "false"} className="flex items-center gap-1 px-2 py-1 rounded hover:bg-gray-100 focus:outline-none" title="Export">
|
|
139
|
+
<MdDownload />
|
|
140
|
+
<span className="text-sm"></span>
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
{dropdownOpen && (<div className="absolute z-10 mt-2 w-40 bg-white border rounded shadow-md">
|
|
144
144
|
<button type="button" onClick={() => {
|
|
145
145
|
setDropdownOpen(false);
|
|
146
146
|
handleDownloadPDF();
|
|
147
|
-
}} className="w-full text-left px-4 py-2 hover:bg-gray-100">
|
|
148
|
-
Export as PDF
|
|
149
|
-
</button>
|
|
147
|
+
}} className="w-full text-left px-4 py-2 hover:bg-gray-100">
|
|
148
|
+
Export as PDF
|
|
149
|
+
</button>
|
|
150
150
|
<button type="button" onClick={() => {
|
|
151
151
|
setDropdownOpen(false);
|
|
152
152
|
handleDownloadHTML();
|
|
153
|
-
}} className="w-full text-left px-4 py-2 hover:bg-gray-100">
|
|
154
|
-
Export as HTML
|
|
155
|
-
</button>
|
|
153
|
+
}} className="w-full text-left px-4 py-2 hover:bg-gray-100">
|
|
154
|
+
Export as HTML
|
|
155
|
+
</button>
|
|
156
156
|
<button type="button" onClick={() => {
|
|
157
157
|
setDropdownOpen(false);
|
|
158
158
|
handleDownloadDOCX();
|
|
159
|
-
}} className="w-full text-left px-4 py-2 hover:bg-gray-100">
|
|
160
|
-
Export as DOCX
|
|
161
|
-
</button>
|
|
162
|
-
</div>)}
|
|
163
|
-
</div>
|
|
159
|
+
}} className="w-full text-left px-4 py-2 hover:bg-gray-100">
|
|
160
|
+
Export as DOCX
|
|
161
|
+
</button>
|
|
162
|
+
</div>)}
|
|
163
|
+
</div>
|
|
164
164
|
</div>);
|
|
165
165
|
}
|
|
@@ -2,7 +2,7 @@ import { MdContentPaste, MdContentCut, MdContentCopy, MdFormatPaint, } from "rea
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import ToolbarButton from "./ToolbarButton";
|
|
4
4
|
export default function ClipboardGroup({ editor }) {
|
|
5
|
-
return (<div className="flex gap-1 border-r pr-3">
|
|
5
|
+
return (<div className="flex gap-1 border-r pr-3">
|
|
6
6
|
<ToolbarButton icon={MdContentPaste} title="Paste" onClick={async () => {
|
|
7
7
|
try {
|
|
8
8
|
const text = await navigator.clipboard.readText();
|
|
@@ -11,7 +11,7 @@ export default function ClipboardGroup({ editor }) {
|
|
|
11
11
|
catch (error) {
|
|
12
12
|
console.error("Failed to read clipboard contents:", error);
|
|
13
13
|
}
|
|
14
|
-
}}/>
|
|
14
|
+
}}/>
|
|
15
15
|
<ToolbarButton icon={MdContentCut} title="Cut" onClick={() => {
|
|
16
16
|
const { from, to } = editor.state.selection;
|
|
17
17
|
if (from === to)
|
|
@@ -20,17 +20,17 @@ export default function ClipboardGroup({ editor }) {
|
|
|
20
20
|
navigator.clipboard.writeText(selectedText).then(() => {
|
|
21
21
|
editor.chain().focus().deleteRange({ from, to }).run();
|
|
22
22
|
});
|
|
23
|
-
}}/>
|
|
23
|
+
}}/>
|
|
24
24
|
<ToolbarButton icon={MdContentCopy} title="Copy" onClick={() => {
|
|
25
25
|
const { from, to } = editor.state.selection;
|
|
26
26
|
if (from === to)
|
|
27
27
|
return;
|
|
28
28
|
const selectedText = editor.state.doc.textBetween(from, to);
|
|
29
29
|
navigator.clipboard.writeText(selectedText);
|
|
30
|
-
}}/>
|
|
30
|
+
}}/>
|
|
31
31
|
<ToolbarButton icon={MdFormatPaint} title="Format Painter" onClick={() => {
|
|
32
32
|
const currentMarks = editor.getAttributes("textStyle");
|
|
33
33
|
localStorage.setItem("formatPainter", JSON.stringify(currentMarks));
|
|
34
|
-
}}/>
|
|
34
|
+
}}/>
|
|
35
35
|
</div>);
|
|
36
36
|
}
|
|
@@ -32,9 +32,9 @@ export default function FileGroup({ editor }) {
|
|
|
32
32
|
e.target.value = "";
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
-
return (<div className="flex items-center gap-1 border-r pr-3" role="group" aria-label="File actions">
|
|
36
|
-
<input type="file" accept=".json" ref={fileInputRef} onChange={handleFileChange} className="hidden" aria-label="Open JSON file"/>
|
|
37
|
-
<ToolbarButton icon={VscNewFile} onClick={handleNew} title="New"/>
|
|
38
|
-
<ToolbarButton icon={FaRegFolderOpen} onClick={handleOpen} title="Open File"/>
|
|
35
|
+
return (<div className="flex items-center gap-1 border-r pr-3" role="group" aria-label="File actions">
|
|
36
|
+
<input type="file" accept=".json" ref={fileInputRef} onChange={handleFileChange} className="hidden" aria-label="Open JSON file"/>
|
|
37
|
+
<ToolbarButton icon={VscNewFile} onClick={handleNew} title="New"/>
|
|
38
|
+
<ToolbarButton icon={FaRegFolderOpen} onClick={handleOpen} title="Open File"/>
|
|
39
39
|
</div>);
|
|
40
40
|
}
|
|
@@ -31,63 +31,63 @@ export default function FontStyleGroup({ editor }) {
|
|
|
31
31
|
editor.off("transaction", updateStates);
|
|
32
32
|
};
|
|
33
33
|
}, [editor]);
|
|
34
|
-
return (<div className="flex gap-1 border-r pr-3 items-center">
|
|
34
|
+
return (<div className="flex gap-1 border-r pr-3 items-center">
|
|
35
35
|
<select title="Font Family" className="text-sm border rounded px-1 py-0.5 mr-2" value={fontFamily} onChange={(e) => {
|
|
36
36
|
const value = e.target.value;
|
|
37
37
|
setFontFamily(value);
|
|
38
38
|
editor.chain().focus().setFontFamily(value).run();
|
|
39
|
-
}}>
|
|
40
|
-
<option value="Arial">Arial</option>
|
|
41
|
-
<option value="Georgia">Georgia</option>
|
|
42
|
-
<option value="Times New Roman">Times New Roman</option>
|
|
43
|
-
<option value="Courier New">Courier New</option>
|
|
44
|
-
<option value="Verdana">Verdana</option>
|
|
45
|
-
</select>
|
|
46
|
-
|
|
39
|
+
}}>
|
|
40
|
+
<option value="Arial">Arial</option>
|
|
41
|
+
<option value="Georgia">Georgia</option>
|
|
42
|
+
<option value="Times New Roman">Times New Roman</option>
|
|
43
|
+
<option value="Courier New">Courier New</option>
|
|
44
|
+
<option value="Verdana">Verdana</option>
|
|
45
|
+
</select>
|
|
46
|
+
|
|
47
47
|
<select title="Font Size" className="text-sm border rounded px-1 py-0.5 mr-2" value={fontSize} onChange={(e) => {
|
|
48
48
|
const value = e.target.value;
|
|
49
49
|
setFontSize(value);
|
|
50
50
|
editor.chain().focus().setFontSize(value).run();
|
|
51
|
-
}}>
|
|
52
|
-
<option value="12px">12</option>
|
|
53
|
-
<option value="14px">14</option>
|
|
54
|
-
<option value="16px">16</option>
|
|
55
|
-
<option value="18px">18</option>
|
|
56
|
-
<option value="24px">24</option>
|
|
57
|
-
<option value="36px">36</option>
|
|
58
|
-
<option value="48px">48</option>
|
|
59
|
-
<option value="64px">64</option>
|
|
60
|
-
<option value="72px">72</option>
|
|
61
|
-
</select>
|
|
62
|
-
|
|
63
|
-
<ToolbarButton icon={MdFormatBold} label="Bold" onClick={() => editor.chain().focus().toggleBold().run()} isActive={editor.isActive("bold")}/>
|
|
64
|
-
<ToolbarButton icon={MdFormatItalic} label="Italic" onClick={() => editor.chain().focus().toggleItalic().run()} isActive={editor.isActive("italic")}/>
|
|
65
|
-
<ToolbarButton icon={MdFormatUnderlined} label="Underline" onClick={() => editor.chain().focus().toggleUnderline().run()} isActive={editor.isActive("underline")}/>
|
|
66
|
-
<ToolbarButton icon={MdStrikethroughS} label="Strikethrough" onClick={() => editor.chain().focus().toggleStrike().run()} isActive={editor.isActive("strike")}/>
|
|
67
|
-
<ToolbarButton icon={MdSubscript} label="Subscript" onClick={() => editor.chain().focus().toggleSubscript().run()} isActive={editor.isActive("subscript")}/>
|
|
68
|
-
<ToolbarButton icon={MdSuperscript} label="Superscript" onClick={() => editor.chain().focus().toggleSuperscript().run()} isActive={editor.isActive("superscript")}/>
|
|
69
|
-
|
|
70
|
-
<label title="Font Color" aria-label="Font Color" className="relative w-8 h-8 flex items-center justify-center cursor-pointer color-label" style={{ "--indicator-color": textColor }}>
|
|
71
|
-
<ImTextColor size={20} className="text-gray-700"/>
|
|
72
|
-
<div className="color-indicator"/>
|
|
51
|
+
}}>
|
|
52
|
+
<option value="12px">12</option>
|
|
53
|
+
<option value="14px">14</option>
|
|
54
|
+
<option value="16px">16</option>
|
|
55
|
+
<option value="18px">18</option>
|
|
56
|
+
<option value="24px">24</option>
|
|
57
|
+
<option value="36px">36</option>
|
|
58
|
+
<option value="48px">48</option>
|
|
59
|
+
<option value="64px">64</option>
|
|
60
|
+
<option value="72px">72</option>
|
|
61
|
+
</select>
|
|
62
|
+
|
|
63
|
+
<ToolbarButton icon={MdFormatBold} label="Bold" onClick={() => editor.chain().focus().toggleBold().run()} isActive={editor.isActive("bold")}/>
|
|
64
|
+
<ToolbarButton icon={MdFormatItalic} label="Italic" onClick={() => editor.chain().focus().toggleItalic().run()} isActive={editor.isActive("italic")}/>
|
|
65
|
+
<ToolbarButton icon={MdFormatUnderlined} label="Underline" onClick={() => editor.chain().focus().toggleUnderline().run()} isActive={editor.isActive("underline")}/>
|
|
66
|
+
<ToolbarButton icon={MdStrikethroughS} label="Strikethrough" onClick={() => editor.chain().focus().toggleStrike().run()} isActive={editor.isActive("strike")}/>
|
|
67
|
+
<ToolbarButton icon={MdSubscript} label="Subscript" onClick={() => editor.chain().focus().toggleSubscript().run()} isActive={editor.isActive("subscript")}/>
|
|
68
|
+
<ToolbarButton icon={MdSuperscript} label="Superscript" onClick={() => editor.chain().focus().toggleSuperscript().run()} isActive={editor.isActive("superscript")}/>
|
|
69
|
+
|
|
70
|
+
<label title="Font Color" aria-label="Font Color" className="relative w-8 h-8 flex items-center justify-center cursor-pointer color-label" style={{ "--indicator-color": textColor }}>
|
|
71
|
+
<ImTextColor size={20} className="text-gray-700"/>
|
|
72
|
+
<div className="color-indicator"/>
|
|
73
73
|
<input type="color" value={textColor} onChange={(e) => {
|
|
74
74
|
const color = e.target.value;
|
|
75
75
|
setTextColor(color);
|
|
76
76
|
editor.chain().focus().setColor(color).run();
|
|
77
|
-
}} className="absolute inset-0 opacity-0 cursor-pointer"/>
|
|
78
|
-
</label>
|
|
79
|
-
|
|
80
|
-
<label title="Highlight Color" aria-label="Highlight Color" className="relative w-8 h-8 flex items-center justify-center cursor-pointer color-label" style={{ "--indicator-color": highlightColor }}>
|
|
81
|
-
<BiSolidColorFill size={20} className="text-gray-700"/>
|
|
82
|
-
<div className="color-indicator"/>
|
|
77
|
+
}} className="absolute inset-0 opacity-0 cursor-pointer"/>
|
|
78
|
+
</label>
|
|
79
|
+
|
|
80
|
+
<label title="Highlight Color" aria-label="Highlight Color" className="relative w-8 h-8 flex items-center justify-center cursor-pointer color-label" style={{ "--indicator-color": highlightColor }}>
|
|
81
|
+
<BiSolidColorFill size={20} className="text-gray-700"/>
|
|
82
|
+
<div className="color-indicator"/>
|
|
83
83
|
<input type="color" value={highlightColor} onChange={(e) => {
|
|
84
84
|
const color = e.target.value;
|
|
85
85
|
setHighlightColor(color);
|
|
86
86
|
editor.chain().focus().setHighlight({ color }).run();
|
|
87
|
-
}} className="absolute inset-0 opacity-0 cursor-pointer"/>
|
|
88
|
-
</label>
|
|
89
|
-
|
|
90
|
-
<ToolbarButton icon={MdFormatClear} label="Clear Formatting" onClick={() => editor.chain().focus().unsetAllMarks().run()}/>
|
|
87
|
+
}} className="absolute inset-0 opacity-0 cursor-pointer"/>
|
|
88
|
+
</label>
|
|
89
|
+
|
|
90
|
+
<ToolbarButton icon={MdFormatClear} label="Clear Formatting" onClick={() => editor.chain().focus().unsetAllMarks().run()}/>
|
|
91
91
|
<ToolbarButton icon={MdFormatPaint} label="Apply Painter Format" onClick={() => {
|
|
92
92
|
const format = JSON.parse(localStorage.getItem("formatPainter") || "{}");
|
|
93
93
|
if (format.color)
|
|
@@ -99,6 +99,6 @@ export default function FontStyleGroup({ editor }) {
|
|
|
99
99
|
.setHighlight({ color: format.backgroundColor })
|
|
100
100
|
.run();
|
|
101
101
|
}
|
|
102
|
-
}}/>
|
|
102
|
+
}}/>
|
|
103
103
|
</div>);
|
|
104
104
|
}
|
|
@@ -95,25 +95,25 @@ export default function InsertGroup({ editor }) {
|
|
|
95
95
|
return url;
|
|
96
96
|
}
|
|
97
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]">
|
|
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
105
|
{[...Array(10)].map((_, row) => [...Array(10)].map((_, col) => {
|
|
106
106
|
const isSelected = row < selectedRows && col < selectedCols;
|
|
107
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(); }}/>
|
|
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
117
|
<ToolbarButton icon={MdInsertLink} label="Insert Link" onClick={() => {
|
|
118
118
|
const url = prompt("Enter URL");
|
|
119
119
|
if (url)
|
|
@@ -123,7 +123,7 @@ export default function InsertGroup({ editor }) {
|
|
|
123
123
|
.extendMarkRange("link")
|
|
124
124
|
.setLink({ href: url })
|
|
125
125
|
.run();
|
|
126
|
-
}}/>
|
|
126
|
+
}}/>
|
|
127
127
|
<ToolbarButton icon={MdInsertComment} label="Insert Comment" onClick={() => {
|
|
128
128
|
const comment = prompt("Enter your comment");
|
|
129
129
|
if (comment &&
|
|
@@ -133,14 +133,14 @@ export default function InsertGroup({ editor }) {
|
|
|
133
133
|
else {
|
|
134
134
|
console.warn("Cannot apply comment — maybe no selection?");
|
|
135
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()}/>
|
|
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
144
|
<ToolbarButton icon={MdOutlineOndemandVideo} label="Embed" onClick={() => {
|
|
145
145
|
const url = prompt("Enter embed URL (YouTube, Vimeo, Google Maps, etc.)");
|
|
146
146
|
if (!url)
|
|
@@ -157,6 +157,6 @@ export default function InsertGroup({ editor }) {
|
|
|
157
157
|
height: height ? parseInt(height) : 315,
|
|
158
158
|
})
|
|
159
159
|
.run();
|
|
160
|
-
}}/>
|
|
160
|
+
}}/>
|
|
161
161
|
</div>);
|
|
162
162
|
}
|
|
@@ -3,14 +3,14 @@ import React from "react";
|
|
|
3
3
|
import { MdFormatListBulleted, MdFormatListNumbered, MdFormatIndentDecrease, MdFormatIndentIncrease, MdFormatAlignLeft, MdFormatAlignCenter, MdFormatAlignRight, MdFormatAlignJustify, } from "react-icons/md";
|
|
4
4
|
import ToolbarButton from "./ToolbarButton";
|
|
5
5
|
export default function ListAlignGroup({ editor }) {
|
|
6
|
-
return (<div className="flex gap-1 border-r pr-3">
|
|
7
|
-
<ToolbarButton icon={MdFormatListBulleted} title="Bulleted List" onClick={() => editor.chain().focus().toggleBulletList().run()} disabled={!editor.can().toggleBulletList()}/>
|
|
8
|
-
<ToolbarButton icon={MdFormatListNumbered} title="Numbered List" onClick={() => editor.chain().focus().toggleOrderedList().run()} disabled={!editor.can().toggleOrderedList()}/>
|
|
9
|
-
<ToolbarButton icon={MdFormatIndentIncrease} title="Increase Indent" onClick={() => editor.chain().focus().sinkListItem("listItem").run()} disabled={!editor.can().sinkListItem("listItem")}/>
|
|
10
|
-
<ToolbarButton icon={MdFormatIndentDecrease} title="Decrease Indent" onClick={() => editor.chain().focus().liftListItem("listItem").run()} disabled={!editor.can().liftListItem("listItem")}/>
|
|
11
|
-
<ToolbarButton icon={MdFormatAlignLeft} title="Align Left" onClick={() => editor.chain().focus().setTextAlign("left").run()} disabled={!editor.can().setTextAlign("left")}/>
|
|
12
|
-
<ToolbarButton icon={MdFormatAlignCenter} title="Align Center" onClick={() => editor.chain().focus().setTextAlign("center").run()} disabled={!editor.can().setTextAlign("center")}/>
|
|
13
|
-
<ToolbarButton icon={MdFormatAlignRight} title="Align Right" onClick={() => editor.chain().focus().setTextAlign("right").run()} disabled={!editor.can().setTextAlign("right")}/>
|
|
14
|
-
<ToolbarButton icon={MdFormatAlignJustify} title="Justify" onClick={() => editor.chain().focus().setTextAlign("justify").run()} disabled={!editor.can().setTextAlign("justify")}/>
|
|
6
|
+
return (<div className="flex gap-1 border-r pr-3">
|
|
7
|
+
<ToolbarButton icon={MdFormatListBulleted} title="Bulleted List" onClick={() => editor.chain().focus().toggleBulletList().run()} disabled={!editor.can().toggleBulletList()}/>
|
|
8
|
+
<ToolbarButton icon={MdFormatListNumbered} title="Numbered List" onClick={() => editor.chain().focus().toggleOrderedList().run()} disabled={!editor.can().toggleOrderedList()}/>
|
|
9
|
+
<ToolbarButton icon={MdFormatIndentIncrease} title="Increase Indent" onClick={() => editor.chain().focus().sinkListItem("listItem").run()} disabled={!editor.can().sinkListItem("listItem")}/>
|
|
10
|
+
<ToolbarButton icon={MdFormatIndentDecrease} title="Decrease Indent" onClick={() => editor.chain().focus().liftListItem("listItem").run()} disabled={!editor.can().liftListItem("listItem")}/>
|
|
11
|
+
<ToolbarButton icon={MdFormatAlignLeft} title="Align Left" onClick={() => editor.chain().focus().setTextAlign("left").run()} disabled={!editor.can().setTextAlign("left")}/>
|
|
12
|
+
<ToolbarButton icon={MdFormatAlignCenter} title="Align Center" onClick={() => editor.chain().focus().setTextAlign("center").run()} disabled={!editor.can().setTextAlign("center")}/>
|
|
13
|
+
<ToolbarButton icon={MdFormatAlignRight} title="Align Right" onClick={() => editor.chain().focus().setTextAlign("right").run()} disabled={!editor.can().setTextAlign("right")}/>
|
|
14
|
+
<ToolbarButton icon={MdFormatAlignJustify} title="Justify" onClick={() => editor.chain().focus().setTextAlign("justify").run()} disabled={!editor.can().setTextAlign("justify")}/>
|
|
15
15
|
</div>);
|
|
16
16
|
}
|
|
@@ -7,25 +7,25 @@ export default function MiscGroup({ editor }) {
|
|
|
7
7
|
const previewWindow = window.open("", "_blank");
|
|
8
8
|
if (previewWindow) {
|
|
9
9
|
previewWindow.document.open();
|
|
10
|
-
previewWindow.document.write(`
|
|
11
|
-
<html>
|
|
12
|
-
<head>
|
|
13
|
-
<title>Preview</title>
|
|
14
|
-
<style>
|
|
15
|
-
body { font-family: sans-serif; padding: 2rem; line-height: 1.6; }
|
|
16
|
-
</style>
|
|
17
|
-
</head>
|
|
18
|
-
<body>${html}</body>
|
|
19
|
-
</html>
|
|
10
|
+
previewWindow.document.write(`
|
|
11
|
+
<html>
|
|
12
|
+
<head>
|
|
13
|
+
<title>Preview</title>
|
|
14
|
+
<style>
|
|
15
|
+
body { font-family: sans-serif; padding: 2rem; line-height: 1.6; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>${html}</body>
|
|
19
|
+
</html>
|
|
20
20
|
`);
|
|
21
21
|
previewWindow.document.close();
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
-
return (<div className="flex gap-1 items-center border-r pr-3">
|
|
25
|
-
<ToolbarButton icon={MdUndo} label="Undo" onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().undo()}/>
|
|
26
|
-
<ToolbarButton icon={MdRedo} label="Redo" onClick={() => editor.chain().focus().redo().run()} disabled={!editor.can().redo()}/>
|
|
27
|
-
<ToolbarButton icon={MdRefresh} label="Reset Formatting" onClick={() => editor.chain().focus().unsetAllMarks().clearNodes().run()}/>
|
|
28
|
-
<ToolbarButton icon={MdCode} label="Toggle Code Block" onClick={() => editor.chain().focus().toggleCodeBlock().run()} isActive={editor.isActive("codeBlock")}/>
|
|
29
|
-
<ToolbarButton icon={MdVisibility} label="Preview" onClick={handlePreview}/>
|
|
24
|
+
return (<div className="flex gap-1 items-center border-r pr-3">
|
|
25
|
+
<ToolbarButton icon={MdUndo} label="Undo" onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().undo()}/>
|
|
26
|
+
<ToolbarButton icon={MdRedo} label="Redo" onClick={() => editor.chain().focus().redo().run()} disabled={!editor.can().redo()}/>
|
|
27
|
+
<ToolbarButton icon={MdRefresh} label="Reset Formatting" onClick={() => editor.chain().focus().unsetAllMarks().clearNodes().run()}/>
|
|
28
|
+
<ToolbarButton icon={MdCode} label="Toggle Code Block" onClick={() => editor.chain().focus().toggleCodeBlock().run()} isActive={editor.isActive("codeBlock")}/>
|
|
29
|
+
<ToolbarButton icon={MdVisibility} label="Preview" onClick={handlePreview}/>
|
|
30
30
|
</div>);
|
|
31
31
|
}
|