tetrons 2.3.27 → 2.3.29

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 (60) hide show
  1. package/dist/app/api/ai-action/route.cjs +61 -0
  2. package/dist/app/api/ai-action/route.d.mts +9 -0
  3. package/dist/app/api/ai-action/route.d.ts +9 -0
  4. package/dist/app/api/ai-action/route.mjs +36 -0
  5. package/dist/app/api/export/route.cjs +33 -0
  6. package/dist/app/api/export/route.d.mts +3 -0
  7. package/dist/app/api/export/route.d.ts +3 -0
  8. package/dist/app/api/export/route.mjs +8 -0
  9. package/dist/app/api/register/route.cjs +120 -0
  10. package/dist/app/api/register/route.d.mts +10 -0
  11. package/dist/app/api/register/route.d.ts +10 -0
  12. package/dist/app/api/register/route.mjs +85 -0
  13. package/dist/app/api/save/route.cjs +53 -0
  14. package/dist/app/api/save/route.d.mts +9 -0
  15. package/dist/app/api/save/route.d.ts +9 -0
  16. package/dist/app/api/save/route.mjs +18 -0
  17. package/dist/app/api/transcribe/route.cjs +72 -0
  18. package/dist/app/api/transcribe/route.d.mts +10 -0
  19. package/dist/app/api/transcribe/route.d.ts +10 -0
  20. package/dist/app/api/transcribe/route.mjs +46 -0
  21. package/dist/app/api/validate/route.cjs +143 -0
  22. package/dist/app/api/validate/route.d.mts +13 -0
  23. package/dist/app/api/validate/route.d.ts +13 -0
  24. package/dist/app/api/validate/route.mjs +107 -0
  25. package/dist/index.d.ts +12 -6
  26. package/package.json +3 -2
  27. package/dist/app/page.d.ts +0 -2
  28. package/dist/components/components/UI/Button.tsx +0 -0
  29. package/dist/components/components/UI/Dropdown.tsx +0 -0
  30. package/dist/components/components/tetrons/EditorContent.tsx +0 -280
  31. package/dist/components/components/tetrons/ResizableImageComponent.tsx +0 -112
  32. package/dist/components/components/tetrons/ResizableVideoComponent.tsx +0 -56
  33. package/dist/components/tetrons/EditorContent.d.ts +0 -6
  34. package/dist/components/tetrons/ResizableImage.d.ts +0 -1
  35. package/dist/components/tetrons/ResizableImage.ts +0 -35
  36. package/dist/components/tetrons/ResizableImageComponent.d.ts +0 -4
  37. package/dist/components/tetrons/ResizableImageComponent.jsx +0 -73
  38. package/dist/components/tetrons/ResizableVideo.ts +0 -66
  39. package/dist/components/tetrons/extensions/Spellcheck.ts +0 -50
  40. package/dist/components/tetrons/helpers.ts +0 -0
  41. package/dist/components/tetrons/toolbar/AIGroup.tsx +0 -209
  42. package/dist/components/tetrons/toolbar/ActionGroup.tsx +0 -218
  43. package/dist/components/tetrons/toolbar/ClipboardGroup.tsx +0 -58
  44. package/dist/components/tetrons/toolbar/FileGroup.tsx +0 -66
  45. package/dist/components/tetrons/toolbar/FontStyleGroup.tsx +0 -194
  46. package/dist/components/tetrons/toolbar/InsertGroup.tsx +0 -267
  47. package/dist/components/tetrons/toolbar/ListAlignGroup.tsx +0 -69
  48. package/dist/components/tetrons/toolbar/MiscGroup.tsx +0 -104
  49. package/dist/components/tetrons/toolbar/TableContextMenu.tsx +0 -91
  50. package/dist/components/tetrons/toolbar/TetronsToolbar.tsx +0 -77
  51. package/dist/components/tetrons/toolbar/ToolbarButton.tsx +0 -36
  52. package/dist/components/tetrons/toolbar/extensions/Comment.ts +0 -72
  53. package/dist/components/tetrons/toolbar/extensions/Embed.ts +0 -113
  54. package/dist/components/tetrons/toolbar/extensions/FontFamily.ts +0 -43
  55. package/dist/components/tetrons/toolbar/extensions/FontSize.ts +0 -43
  56. package/dist/components/tetrons/toolbar/extensions/ResizableTable.ts +0 -16
  57. package/dist/components/tetrons/toolbar/marks/Subscript.ts +0 -45
  58. package/dist/components/tetrons/toolbar/marks/Superscript.ts +0 -45
  59. package/dist/styles/styles/tetrons.css +0 -563
  60. /package/dist/{index.js → index.cjs} +0 -0
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/app/api/validate/route.ts
31
+ var route_exports = {};
32
+ __export(route_exports, {
33
+ OPTIONS: () => OPTIONS,
34
+ POST: () => POST
35
+ });
36
+ module.exports = __toCommonJS(route_exports);
37
+ var import_server = require("next/server");
38
+
39
+ // src/lib/db.js
40
+ var import_mongoose = __toESM(require("mongoose"));
41
+ var MONGO_URL = process.env.MONGO_URL;
42
+ var connectDB = async () => {
43
+ if (import_mongoose.default.connection.readyState >= 1) {
44
+ return;
45
+ }
46
+ try {
47
+ await import_mongoose.default.connect(MONGO_URL);
48
+ console.log("Connected to MongoDB \u{1F44D}");
49
+ } catch (error) {
50
+ console.error("MongoDB connection failed \u274C", error);
51
+ process.exit(1);
52
+ }
53
+ };
54
+
55
+ // src/models/ApiKey.ts
56
+ var import_mongoose2 = __toESM(require("mongoose"));
57
+ var ApiKeySchema = new import_mongoose2.default.Schema({
58
+ email: { type: String, required: true },
59
+ organization: { type: String, required: true },
60
+ version: {
61
+ type: String,
62
+ enum: ["free", "pro", "premium", "platinum"],
63
+ required: true
64
+ },
65
+ apiKey: { type: String, required: true, unique: true },
66
+ createdAt: { type: Date, default: Date.now },
67
+ expiresAt: { type: Date }
68
+ });
69
+ var ApiKey = import_mongoose2.default.models.ApiKey || import_mongoose2.default.model("ApiKey", ApiKeySchema);
70
+
71
+ // src/app/api/validate/route.ts
72
+ var corsHeaders = {
73
+ "Access-Control-Allow-Origin": "*",
74
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
75
+ "Access-Control-Allow-Headers": "Content-Type"
76
+ };
77
+ async function OPTIONS() {
78
+ return new Response(null, {
79
+ status: 204,
80
+ headers: corsHeaders
81
+ });
82
+ }
83
+ async function POST(req) {
84
+ try {
85
+ const body = await req.json();
86
+ const apiKey = body.apiKey;
87
+ if (!apiKey) {
88
+ return import_server.NextResponse.json(
89
+ { error: "API key required" },
90
+ {
91
+ status: 400,
92
+ headers: corsHeaders
93
+ }
94
+ );
95
+ }
96
+ await connectDB();
97
+ const keyEntry = await ApiKey.findOne({ apiKey });
98
+ if (!keyEntry) {
99
+ return import_server.NextResponse.json(
100
+ { error: "Invalid API key" },
101
+ {
102
+ status: 401,
103
+ headers: corsHeaders
104
+ }
105
+ );
106
+ }
107
+ if (keyEntry.version === "free" && keyEntry.expiresAt && /* @__PURE__ */ new Date() > new Date(keyEntry.expiresAt)) {
108
+ return import_server.NextResponse.json(
109
+ { error: "Free trial expired" },
110
+ {
111
+ status: 403,
112
+ headers: corsHeaders
113
+ }
114
+ );
115
+ }
116
+ return import_server.NextResponse.json(
117
+ {
118
+ valid: true,
119
+ version: keyEntry.version,
120
+ email: keyEntry.email,
121
+ organization: keyEntry.organization
122
+ },
123
+ {
124
+ status: 200,
125
+ headers: corsHeaders
126
+ }
127
+ );
128
+ } catch (err) {
129
+ console.error("Validation Error:", err);
130
+ return import_server.NextResponse.json(
131
+ { error: "Server error" },
132
+ {
133
+ status: 500,
134
+ headers: corsHeaders
135
+ }
136
+ );
137
+ }
138
+ }
139
+ // Annotate the CommonJS export names for ESM import in node:
140
+ 0 && (module.exports = {
141
+ OPTIONS,
142
+ POST
143
+ });
@@ -0,0 +1,13 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ declare function OPTIONS(): Promise<Response>;
4
+ declare function POST(req: NextRequest): Promise<NextResponse<{
5
+ error: string;
6
+ }> | NextResponse<{
7
+ valid: boolean;
8
+ version: any;
9
+ email: any;
10
+ organization: any;
11
+ }>>;
12
+
13
+ export { OPTIONS, POST };
@@ -0,0 +1,13 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ declare function OPTIONS(): Promise<Response>;
4
+ declare function POST(req: NextRequest): Promise<NextResponse<{
5
+ error: string;
6
+ }> | NextResponse<{
7
+ valid: boolean;
8
+ version: any;
9
+ email: any;
10
+ organization: any;
11
+ }>>;
12
+
13
+ export { OPTIONS, POST };
@@ -0,0 +1,107 @@
1
+ // src/app/api/validate/route.ts
2
+ import { NextResponse } from "next/server";
3
+
4
+ // src/lib/db.js
5
+ import mongoose from "mongoose";
6
+ var MONGO_URL = process.env.MONGO_URL;
7
+ var connectDB = async () => {
8
+ if (mongoose.connection.readyState >= 1) {
9
+ return;
10
+ }
11
+ try {
12
+ await mongoose.connect(MONGO_URL);
13
+ console.log("Connected to MongoDB \u{1F44D}");
14
+ } catch (error) {
15
+ console.error("MongoDB connection failed \u274C", error);
16
+ process.exit(1);
17
+ }
18
+ };
19
+
20
+ // src/models/ApiKey.ts
21
+ import mongoose2 from "mongoose";
22
+ var ApiKeySchema = new mongoose2.Schema({
23
+ email: { type: String, required: true },
24
+ organization: { type: String, required: true },
25
+ version: {
26
+ type: String,
27
+ enum: ["free", "pro", "premium", "platinum"],
28
+ required: true
29
+ },
30
+ apiKey: { type: String, required: true, unique: true },
31
+ createdAt: { type: Date, default: Date.now },
32
+ expiresAt: { type: Date }
33
+ });
34
+ var ApiKey = mongoose2.models.ApiKey || mongoose2.model("ApiKey", ApiKeySchema);
35
+
36
+ // src/app/api/validate/route.ts
37
+ var corsHeaders = {
38
+ "Access-Control-Allow-Origin": "*",
39
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
40
+ "Access-Control-Allow-Headers": "Content-Type"
41
+ };
42
+ async function OPTIONS() {
43
+ return new Response(null, {
44
+ status: 204,
45
+ headers: corsHeaders
46
+ });
47
+ }
48
+ async function POST(req) {
49
+ try {
50
+ const body = await req.json();
51
+ const apiKey = body.apiKey;
52
+ if (!apiKey) {
53
+ return NextResponse.json(
54
+ { error: "API key required" },
55
+ {
56
+ status: 400,
57
+ headers: corsHeaders
58
+ }
59
+ );
60
+ }
61
+ await connectDB();
62
+ const keyEntry = await ApiKey.findOne({ apiKey });
63
+ if (!keyEntry) {
64
+ return NextResponse.json(
65
+ { error: "Invalid API key" },
66
+ {
67
+ status: 401,
68
+ headers: corsHeaders
69
+ }
70
+ );
71
+ }
72
+ if (keyEntry.version === "free" && keyEntry.expiresAt && /* @__PURE__ */ new Date() > new Date(keyEntry.expiresAt)) {
73
+ return NextResponse.json(
74
+ { error: "Free trial expired" },
75
+ {
76
+ status: 403,
77
+ headers: corsHeaders
78
+ }
79
+ );
80
+ }
81
+ return NextResponse.json(
82
+ {
83
+ valid: true,
84
+ version: keyEntry.version,
85
+ email: keyEntry.email,
86
+ organization: keyEntry.organization
87
+ },
88
+ {
89
+ status: 200,
90
+ headers: corsHeaders
91
+ }
92
+ );
93
+ } catch (err) {
94
+ console.error("Validation Error:", err);
95
+ return NextResponse.json(
96
+ { error: "Server error" },
97
+ {
98
+ status: 500,
99
+ headers: corsHeaders
100
+ }
101
+ );
102
+ }
103
+ }
104
+ export {
105
+ OPTIONS,
106
+ POST
107
+ };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,12 @@
1
- import EditorContent from "./components/tetrons/EditorContent";
2
- export declare function initializeTetrons(apiKey: string): Promise<void>;
3
- export declare function getTetronsVersion(): "" | "free" | "pro" | "premium" | "platinum";
4
- export declare function isApiKeyValid(): boolean;
5
- export { EditorContent };
6
- export default EditorContent;
1
+ import React from 'react';
2
+
3
+ type EditorContentProps = {
4
+ apiKey: string;
5
+ };
6
+ declare function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
7
+
8
+ declare function initializeTetrons(apiKey: string): Promise<void>;
9
+ declare function getTetronsVersion(): "" | "free" | "pro" | "premium" | "platinum";
10
+ declare function isApiKeyValid(): boolean;
11
+
12
+ export { EditorContent, EditorContent as default, getTetronsVersion, initializeTetrons, isApiKeyValid };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "tetrons",
3
- "version": "2.3.27",
3
+ "version": "2.3.29",
4
4
  "description": "A Next.js project written in TypeScript",
