tetrons 2.2.3 → 2.2.4

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.
Files changed (49) hide show
  1. package/dist/app/api/register/route.d.ts +6 -0
  2. package/dist/app/api/register/route.js +26 -0
  3. package/dist/app/api/validate/route.d.ts +7 -0
  4. package/dist/app/api/validate/route.js +18 -0
  5. package/dist/app/layout.d.ts +1 -2
  6. package/dist/app/{layout.js → layout.jsx} +5 -2
  7. package/dist/app/page.d.ts +1 -1
  8. package/dist/app/page.jsx +55 -0
  9. package/dist/components/tetrons/EditorContent.d.ts +6 -1
  10. package/dist/components/tetrons/{EditorContent.js → EditorContent.jsx} +58 -3
  11. package/dist/components/tetrons/{ResizableImageComponent.js → ResizableImageComponent.jsx} +12 -10
  12. package/dist/components/tetrons/{ResizableVideoComponent.js → ResizableVideoComponent.jsx} +8 -7
  13. package/dist/components/tetrons/toolbar/ActionGroup.d.ts +2 -1
  14. package/dist/components/tetrons/toolbar/{ActionGroup.js → ActionGroup.jsx} +35 -12
  15. package/dist/components/tetrons/toolbar/ClipboardGroup.d.ts +2 -1
  16. package/dist/components/tetrons/toolbar/ClipboardGroup.jsx +36 -0
  17. package/dist/components/tetrons/toolbar/FileGroup.d.ts +2 -1
  18. package/dist/components/tetrons/toolbar/{FileGroup.js → FileGroup.jsx} +6 -3
  19. package/dist/components/tetrons/toolbar/FontStyleGroup.d.ts +2 -1
  20. package/dist/components/tetrons/toolbar/FontStyleGroup.jsx +104 -0
  21. package/dist/components/tetrons/toolbar/InsertGroup.d.ts +2 -1
  22. package/dist/components/tetrons/toolbar/InsertGroup.jsx +163 -0
  23. package/dist/components/tetrons/toolbar/ListAlignGroup.d.ts +2 -1
  24. package/dist/components/tetrons/toolbar/ListAlignGroup.jsx +16 -0
  25. package/dist/components/tetrons/toolbar/MiscGroup.d.ts +2 -1
  26. package/dist/components/tetrons/toolbar/MiscGroup.jsx +31 -0
  27. package/dist/components/tetrons/toolbar/TableContextMenu.d.ts +2 -1
  28. package/dist/components/tetrons/toolbar/{TableContextMenu.js → TableContextMenu.jsx} +21 -3
  29. package/dist/components/tetrons/toolbar/TetronsToolbar.d.ts +2 -1
  30. package/dist/components/tetrons/toolbar/{TetronsToolbar.js → TetronsToolbar.jsx} +14 -2
  31. package/dist/components/tetrons/toolbar/ToolbarButton.jsx +8 -0
  32. package/dist/index.d.mts +5 -2
  33. package/dist/index.mjs +658 -744
  34. package/dist/lib/db.d.ts +1 -0
  35. package/dist/lib/db.js +15 -0
  36. package/dist/models/ApiKey.d.ts +2 -0
  37. package/dist/models/ApiKey.js +14 -0
  38. package/dist/utils/apiKeyUtils.d.ts +11 -0
  39. package/dist/utils/apiKeyUtils.js +17 -0
  40. package/package.json +2 -1
  41. package/dist/app/page.js +0 -6
  42. package/dist/components/tetrons/toolbar/ClipboardGroup.js +0 -31
  43. package/dist/components/tetrons/toolbar/FontStyleGroup.js +0 -63
  44. package/dist/components/tetrons/toolbar/InsertGroup.js +0 -138
  45. package/dist/components/tetrons/toolbar/ListAlignGroup.js +0 -7
  46. package/dist/components/tetrons/toolbar/MiscGroup.js +0 -25
  47. package/dist/components/tetrons/toolbar/ToolbarButton.js +0 -7
  48. /package/dist/components/UI/{Button.js → Button.jsx} +0 -0
  49. /package/dist/components/UI/{Dropdown.js → Dropdown.jsx} +0 -0
