tetrons 2.1.9 → 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 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.
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { Metadata } from "next";
2
3
  import "./globals.css";
3
4
  export declare const metadata: Metadata;
@@ -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
  }
@@ -1 +1,2 @@
1
+ /// <reference types="react" />
1
2
  export default function Home(): import("react").JSX.Element;
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
  }