5
5
  "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
6
7
  "types": "dist/index.d.ts",
7
8
  "scripts": {
8
9
  "dev": "next dev --turbo",
9
- "build": "tsup src/index.ts --format esm,cjs --dts --out-dir dist && copyfiles -u 1 src/styles/*.css dist/styles && copyfiles -u 1 src/components/**/*.tsx dist/components",
10
+ "build": "tsup && copyfiles -u 2 src/styles/*.css dist/styles && copyfiles -u 2 src/components/**/*.tsx dist/components",
10
11
  "start": "next start",
11
12
  "lint": "next lint"
12
13
  },
@@ -1,2 +0,0 @@
1
- import "../styles/tetrons.css";
2
- export default function Home(): import("react").JSX.Element;
File without changes
File without changes
@@ -1,280 +0,0 @@
1
- "use client";
2
-
3
- import React, { useEffect, useRef, useState } from "react";
4
- import {
5
- useEditor,
6
- EditorContent as TiptapEditorContent,
7
- } from "@tiptap/react";
8
-
9
- import Document from "@tiptap/extension-document";
10
- import Paragraph from "@tiptap/extension-paragraph";
11
- import Text from "@tiptap/extension-text";
12
- import History from "@tiptap/extension-history";
13
- import Bold from "@tiptap/extension-bold";
14
- import Italic from "@tiptap/extension-italic";
15
- import Underline from "@tiptap/extension-underline";
16
- import Strike from "@tiptap/extension-strike";
17
- import Code from "@tiptap/extension-code";
18
- import Blockquote from "@tiptap/extension-blockquote";
19
- import HardBreak from "@tiptap/extension-hard-break";
20
- import Heading from "@tiptap/extension-heading";
21
- import HorizontalRule from "@tiptap/extension-horizontal-rule";
22
- import TextAlign from "@tiptap/extension-text-align";
23
- import Color from "@tiptap/extension-color";
24
- import Highlight from "@tiptap/extension-highlight";
25
- import Image from "@tiptap/extension-image";
26
- import Link from "@tiptap/extension-link";
27
- import TextStyle from "@tiptap/extension-text-style";
28
- import ListItem from "@tiptap/extension-list-item";
29
- import BulletList from "@tiptap/extension-bullet-list";
30
- import OrderedList from "@tiptap/extension-ordered-list";
31
- import TableRow from "@tiptap/extension-table-row";
32
- import TableCell from "@tiptap/extension-table-cell";
33
- import TableHeader from "@tiptap/extension-table-header";
34
- import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
35
-
36
- import js from "highlight.js/lib/languages/javascript";
37
- import ts from "highlight.js/lib/languages/typescript";
38
- import { createLowlight } from "lowlight";
39
-
40
- import { useTypo } from "../../utils/useTypo";
41
- import { Spellcheck } from "./extensions/Spellcheck";
42
-
43
- import { Comment } from "./toolbar/extensions/Comment";
44
- import { Subscript } from "./toolbar/marks/Subscript";
45
- import { Superscript } from "./toolbar/marks/Superscript";
46
- import { ResizableTable } from "./toolbar/extensions/ResizableTable";
47
- import { Embed } from "./toolbar/extensions/Embed";
48
- import { FontFamily } from "./toolbar/extensions/FontFamily";
49
- import { FontSize } from "./toolbar/extensions/FontSize";
50
- import { ResizableImage } from "./ResizableImage";
51
- import { ResizableVideo } from "./ResizableVideo";
52
- import TableContextMenu from "./toolbar/TableContextMenu";
53
- import TetronsToolbar from "./toolbar/TetronsToolbar";
54
-
55
- const lowlight = createLowlight();
56
- lowlight.register("js", js);
57
- lowlight.register("ts", ts);
58
-
59
- type EditorContentProps = {
60
- apiKey: string;
61
- };
62
-
63
- export default function EditorContent({ apiKey }: EditorContentProps) {
64
- const typo = useTypo();
65
-
66
- const [isValid, setIsValid] = useState<boolean | null>(null);
67
- const [error, setError] = useState<string | null>(null);
68
- const [versions, setVersions] = useState<string[]>([]);
69
- const [userVersion, setUserVersion] = useState<
70
- "free" | "pro" | "premium" | "platinum" | null
71
- >(null);
72
- const [currentVersionIndex, setCurrentVersionIndex] = useState<number | null>(
73
- null
74
- );
75
-
76
- const wrapperRef = useRef<HTMLDivElement>(null);
77
-
78
- function getApiBaseUrl(): string {
79
- if (
80
- typeof import.meta !== "undefined" &&
81
- import.meta.env?.VITE_TETRONS_API_URL
82
- ) {
83
- return import.meta.env.VITE_TETRONS_API_URL;
84
- }
85
- if (
86
- typeof process !== "undefined" &&
87
- process.env?.NEXT_PUBLIC_TETRONS_API_URL
88
- ) {
89
- return process.env.NEXT_PUBLIC_TETRONS_API_URL;
90
- }
91
- return "https://staging.tetrons.com";
92
- }
93
-
94
- const API_BASE_URL = getApiBaseUrl();
95
-
96
- useEffect(() => {
97
- const validateKey = async () => {
98
- try {
99
- const res = await fetch(`${API_BASE_URL}/api/validate`, {
100
- method: "POST",
101
- headers: {
102
- "Content-Type": "application/json",
103
- },
104
- body: JSON.stringify({ apiKey }),
105
- });
106
-
107
- const data = await res.json();
108
- if (!res.ok) throw new Error(data.error || "Invalid API Key");
109
-
110
- setIsValid(true);
111
- setUserVersion(data.version);
112
- } catch (err: unknown) {
113
- setError(err instanceof Error ? err.message : "Invalid API Key");
114
- setIsValid(false);
115
- }
116
- };
117
-
118
- validateKey();
119
- }, [apiKey, API_BASE_URL]);
120
-
121
- const editor = useEditor({
122
- extensions: [
123
- Document,
124
- Paragraph,
125
- Text,
126
- History,
127
- Bold,
128
- Italic,
129
- Underline,
130
- Strike,
131
- Code,
132
- Blockquote,
133
- HardBreak,
134
- Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
135
- HorizontalRule,
136
- TextStyle,
137
- Color,
138
- Highlight.configure({ multicolor: true }),
139
- FontFamily,
140
- FontSize,
141
- TextAlign.configure({ types: ["heading", "paragraph"] }),
142
- ListItem,
143
- BulletList,
144
- OrderedList,
145
- Subscript,
146
- Superscript,
147
- Image,
148
- Link.configure({
149
- openOnClick: false,
150
- autolink: true,
151
- linkOnPaste: true,
152
- }),
153
- ResizableTable.configure({ resizable: true }),
154
- TableRow,
155
- TableCell,
156
- TableHeader,
157
- Embed,
158
- ResizableImage,
159
- ResizableVideo,
160
- Comment,
161
- CodeBlockLowlight.configure({
162
- lowlight,
163
- HTMLAttributes: {
164
- class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
165
- },
166
- }),
167
- ...(typo
168
- ? [
169
- Spellcheck.configure({
170
- spellcheckFn: (word: string) => typo.check(word),
171
- }),
172
- ]
173
- : []),
174
- ],
175
- content: "",
176
- editorProps: {
177
- attributes: {
178
- class: "min-h-full focus:outline-none p-0",
179
- "data-placeholder": "Start typing here...",
180
- },
181
- },
182
- immediatelyRender: false,
183
- });
184
-
185
- useEffect(() => {
186
- return () => {
187
- editor?.destroy();
188
- };
189
- }, [editor]);
190
-
191
- const handleEditorClick = () => {
192
- if (editor && !editor.isFocused) {
193
- editor.commands.focus();
194
- }
195
- };
196
-
197
- const saveVersion = () => {
198
- if (!editor) return;
199
- const content = editor.getJSON();
200
- setVersions((prev) => [...prev, JSON.stringify(content)]);
201
- setCurrentVersionIndex(versions.length);
202
- };
203
-
204
- const restoreVersion = (index: number) => {
205
- if (!editor) return;
206
- const versionContent = versions[index];
207
- if (versionContent) {
208
- editor.commands.setContent(JSON.parse(versionContent));
209
- setCurrentVersionIndex(index);
210
- }
211
- };
212
-
213
- if (isValid === false) {
214
- return <div className="editor-error">⚠️ {error}</div>;
215
- }
216
-
217
- if (isValid === null) {
218
- return <div className="editor-loading">🔍 Validating license...</div>;
219
- }
220
-
221
- if (!typo) {
222
- return <div className="editor-loading">📖 Loading dictionary...</div>;
223
- }
224
-
225
- return (
226
- <div className="editor-container">
227
- {userVersion !== "free" && (
228
- <div className="editor-toolbar">
229
- <button
230
- type="button"
231
- onClick={saveVersion}
232
- disabled={!editor}
233
- className="editor-save-btn"
234
- >
235
- Save Version
236
- </button>
237
-
238
- <div className="editor-versions-wrapper">
239
- {versions.length === 0 ? (
240
- <span className="editor-no-versions">No saved versions</span>
241
- ) : (
242
- versions.map((_, idx) => (
243
- <button
244
- key={idx}
245
- type="button"
246
- onClick={() => restoreVersion(idx)}
247
- className={`editor-version-btn ${
248
- idx === currentVersionIndex ? "active" : ""
249
- }`}
250
- title={`Restore Version ${idx + 1}`}
251
- >
252
- {`V${idx + 1}`}
253
- </button>
254
- ))
255
- )}
256
- </div>
257
- </div>
258
- )}
259
-
260
- {editor && userVersion && (
261
- <TetronsToolbar editor={editor} version={userVersion} />
262
- )}
263
-
264
- <div
265
- ref={wrapperRef}
266
- className="editor-content-wrapper"
267
- onClick={handleEditorClick}
268
- >
269
- {editor ? (
270
- <>
271
- <TiptapEditorContent editor={editor} />
272
- <TableContextMenu editor={editor} />
273
- </>
274
- ) : (
275
- <div className="editor-loading">Loading editor...</div>
276
- )}
277
- </div>
278
- </div>
279
- );
280
- }