@@ -0,0 +1,6 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function POST(req: NextRequest): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ apiKey: string;
6
+ }>>;
@@ -0,0 +1,26 @@
1
+ import { NextResponse } from "next/server";
2
+ import { connectDB } from "../../../lib/db";
3
+ import { ApiKey } from "../../../models/ApiKey";
4
+ import { generateApiKey } from "../../../utils/apiKeyUtils";
5
+ export async function POST(req) {
6
+ const { email, organization, version } = await req.json();
7
+ if (!email || !organization || !version)
8
+ return NextResponse.json({ error: "Missing fields" }, { status: 400 });
9
+ await connectDB();
10
+ if (version === "free") {
11
+ const expiresAt = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
12
+ const apiKey = generateApiKey(48);
13
+ await ApiKey.deleteMany({ email, version });
14
+ await ApiKey.create({
15
+ email,
16
+ organization,
17
+ version,
18
+ apiKey,
19
+ expiresAt,
20
+ });
21
+ return NextResponse.json({ apiKey, expiresAt });
22
+ }
23
+ const apiKey = generateApiKey(48);
24
+ await ApiKey.create({ email, organization, version, apiKey });
25
+ return NextResponse.json({ apiKey });
26
+ }
@@ -0,0 +1,7 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function POST(req: NextRequest): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ valid: boolean;
6
+ version: any;
7
+ }>>;
@@ -0,0 +1,18 @@
1
+ import { NextResponse } from "next/server";
2
+ import { connectDB } from "../../../lib/db";
3
+ import { ApiKey } from "../../../models/ApiKey";
4
+ export async function POST(req) {
5
+ const { apiKey } = await req.json();
6
+ if (!apiKey)
7
+ return NextResponse.json({ error: "API key required" }, { status: 400 });
8
+ await connectDB();
9
+ const keyEntry = await ApiKey.findOne({ apiKey });
10
+ if (!keyEntry)
11
+ return NextResponse.json({ error: "Invalid key" }, { status: 401 });
12
+ if (keyEntry.version === "free" &&
13
+ keyEntry.expiresAt &&
14
+ new Date() > new Date(keyEntry.expiresAt)) {
15
+ return NextResponse.json({ error: "Free trial expired" }, { status: 403 });
16
+ }
17
+ return NextResponse.json({ valid: true, version: keyEntry.version });
18
+ }
@@ -1,7 +1,6 @@
1
- /// <reference types="react" />
2
1
  import type { Metadata } from "next";
3
2
  import "./globals.css";
4
3
  export declare const metadata: Metadata;
5
4
  export default function RootLayout({ children, }: {
6
5
  children: React.ReactNode;
7
- }): import("react/jsx-runtime").JSX.Element;
6
+ }): import("react").JSX.Element;
@@ -1,4 +1,3 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
1
  import { Geist, Geist_Mono } from "next/font/google";
3
2
  import "./globals.css";
4
3
  const geistSans = Geist({
@@ -23,5 +22,9 @@ export const metadata = {
23
22
  manifest: "/site.webmanifest",
24
23
  };
25
24
  export default function RootLayout({ children, }) {
26
- return (_jsx("html", { lang: "en", children: _jsx("body", { suppressHydrationWarning: true, className: `${geistSans.variable} ${geistMono.variable} font-sans antialiased bg-gray-50 text-gray-900`, children: _jsx("main", { className: "max-w-screen-xl mx-auto p-4", children: 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>
29
+ </html>);
27
30
  }
@@ -1,2 +1,2 @@
1
1
  import "../styles/tetrons.css";
2
- export default function Home(): import("react/jsx-runtime").JSX.Element;
2
+ export default function Home(): import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ import EditorContent from "../components/tetrons/EditorContent";
4
+ import "../styles/tetrons.css";
5
+ export default function Home() {
6
+ const [apiKey, setApiKey] = useState(null);
7
+ const [loading, setLoading] = useState(true);
8
+ useEffect(() => {
9
+ const fetchApiKey = async () => {
10
+ try {
11
+ const cachedKey = localStorage.getItem("my-api-key");
12
+ if (cachedKey) {
13
+ setApiKey(cachedKey);
14
+ setLoading(false);
15
+ return;
16
+ }
17
+ const res = await fetch("/api/register", {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ },
22
+ body: JSON.stringify({
23
+ email: "mr.swastikjha@gmail.com",
24
+ organization: "FCSPL",
25
+ version: "free",
26
+ }),
27
+ });
28
+ const data = await res.json();
29
+ if (!res.ok || !data.apiKey) {
30
+ throw new Error(data.error || "Failed to fetch API key");
31
+ }
32
+ localStorage.setItem("my-api-key", data.apiKey);
33
+ setApiKey(data.apiKey);
34
+ }
35
+ catch (error) {
36
+ console.error("Error fetching API key:", error);
37
+ }
38
+ finally {
39
+ setLoading(false);
40
+ }
41
+ };
42
+ fetchApiKey();
43
+ }, []);
44
+ if (loading) {
45
+ return <div className="text-center p-4">⏳ Loading Editor...</div>;
46
+ }
47
+ if (!apiKey) {
48
+ return (<div className="text-center text-red-600">❌ Failed to load API key</div>);
49
+ }
50
+ return (<main className="flex flex-col h-screen overflow-hidden">
51
+ <div className="flex-1 overflow-auto flex flex-col">
52
+ <EditorContent apiKey={apiKey}/>
53
+ </div>
54
+ </main>);
55
+ }
@@ -1 +1,6 @@
1
- export default function EditorContent(): import("react/jsx-runtime").JSX.Element;
1
+ import React from "react";
2
+ type EditorContentProps = {
3
+ apiKey: string;
4
+ };
5
+ export default function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
6
+ export {};
@@ -1,5 +1,4 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
2
  import React from "react";
4
3
  import { Comment } from "./toolbar/extensions/Comment";
5
4
  import { useEffect, useRef } from "react";
@@ -46,9 +45,38 @@ import ts from "highlight.js/lib/languages/typescript";
46
45
  const lowlight = createLowlight();
47
46
  lowlight.register("js", js);
48
47
  lowlight.register("ts", ts);
49
- export default function EditorContent() {
48
+ export default function EditorContent({ apiKey }) {
49
+ const [isValid, setIsValid] = React.useState(null);
50
+ const [error, setError] = React.useState(null);
50
51
  const [versions, setVersions] = React.useState([]);
51
52
  const [currentVersionIndex, setCurrentVersionIndex] = React.useState(null);
53
+ useEffect(() => {
54
+ const validateKey = async () => {
55
+ try {
56
+ const res = await fetch("/api/validate", {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/json",
60
+ },
61
+ body: JSON.stringify({ apiKey }),
62
+ });
63
+ const data = await res.json();
64
+ if (!res.ok)
65
+ throw new Error(data.error || "Invalid API Key");
66
+ setIsValid(true);
67
+ }
68
+ catch (err) {
69
+ if (err instanceof Error) {
70
+ setError(err.message || "Invalid API Key");
71
+ }
72
+ else {
73
+ setError("Invalid API Key");
74
+ }
75
+ setIsValid(false);
76
+ }
77
+ };
78
+ validateKey();
79
+ }, [apiKey]);
52
80
  const editor = useEditor({
53
81
  extensions: [
54
82
  Document,
@@ -134,5 +162,32 @@ export default function EditorContent() {
134
162
  setCurrentVersionIndex(index);
135
163
  }
136
164
  };
137
- return (_jsxs("div", { className: "editor-container", children: [_jsxs("div", { className: "editor-toolbar", children: [_jsx("button", { type: "button", onClick: saveVersion, disabled: !editor, className: "editor-save-btn", children: "Save Version" }), _jsxs("div", { className: "editor-versions-wrapper", children: [versions.length === 0 && (_jsx("span", { className: "editor-no-versions", children: "No saved versions" })), versions.map((_, idx) => (_jsx("button", { type: "button", onClick: () => restoreVersion(idx), className: `editor-version-btn ${idx === currentVersionIndex ? "active" : ""}`, title: `Restore Version ${idx + 1}`, children: `V${idx + 1}` }, idx)))] })] }), editor && _jsx(TetronsToolbar, { editor: editor }), _jsx("div", { ref: wrapperRef, className: "editor-content-wrapper", onClick: handleEditorClick, children: editor ? (_jsxs(_Fragment, { children: [_jsx(TiptapEditorContent, { editor: editor }), editor && _jsx(TableContextMenu, { editor: editor })] })) : (_jsx("div", { className: "editor-loading", children: "Loading editor..." })) })] }));
165
+ if (isValid === false) {
166
+ return <div className="editor-error">⚠️ {error}</div>;
167
+ }
168
+ if (isValid === null) {
169
+ return <div className="editor-loading">🔍 Validating license...</div>;
170
+ }
171
+ return (<div className="editor-container">
172
+ <div className="editor-toolbar">
173
+ <button type="button" onClick={saveVersion} disabled={!editor} className="editor-save-btn">
174
+ Save Version
175
+ </button>
176
+
177
+ <div className="editor-versions-wrapper">
178
+ {versions.length === 0 && (<span className="editor-no-versions">No saved versions</span>)}
179
+ {versions.map((_, idx) => (<button type="button" key={idx} onClick={() => restoreVersion(idx)} className={`editor-version-btn ${idx === currentVersionIndex ? "active" : ""}`} title={`Restore Version ${idx + 1}`}>
180
+ {`V${idx + 1}`}
181
+ </button>))}
182
+ </div>
183
+ </div>
184
+
185
+ {editor && <TetronsToolbar editor={editor}/>}
186
+ <div ref={wrapperRef} className="editor-content-wrapper" onClick={handleEditorClick}>
187
+ {editor ? (<>
188
+ <TiptapEditorContent editor={editor}/>
189
+ {editor && <TableContextMenu editor={editor}/>}
190
+ </>) : (<div className="editor-loading">Loading editor...</div>)}
191
+ </div>
192
+ </div>);
138
193
  }
@@ -1,5 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
1
+ import React, { useRef, useEffect } from "react";
3
2
  import { NodeViewWrapper } from "@tiptap/react";
4
3
  const ResizableImageComponent = ({ node, updateAttributes, selected, }) => {
5
4
  const { src, alt, title, width, height } = node.attrs;
@@ -17,19 +16,22 @@ const ResizableImageComponent = ({ node, updateAttributes, selected, }) => {
17
16
  observer.observe(img);
18
17
  return () => observer.disconnect();
19
18
  }, [updateAttributes]);
20
- return (_jsx(NodeViewWrapper, { ref: wrapperRef, contentEditable: false, className: `resizable-image-wrapper ${selected ? "ProseMirror-selectednode" : ""}`, style: {
19
+ return (<NodeViewWrapper ref={wrapperRef} contentEditable={false} className={`resizable-image-wrapper ${selected ? "ProseMirror-selectednode" : ""}`} style={{
21
20
  resize: "both",
22
21
  overflow: "auto",
23
22
  border: "1px solid #ccc",
24
23
  padding: 2,
25
24
  display: "inline-block",
26
25
  maxWidth: "100%",
27
- }, children: _jsx("img", { ref: imgRef, src: src, alt: alt !== null && alt !== void 0 ? alt : "", title: title !== null && title !== void 0 ? title : "", loading: "lazy", style: {
28
- width: width ? `${width}px` : "auto",
29
- height: height ? `${height}px` : "auto",
30
- display: "block",
31
- userSelect: "none",
32
- pointerEvents: "auto",
33
- }, draggable: false }) }));
26
+ }}>
27
+ {/* eslint-disable-next-line @next/next/no-img-element */}
28
+ <img ref={imgRef} src={src} alt={alt !== null && alt !== void 0 ? alt : ""} title={title !== null && title !== void 0 ? title : ""} loading="lazy" style={{
29
+ width: width ? `${width}px` : "auto",
30
+ height: height ? `${height}px` : "auto",
31
+ display: "block",
32
+ userSelect: "none",
33
+ pointerEvents: "auto",
34
+ }} draggable={false}/>
35
+ </NodeViewWrapper>);
34
36
  };
35
37
  export default ResizableImageComponent;
@@ -1,5 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
1
+ import React, { useRef, useEffect } from "react";
3
2
  import { NodeViewWrapper } from "@tiptap/react";
4
3
  const ResizableVideoComponent = ({ node, updateAttributes, selected, }) => {
5
4
  const { src, controls, width, height } = node.attrs;
@@ -17,15 +16,17 @@ const ResizableVideoComponent = ({ node, updateAttributes, selected, }) => {
17
16
  observer.observe(video);
18
17
  return () => observer.disconnect();
19
18
  }, [updateAttributes]);
20
- return (_jsx(NodeViewWrapper, { ref: wrapperRef, contentEditable: false, className: `resizable-video-wrapper ${selected ? "ProseMirror-selectednode" : ""}`, style: {
19
+ return (<NodeViewWrapper ref={wrapperRef} contentEditable={false} className={`resizable-video-wrapper ${selected ? "ProseMirror-selectednode" : ""}`} style={{
21
20
  resize: "both",
22
21
  overflow: "auto",
23
22
  border: "1px solid #ccc",
24
23
  padding: "2px",
25
24
  display: "inline-block",
26
- }, children: _jsx("video", { ref: videoRef, src: src, controls: controls, style: {
27
- width: width ? `${width}px` : "auto",
28
- height: height ? `${height}px` : "auto",
29
- } }) }));
25
+ }}>
26
+ <video ref={videoRef} src={src} controls={controls} style={{
27
+ width: width ? `${width}px` : "auto",
28
+ height: height ? `${height}px` : "auto",
29
+ }}/>
30
+ </NodeViewWrapper>);
30
31
  };
31
32
  export default ResizableVideoComponent;
@@ -1,6 +1,7 @@
1
+ import React from "react";
1
2
  import { Editor } from "@tiptap/react";
2
3
  type ActionGroupProps = {
3
4
  editor: Editor;
4
5
  };
5
- export default function ActionGroup({ editor }: ActionGroupProps): import("react/jsx-runtime").JSX.Element;
6
+ export default function ActionGroup({ editor }: ActionGroupProps): React.JSX.Element;
6
7
  export {};
@@ -1,6 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from "react";
2
+ import React, { useEffect, useRef, useState } from "react";
4
3
  import { MdZoomIn, MdZoomOut, MdPrint, MdSave, MdDownload, } from "react-icons/md";
5
4
  import ToolbarButton from "./ToolbarButton";
6
5
  export default function ActionGroup({ editor }) {
@@ -131,14 +130,38 @@ export default function ActionGroup({ editor }) {
131
130
  link.click();
132
131
  document.body.removeChild(link);
133
132
  };
134
- return (_jsxs("div", { className: "action-group", role: "group", "aria-label": "Editor actions", children: [_jsx(ToolbarButton, { icon: MdZoomIn, onClick: zoomIn, title: "Zoom In" }), _jsx(ToolbarButton, { icon: MdZoomOut, onClick: zoomOut, title: "Zoom Out" }), _jsx(ToolbarButton, { icon: MdPrint, onClick: handlePrint, title: "Print" }), _jsx(ToolbarButton, { icon: MdSave, onClick: handleSave, title: "Save" }), _jsxs("div", { className: "relative", ref: dropdownRef, children: [_jsxs("button", { type: "button", onClick: () => setDropdownOpen((open) => !open), "aria-haspopup": "menu", "aria-expanded": dropdownOpen ? "true" : "false", className: "export-button", title: "Export", children: [_jsx(MdDownload, {}), _jsx("span", { className: "text-sm" })] }), dropdownOpen && (_jsxs("div", { className: "export-dropdown", children: [_jsx("button", { type: "button", onClick: () => {
135
- setDropdownOpen(false);
136
- handleDownloadPDF();
137
- }, children: "Export as PDF" }), _jsx("button", { type: "button", onClick: () => {
138
- setDropdownOpen(false);
139
- handleDownloadHTML();
140
- }, children: "Export as HTML" }), _jsx("button", { type: "button", onClick: () => {
141
- setDropdownOpen(false);
142
- handleDownloadDOCX();
143
- }, children: "Export as DOCX" })] }))] })] }));
133
+ return (<div className="action-group" role="group" aria-label="Editor actions">
134
+ <ToolbarButton icon={MdZoomIn} onClick={zoomIn} title="Zoom In"/>
135
+ <ToolbarButton icon={MdZoomOut} onClick={zoomOut} title="Zoom Out"/>
136
+ <ToolbarButton icon={MdPrint} onClick={handlePrint} title="Print"/>
137
+ <ToolbarButton icon={MdSave} onClick={handleSave} title="Save"/>
138
+
139
+ <div className="relative" ref={dropdownRef}>
140
+ <button type="button" onClick={() => setDropdownOpen((open) => !open)} aria-haspopup="menu" aria-expanded={dropdownOpen ? "true" : "false"} className="export-button" title="Export">
141
+ <MdDownload />
142
+ <span className="text-sm"></span>
143
+ </button>
144
+
145
+ {dropdownOpen && (<div className="export-dropdown">
146
+ <button type="button" onClick={() => {
147
+ setDropdownOpen(false);
148
+ handleDownloadPDF();
149
+ }}>
150
+ Export as PDF
151
+ </button>
152
+ <button type="button" onClick={() => {
153
+ setDropdownOpen(false);
154
+ handleDownloadHTML();
155
+ }}>
156
+ Export as HTML
157
+ </button>
158
+ <button type="button" onClick={() => {
159
+ setDropdownOpen(false);
160
+ handleDownloadDOCX();
161
+ }}>
162
+ Export as DOCX
163
+ </button>
164
+ </div>)}
165
+ </div>
166
+ </div>);
144
167
  }
@@ -1,4 +1,5 @@
1
+ import React from "react";
1
2
  import { Editor } from "@tiptap/react";
2
3
  export default function ClipboardGroup({ editor }: {
3
4
  editor: Editor;
4
- }): import("react/jsx-runtime").JSX.Element;
5
+ }): React.JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { MdContentPaste, MdContentCut, MdContentCopy, MdFormatPaint, } from "react-icons/md";
2
+ import React from "react";
3
+ import ToolbarButton from "./ToolbarButton";
4
+ export default function ClipboardGroup({ editor }) {
5
+ return (<div className="clipboard-group">
6
+ <ToolbarButton icon={MdContentPaste} title="Paste" onClick={async () => {
7
+ try {
8
+ const text = await navigator.clipboard.readText();
9
+ editor.chain().focus().insertContent(text).run();
10
+ }
11
+ catch (error) {
12
+ console.error("Failed to read clipboard contents:", error);
13
+ }
14
+ }}/>
15
+ <ToolbarButton icon={MdContentCut} title="Cut" onClick={() => {
16
+ const { from, to } = editor.state.selection;
17
+ if (from === to)
18
+ return;
19
+ const selectedText = editor.state.doc.textBetween(from, to);
20
+ navigator.clipboard.writeText(selectedText).then(() => {
21
+ editor.chain().focus().deleteRange({ from, to }).run();
22
+ });
23
+ }}/>
24
+ <ToolbarButton icon={MdContentCopy} title="Copy" onClick={() => {
25
+ const { from, to } = editor.state.selection;
26
+ if (from === to)
27
+ return;
28
+ const selectedText = editor.state.doc.textBetween(from, to);
29
+ navigator.clipboard.writeText(selectedText);
30
+ }}/>
31
+ <ToolbarButton icon={MdFormatPaint} title="Format Painter" onClick={() => {
32
+ const currentMarks = editor.getAttributes("textStyle");
33
+ localStorage.setItem("formatPainter", JSON.stringify(currentMarks));
34
+ }}/>
35
+ </div>);
36
+ }
@@ -1,6 +1,7 @@
1
1
  import { Editor } from "@tiptap/react";
2
+ import React from "react";
2
3
  type FileGroupProps = {
3
4
  editor: Editor;
4
5
  };
5
- export default function FileGroup({ editor }: FileGroupProps): import("react/jsx-runtime").JSX.Element;
6
+ export default function FileGroup({ editor }: FileGroupProps): React.JSX.Element;
6
7
  export {};
@@ -1,9 +1,8 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import { FaRegFolderOpen } from "react-icons/fa";
4
3
  import { VscNewFile } from "react-icons/vsc";
5
4
  import ToolbarButton from "./ToolbarButton";
6
- import { useRef } from "react";
5
+ import React, { useRef } from "react";
7
6
  export default function FileGroup({ editor }) {
8
7
  const fileInputRef = useRef(null);
9
8
  const handleNew = () => {
@@ -33,5 +32,9 @@ export default function FileGroup({ editor }) {
33
32
  e.target.value = "";
34
33
  }
35
34
  };
36
- return (_jsxs("div", { className: "file-group", role: "group", "aria-label": "File actions", children: [_jsx("input", { type: "file", accept: ".json", ref: fileInputRef, onChange: handleFileChange, className: "hidden", "aria-label": "Open JSON file" }), _jsx(ToolbarButton, { icon: VscNewFile, onClick: handleNew, title: "New" }), _jsx(ToolbarButton, { icon: FaRegFolderOpen, onClick: handleOpen, title: "Open File" })] }));
35
+ return (<div className="file-group" 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
+ </div>);
37
40
  }
@@ -1,6 +1,7 @@
1
1
  import { Editor } from "@tiptap/react";
2
+ import React from "react";
2
3
  interface FontStyleGroupProps {
3
4
  editor: Editor;
4
5
  }
5
- export default function FontStyleGroup({ editor }: FontStyleGroupProps): import("react/jsx-runtime").JSX.Element;
6
+ export default function FontStyleGroup({ editor }: FontStyleGroupProps): React.JSX.Element;
6
7
  export {};
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import { MdFormatBold, MdFormatItalic, MdFormatUnderlined, MdStrikethroughS, MdSubscript, MdSuperscript, MdFormatClear, MdFormatPaint, } from "react-icons/md";
3
+ import { ImTextColor } from "react-icons/im";
4
+ import { BiSolidColorFill } from "react-icons/bi";
5
+ import ToolbarButton from "./ToolbarButton";
6
+ import React, { useEffect, useState } from "react";
7
+ export default function FontStyleGroup({ editor }) {
8
+ const [textColor, setTextColor] = useState("#000000");
9
+ const [highlightColor, setHighlightColor] = useState("#ffff00");
10
+ const [fontFamily, setFontFamily] = useState("Arial");
11
+ const [fontSize, setFontSize] = useState("16px");
12
+ useEffect(() => {
13
+ if (!editor)
14
+ return;
15
+ const updateStates = () => {
16
+ var _a, _b, _c;
17
+ const highlight = editor.getAttributes("highlight");
18
+ setHighlightColor((highlight === null || highlight === void 0 ? void 0 : highlight.color) || "#ffff00");
19
+ const color = (_a = editor.getAttributes("textStyle")) === null || _a === void 0 ? void 0 : _a.color;
20
+ setTextColor(color || "#000000");
21
+ const fontAttr = ((_b = editor.getAttributes("fontFamily")) === null || _b === void 0 ? void 0 : _b.font) || "Arial";
22
+ setFontFamily(fontAttr);
23
+ const sizeAttr = ((_c = editor.getAttributes("fontSize")) === null || _c === void 0 ? void 0 : _c.size) || "16px";
24
+ setFontSize(sizeAttr);
25
+ };
26
+ updateStates();
27
+ editor.on("selectionUpdate", updateStates);
28
+ editor.on("transaction", updateStates);
29
+ return () => {
30
+ editor.off("selectionUpdate", updateStates);
31
+ editor.off("transaction", updateStates);
32
+ };
33
+ }, [editor]);
34
+ return (<div className="font-style-group">
35
+ <select title="Font Family" value={fontFamily} onChange={(e) => {
36
+ const value = e.target.value;
37
+ setFontFamily(value);
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
+
47
+ <select title="Font Size" value={fontSize} onChange={(e) => {
48
+ const value = e.target.value;
49
+ setFontSize(value);
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="color-label" style={{ "--indicator-color": textColor }}>
71
+ <ImTextColor size={20}/>
72
+ <div className="color-indicator"/>
73
+ <input type="color" value={textColor} onChange={(e) => {
74
+ const color = e.target.value;
75
+ setTextColor(color);
76
+ editor.chain().focus().setColor(color).run();
77
+ }}/>
78
+ </label>
79
+
80
+ <label title="Highlight Color" aria-label="Highlight Color" className="color-label" style={{ "--indicator-color": highlightColor }}>
81
+ <BiSolidColorFill size={20}/>
82
+ <div className="color-indicator"/>
83
+ <input type="color" value={highlightColor} onChange={(e) => {
84
+ const color = e.target.value;
85
+ setHighlightColor(color);
86
+ editor.chain().focus().setHighlight({ color }).run();
87
+ }}/>
88
+ </label>
89
+
90
+ <ToolbarButton icon={MdFormatClear} label="Clear Formatting" onClick={() => editor.chain().focus().unsetAllMarks().run()}/>
91
+ <ToolbarButton icon={MdFormatPaint} label="Apply Painter Format" onClick={() => {
92
+ const format = JSON.parse(localStorage.getItem("formatPainter") || "{}");
93
+ if (format.color)
94
+ editor.chain().focus().setColor(format.color).run();
95
+ if (format.backgroundColor) {
96
+ editor
97
+ .chain()
98
+ .focus()
99
+ .setHighlight({ color: format.backgroundColor })
100
+ .run();
101
+ }
102
+ }}/>
103
+ </div>);
104
+ }
@@ -1,4 +1,5 @@
1
+ import React from "react";
1
2
  import { Editor } from "@tiptap/react";
2
3
  export default function InsertGroup({ editor }: {
3
4
  editor: Editor;
4
- }): import("react/jsx-runtime").JSX.Element;
5
+ }): React.JSX.Element;