tetrons 2.3.1 → 2.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/components/tetrons/EditorContent.tsx +282 -0
- package/dist/components/components/tetrons/ResizableImageComponent.tsx +77 -0
- package/dist/components/components/tetrons/ResizableVideoComponent.tsx +56 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +53 -11
- package/package.json +9 -9
- package/dist/app/api/export/route.d.ts +0 -1
- package/dist/app/api/export/route.js +0 -4
- package/dist/app/api/register/route.d.ts +0 -7
- package/dist/app/api/register/route.js +0 -32
- package/dist/app/api/save/route.d.ts +0 -6
- package/dist/app/api/save/route.js +0 -15
- package/dist/app/api/validate/route.d.ts +0 -10
- package/dist/app/api/validate/route.js +0 -58
- package/dist/app/layout.d.ts +0 -6
- package/dist/app/layout.jsx +0 -30
- package/dist/app/page.d.ts +0 -2
- package/dist/app/page.jsx +0 -51
- package/dist/components/UI/Button.jsx +0 -1
- package/dist/components/UI/Dropdown.jsx +0 -1
- package/dist/components/tetrons/EditorContent.d.ts +0 -6
- package/dist/components/tetrons/EditorContent.jsx +0 -209
- package/dist/components/tetrons/ResizableImage.d.ts +0 -1
- package/dist/components/tetrons/ResizableImage.js +0 -40
- package/dist/components/tetrons/ResizableImageComponent.d.ts +0 -11
- package/dist/components/tetrons/ResizableImageComponent.jsx +0 -37
- package/dist/components/tetrons/ResizableVideo.d.ts +0 -12
- package/dist/components/tetrons/ResizableVideo.js +0 -61
- package/dist/components/tetrons/ResizableVideoComponent.d.ts +0 -4
- package/dist/components/tetrons/ResizableVideoComponent.jsx +0 -32
- package/dist/components/tetrons/helpers.d.ts +0 -0
- package/dist/components/tetrons/helpers.js +0 -1
- package/dist/components/tetrons/toolbar/ActionGroup.d.ts +0 -7
- package/dist/components/tetrons/toolbar/ActionGroup.jsx +0 -167
- package/dist/components/tetrons/toolbar/ClipboardGroup.d.ts +0 -5
- package/dist/components/tetrons/toolbar/ClipboardGroup.jsx +0 -36
- package/dist/components/tetrons/toolbar/FileGroup.d.ts +0 -7
- package/dist/components/tetrons/toolbar/FileGroup.jsx +0 -40
- package/dist/components/tetrons/toolbar/FontStyleGroup.d.ts +0 -7
- package/dist/components/tetrons/toolbar/FontStyleGroup.jsx +0 -104
- package/dist/components/tetrons/toolbar/InsertGroup.d.ts +0 -5
- package/dist/components/tetrons/toolbar/InsertGroup.jsx +0 -163
- package/dist/components/tetrons/toolbar/ListAlignGroup.d.ts +0 -5
- package/dist/components/tetrons/toolbar/ListAlignGroup.jsx +0 -16
- package/dist/components/tetrons/toolbar/MiscGroup.d.ts +0 -7
- package/dist/components/tetrons/toolbar/MiscGroup.jsx +0 -31
- package/dist/components/tetrons/toolbar/TableContextMenu.d.ts +0 -7
- package/dist/components/tetrons/toolbar/TableContextMenu.jsx +0 -52
- package/dist/components/tetrons/toolbar/TetronsToolbar.d.ts +0 -6
- package/dist/components/tetrons/toolbar/TetronsToolbar.jsx +0 -46
- package/dist/components/tetrons/toolbar/ToolbarButton.d.ts +0 -12
- package/dist/components/tetrons/toolbar/ToolbarButton.jsx +0 -8
- package/dist/components/tetrons/toolbar/extensions/Comment.d.ts +0 -17
- package/dist/components/tetrons/toolbar/extensions/Comment.js +0 -45
- package/dist/components/tetrons/toolbar/extensions/Embed.d.ts +0 -2
- package/dist/components/tetrons/toolbar/extensions/Embed.js +0 -90
- package/dist/components/tetrons/toolbar/extensions/FontFamily.d.ts +0 -9
- package/dist/components/tetrons/toolbar/extensions/FontFamily.js +0 -28
- package/dist/components/tetrons/toolbar/extensions/FontSize.d.ts +0 -9
- package/dist/components/tetrons/toolbar/extensions/FontSize.js +0 -28
- package/dist/components/tetrons/toolbar/extensions/ResizableTable.d.ts +0 -1
- package/dist/components/tetrons/toolbar/extensions/ResizableTable.js +0 -11
- package/dist/components/tetrons/toolbar/marks/Subscript.d.ts +0 -2
- package/dist/components/tetrons/toolbar/marks/Subscript.js +0 -35
- package/dist/components/tetrons/toolbar/marks/Superscript.d.ts +0 -2
- package/dist/components/tetrons/toolbar/marks/Superscript.js +0 -35
- package/dist/lib/db.d.ts +0 -1
- package/dist/lib/db.js +0 -15
- package/dist/lib/export.d.ts +0 -0
- package/dist/lib/export.js +0 -1
- package/dist/lib/tiptap-extensions.d.ts +0 -0
- package/dist/lib/tiptap-extensions.js +0 -1
- package/dist/models/ApiKey.d.ts +0 -2
- package/dist/models/ApiKey.js +0 -14
- package/dist/utils/apiKeyUtils.d.ts +0 -11
- package/dist/utils/apiKeyUtils.js +0 -33
- package/dist/utils/loadEmojiPicker.d.ts +0 -1
- package/dist/utils/loadEmojiPicker.js +0 -12
- /package/dist/components/{UI/Button.d.ts → components/UI/Button.tsx} +0 -0
- /package/dist/components/{UI/Dropdown.d.ts → components/UI/Dropdown.tsx} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Comment } from "./toolbar/extensions/Comment";
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
import {
|
|
7
|
+
useEditor,
|
|
8
|
+
EditorContent as TiptapEditorContent,
|
|
9
|
+
Editor,
|
|
10
|
+
} from "@tiptap/react";
|
|
11
|
+
|
|
12
|
+
import Document from "@tiptap/extension-document";
|
|
13
|
+
import Paragraph from "@tiptap/extension-paragraph";
|
|
14
|
+
import Text from "@tiptap/extension-text";
|
|
15
|
+
import History from "@tiptap/extension-history";
|
|
16
|
+
import Bold from "@tiptap/extension-bold";
|
|
17
|
+
import Italic from "@tiptap/extension-italic";
|
|
18
|
+
import Underline from "@tiptap/extension-underline";
|
|
19
|
+
import Strike from "@tiptap/extension-strike";
|
|
20
|
+
import Code from "@tiptap/extension-code";
|
|
21
|
+
import Blockquote from "@tiptap/extension-blockquote";
|
|
22
|
+
import HardBreak from "@tiptap/extension-hard-break";
|
|
23
|
+
import Heading from "@tiptap/extension-heading";
|
|
24
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
|
25
|
+
|
|
26
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
27
|
+
import Color from "@tiptap/extension-color";
|
|
28
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
29
|
+
import Image from "@tiptap/extension-image";
|
|
30
|
+
import Link from "@tiptap/extension-link";
|
|
31
|
+
import TextStyle from "@tiptap/extension-text-style";
|
|
32
|
+
|
|
33
|
+
import ListItem from "@tiptap/extension-list-item";
|
|
34
|
+
import BulletList from "@tiptap/extension-bullet-list";
|
|
35
|
+
import OrderedList from "@tiptap/extension-ordered-list";
|
|
36
|
+
import { Subscript } from "./toolbar/marks/Subscript";
|
|
37
|
+
import { Superscript } from "./toolbar/marks/Superscript";
|
|
38
|
+
|
|
39
|
+
import { ResizableTable } from "./toolbar/extensions/ResizableTable";
|
|
40
|
+
import { Embed } from "./toolbar/extensions/Embed";
|
|
41
|
+
import TableRow from "@tiptap/extension-table-row";
|
|
42
|
+
import TableCell from "@tiptap/extension-table-cell";
|
|
43
|
+
import TableHeader from "@tiptap/extension-table-header";
|
|
44
|
+
|
|
45
|
+
import { FontFamily } from "./toolbar/extensions/FontFamily";
|
|
46
|
+
import { FontSize } from "./toolbar/extensions/FontSize";
|
|
47
|
+
import TetronsToolbar from "./toolbar/TetronsToolbar";
|
|
48
|
+
|
|
49
|
+
import { ResizableImage } from "./ResizableImage";
|
|
50
|
+
import { ResizableVideo } from "./ResizableVideo";
|
|
51
|
+
import TableContextMenu from "./toolbar/TableContextMenu";
|
|
52
|
+
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
|
53
|
+
import { createLowlight } from "lowlight";
|
|
54
|
+
|
|
55
|
+
import js from "highlight.js/lib/languages/javascript";
|
|
56
|
+
import ts from "highlight.js/lib/languages/typescript";
|
|
57
|
+
|
|
58
|
+
const lowlight = createLowlight();
|
|
59
|
+
|
|
60
|
+
lowlight.register("js", js);
|
|
61
|
+
lowlight.register("ts", ts);
|
|
62
|
+
|
|
63
|
+
type EditorContentProps = {
|
|
64
|
+
apiKey: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default function EditorContent({ apiKey }: EditorContentProps) {
|
|
68
|
+
const [isValid, setIsValid] = React.useState<boolean | null>(null);
|
|
69
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
70
|
+
const [versions, setVersions] = React.useState<string[]>([]);
|
|
71
|
+
const [userVersion, setUserVersion] = React.useState<
|
|
72
|
+
"free" | "pro" | "premium" | "platinum" | null
|
|
73
|
+
>(null);
|
|
74
|
+
|
|
75
|
+
const [currentVersionIndex, setCurrentVersionIndex] = React.useState<
|
|
76
|
+
number | null
|
|
77
|
+
>(null);
|
|
78
|
+
|
|
79
|
+
function getApiBaseUrl(): string {
|
|
80
|
+
if (
|
|
81
|
+
typeof import.meta !== "undefined" &&
|
|
82
|
+
import.meta.env?.VITE_TETRONS_API_URL
|
|
83
|
+
) {
|
|
84
|
+
return import.meta.env.VITE_TETRONS_API_URL;
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
typeof process !== "undefined" &&
|
|
88
|
+
process.env?.NEXT_PUBLIC_TETRONS_API_URL
|
|
89
|
+
) {
|
|
90
|
+
return process.env.NEXT_PUBLIC_TETRONS_API_URL;
|
|
91
|
+
}
|
|
92
|
+
return "https://staging.tetrons.com";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const API_BASE_URL = getApiBaseUrl();
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const validateKey = async () => {
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetch(`${API_BASE_URL}/api/validate`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify({ apiKey }),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
if (!res.ok) throw new Error(data.error || "Invalid API Key");
|
|
110
|
+
|
|
111
|
+
setIsValid(true);
|
|
112
|
+
setUserVersion(data.version);
|
|
113
|
+
} catch (err: unknown) {
|
|
114
|
+
if (err instanceof Error) {
|
|
115
|
+
setError(err.message || "Invalid API Key");
|
|
116
|
+
} else {
|
|
117
|
+
setError("Invalid API Key");
|
|
118
|
+
}
|
|
119
|
+
setIsValid(false);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
validateKey();
|
|
124
|
+
}, [apiKey]);
|
|
125
|
+
|
|
126
|
+
const editor: Editor | null = useEditor({
|
|
127
|
+
extensions: [
|
|
128
|
+
Document,
|
|
129
|
+
Paragraph,
|
|
130
|
+
Text,
|
|
131
|
+
History,
|
|
132
|
+
Bold,
|
|
133
|
+
Italic,
|
|
134
|
+
Underline,
|
|
135
|
+
Strike,
|
|
136
|
+
Code,
|
|
137
|
+
Blockquote,
|
|
138
|
+
HardBreak,
|
|
139
|
+
Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
|
|
140
|
+
HorizontalRule,
|
|
141
|
+
|
|
142
|
+
TextStyle,
|
|
143
|
+
Color,
|
|
144
|
+
Highlight.configure({ multicolor: true }),
|
|
145
|
+
FontFamily,
|
|
146
|
+
FontSize,
|
|
147
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
148
|
+
|
|
149
|
+
ListItem,
|
|
150
|
+
BulletList,
|
|
151
|
+
OrderedList,
|
|
152
|
+
Subscript,
|
|
153
|
+
Superscript,
|
|
154
|
+
|
|
155
|
+
Image,
|
|
156
|
+
Link.configure({
|
|
157
|
+
openOnClick: false,
|
|
158
|
+
autolink: true,
|
|
159
|
+
linkOnPaste: true,
|
|
160
|
+
}),
|
|
161
|
+
|
|
162
|
+
ResizableTable.configure({
|
|
163
|
+
resizable: true,
|
|
164
|
+
}),
|
|
165
|
+
TableRow,
|
|
166
|
+
TableCell,
|
|
167
|
+
TableHeader,
|
|
168
|
+
Embed,
|
|
169
|
+
|
|
170
|
+
ResizableImage,
|
|
171
|
+
ResizableVideo,
|
|
172
|
+
Comment,
|
|
173
|
+
CodeBlockLowlight.configure({
|
|
174
|
+
lowlight,
|
|
175
|
+
HTMLAttributes: {
|
|
176
|
+
class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
],
|
|
180
|
+
content: "",
|
|
181
|
+
editorProps: {
|
|
182
|
+
attributes: {
|
|
183
|
+
class: "min-h-full focus:outline-none p-0",
|
|
184
|
+
"data-placeholder": "Start typing here...",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
immediatelyRender: false,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
191
|
+
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
return () => {
|
|
194
|
+
editor?.destroy();
|
|
195
|
+
};
|
|
196
|
+
}, [editor]);
|
|
197
|
+
|
|
198
|
+
const handleEditorClick = () => {
|
|
199
|
+
if (editor && !editor.isFocused) {
|
|
200
|
+
editor.commands.focus();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const saveVersion = () => {
|
|
205
|
+
if (!editor) return;
|
|
206
|
+
const content = editor.getJSON();
|
|
207
|
+
setVersions((prev) => [...prev, JSON.stringify(content)]);
|
|
208
|
+
setCurrentVersionIndex(versions.length);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const restoreVersion = (index: number) => {
|
|
212
|
+
if (!editor) return;
|
|
213
|
+
const versionContent = versions[index];
|
|
214
|
+
if (versionContent) {
|
|
215
|
+
editor.commands.setContent(JSON.parse(versionContent));
|
|
216
|
+
setCurrentVersionIndex(index);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
if (isValid === false) {
|
|
221
|
+
return <div className="editor-error">⚠️ {error}</div>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (isValid === null) {
|
|
225
|
+
return <div className="editor-loading">🔍 Validating license...</div>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<div className="editor-container">
|
|
230
|
+
{userVersion !== "free" && (
|
|
231
|
+
<div className="editor-toolbar">
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
onClick={saveVersion}
|
|
235
|
+
disabled={!editor}
|
|
236
|
+
className="editor-save-btn"
|
|
237
|
+
>
|
|
238
|
+
Save Version
|
|
239
|
+
</button>
|
|
240
|
+
|
|
241
|
+
<div className="editor-versions-wrapper">
|
|
242
|
+
{versions.length === 0 && (
|
|
243
|
+
<span className="editor-no-versions">No saved versions</span>
|
|
244
|
+
)}
|
|
245
|
+
{versions.map((_, idx) => (
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
key={idx}
|
|
249
|
+
onClick={() => restoreVersion(idx)}
|
|
250
|
+
className={`editor-version-btn ${
|
|
251
|
+
idx === currentVersionIndex ? "active" : ""
|
|
252
|
+
}`}
|
|
253
|
+
title={`Restore Version ${idx + 1}`}
|
|
254
|
+
>
|
|
255
|
+
{`V${idx + 1}`}
|
|
256
|
+
</button>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{editor && userVersion && (
|
|
263
|
+
<TetronsToolbar editor={editor} version={userVersion} />
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
<div
|
|
267
|
+
ref={wrapperRef}
|
|
268
|
+
className="editor-content-wrapper"
|
|
269
|
+
onClick={handleEditorClick}
|
|
270
|
+
>
|
|
271
|
+
{editor ? (
|
|
272
|
+
<>
|
|
273
|
+
<TiptapEditorContent editor={editor} />
|
|
274
|
+
{editor && <TableContextMenu editor={editor} />}
|
|
275
|
+
</>
|
|
276
|
+
) : (
|
|
277
|
+
<div className="editor-loading">Loading editor...</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from "react";
|
|
2
|
+
import { NodeViewWrapper, NodeViewRendererProps } from "@tiptap/react";
|
|
3
|
+
|
|
4
|
+
interface ResizableImageProps extends NodeViewRendererProps {
|
|
5
|
+
updateAttributes: (attrs: {
|
|
6
|
+
width?: number | null;
|
|
7
|
+
height?: number | null;
|
|
8
|
+
}) => void;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ResizableImageComponent: React.FC<ResizableImageProps> = ({
|
|
13
|
+
node,
|
|
14
|
+
updateAttributes,
|
|
15
|
+
selected,
|
|
16
|
+
}) => {
|
|
17
|
+
const { src, alt, title, width, height } = node.attrs as {
|
|
18
|
+
src: string;
|
|
19
|
+
alt?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
width?: number | null;
|
|
22
|
+
height?: number | null;
|
|
23
|
+
};
|
|
24
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
const imgRef = useRef<HTMLImageElement>(null);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const img = imgRef.current;
|
|
29
|
+
if (!img) return;
|
|
30
|
+
|
|
31
|
+
const observer = new ResizeObserver(() => {
|
|
32
|
+
const w = Math.round(img.offsetWidth);
|
|
33
|
+
const h = Math.round(img.offsetHeight);
|
|
34
|
+
updateAttributes({ width: w, height: h });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
observer.observe(img);
|
|
38
|
+
return () => observer.disconnect();
|
|
39
|
+
}, [updateAttributes]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<NodeViewWrapper
|
|
43
|
+
ref={wrapperRef}
|
|
44
|
+
contentEditable={false}
|
|
45
|
+
className={`resizable-image-wrapper ${
|
|
46
|
+
selected ? "ProseMirror-selectednode" : ""
|
|
47
|
+
}`}
|
|
48
|
+
style={{
|
|
49
|
+
resize: "both",
|
|
50
|
+
overflow: "auto",
|
|
51
|
+
border: "1px solid #ccc",
|
|
52
|
+
padding: 2,
|
|
53
|
+
display: "inline-block",
|
|
54
|
+
maxWidth: "100%",
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
58
|
+
<img
|
|
59
|
+
ref={imgRef}
|
|
60
|
+
src={src}
|
|
61
|
+
alt={alt ?? ""}
|
|
62
|
+
title={title ?? ""}
|
|
63
|
+
loading="lazy"
|
|
64
|
+
style={{
|
|
65
|
+
width: width ? `${width}px` : "auto",
|
|
66
|
+
height: height ? `${height}px` : "auto",
|
|
67
|
+
display: "block",
|
|
68
|
+
userSelect: "none",
|
|
69
|
+
pointerEvents: "auto",
|
|
70
|
+
}}
|
|
71
|
+
draggable={false}
|
|
72
|
+
/>
|
|
73
|
+
</NodeViewWrapper>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default ResizableImageComponent;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from "react";
|
|
2
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
3
|
+
import type { NodeViewProps } from "@tiptap/core";
|
|
4
|
+
|
|
5
|
+
const ResizableVideoComponent: React.FC<NodeViewProps> = ({
|
|
6
|
+
node,
|
|
7
|
+
updateAttributes,
|
|
8
|
+
selected,
|
|
9
|
+
}) => {
|
|
10
|
+
const { src, controls, width, height } = node.attrs;
|
|
11
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const video = videoRef.current;
|
|
16
|
+
if (!video) return;
|
|
17
|
+
|
|
18
|
+
const observer = new ResizeObserver(() => {
|
|
19
|
+
const w = Math.round(video.offsetWidth);
|
|
20
|
+
const h = Math.round(video.offsetHeight);
|
|
21
|
+
updateAttributes({ width: w, height: h });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
observer.observe(video);
|
|
25
|
+
return () => observer.disconnect();
|
|
26
|
+
}, [updateAttributes]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<NodeViewWrapper
|
|
30
|
+
ref={wrapperRef}
|
|
31
|
+
contentEditable={false}
|
|
32
|
+
className={`resizable-video-wrapper ${
|
|
33
|
+
selected ? "ProseMirror-selectednode" : ""
|
|
34
|
+
}`}
|
|
35
|
+
style={{
|
|
36
|
+
resize: "both",
|
|
37
|
+
overflow: "auto",
|
|
38
|
+
border: "1px solid #ccc",
|
|
39
|
+
padding: "2px",
|
|
40
|
+
display: "inline-block",
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<video
|
|
44
|
+
ref={videoRef}
|
|
45
|
+
src={src}
|
|
46
|
+
controls={controls}
|
|
47
|
+
style={{
|
|
48
|
+
width: width ? `${width}px` : "auto",
|
|
49
|
+
height: height ? `${height}px` : "auto",
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
</NodeViewWrapper>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default ResizableVideoComponent;
|
package/dist/index.d.mts
CHANGED
|
@@ -5,4 +5,8 @@ type EditorContentProps = {
|
|
|
5
5
|
};
|
|
6
6
|
declare function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
|
|
7
7
|
|
|
8
|
-
|
|
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/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import EditorContent from "./components/tetrons/EditorContent";
|
|
2
|
-
import "./styles/tetrons.css";
|
|
3
2
|
export declare function initializeTetrons(apiKey: string): Promise<void>;
|
|
4
3
|
export declare function getTetronsVersion(): "" | "free" | "pro" | "premium" | "platinum";
|
|
5
4
|
export declare function isApiKeyValid(): boolean;
|
package/dist/index.js
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -14345,7 +14345,7 @@ var FontSize = Mark2.create({
|
|
|
14345
14345
|
});
|
|
14346
14346
|
|
|
14347
14347
|
// src/components/tetrons/toolbar/TetronsToolbar.tsx
|
|
14348
|
-
import React9, { useEffect as useEffect3 } from "react";
|
|
14348
|
+
import React9, { useEffect as useEffect3, useState as useState4 } from "react";
|
|
14349
14349
|
|
|
14350
14350
|
// src/components/tetrons/toolbar/ActionGroup.tsx
|
|
14351
14351
|
import React2, { useEffect, useRef, useState } from "react";
|
|
@@ -15233,8 +15233,11 @@ function FileGroup({ editor }) {
|
|
|
15233
15233
|
}
|
|
15234
15234
|
|
|
15235
15235
|
// src/components/tetrons/toolbar/TetronsToolbar.tsx
|
|
15236
|
-
function TetronsToolbar({
|
|
15237
|
-
|
|
15236
|
+
function TetronsToolbar({
|
|
15237
|
+
editor,
|
|
15238
|
+
version
|
|
15239
|
+
}) {
|
|
15240
|
+
const [autoSave, setAutoSave] = useState4(false);
|
|
15238
15241
|
useEffect3(() => {
|
|
15239
15242
|
if (!editor) return;
|
|
15240
15243
|
const handleUpdate = () => {
|
|
@@ -15251,7 +15254,7 @@ function TetronsToolbar({ editor }) {
|
|
|
15251
15254
|
editor.off("update", handleUpdate);
|
|
15252
15255
|
};
|
|
15253
15256
|
}, [autoSave, editor]);
|
|
15254
|
-
return /* @__PURE__ */ React9.createElement("div", { className: "tetrons-toolbar" }, /* @__PURE__ */ React9.createElement("div", { className: "group" }, /* @__PURE__ */ React9.createElement(
|
|
15257
|
+
return /* @__PURE__ */ React9.createElement("div", { className: "tetrons-toolbar" }, version !== "free" && /* @__PURE__ */ React9.createElement("div", { className: "group" }, /* @__PURE__ */ React9.createElement(
|
|
15255
15258
|
"input",
|
|
15256
15259
|
{
|
|
15257
15260
|
type: "checkbox",
|
|
@@ -15259,7 +15262,7 @@ function TetronsToolbar({ editor }) {
|
|
|
15259
15262
|
checked: autoSave,
|
|
15260
15263
|
onChange: (e) => setAutoSave(e.target.checked)
|
|
15261
15264
|
}
|
|
15262
|
-
), /* @__PURE__ */ React9.createElement("label", { htmlFor: "autoSave" }, "Auto Save")), /* @__PURE__ */ React9.createElement(FileGroup, { editor }), /* @__PURE__ */ React9.createElement(ClipboardGroup, { editor }), /* @__PURE__ */ React9.createElement(FontStyleGroup, { editor }), /* @__PURE__ */ React9.createElement(ListAlignGroup, { editor }), /* @__PURE__ */ React9.createElement(InsertGroup, { editor }), /* @__PURE__ */ React9.createElement(
|
|
15265
|
+
), /* @__PURE__ */ React9.createElement("label", { htmlFor: "autoSave" }, "Auto Save")), ["pro", "premium", "platinum"].includes(version) && /* @__PURE__ */ React9.createElement(FileGroup, { editor }), /* @__PURE__ */ React9.createElement(ClipboardGroup, { editor }), /* @__PURE__ */ React9.createElement(FontStyleGroup, { editor }), /* @__PURE__ */ React9.createElement(ListAlignGroup, { editor }), ["premium", "platinum"].includes(version) && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(InsertGroup, { editor }), /* @__PURE__ */ React9.createElement(ActionGroup, { editor })), version === "platinum" && /* @__PURE__ */ React9.createElement(MiscGroup, { editor }));
|
|
15263
15266
|
}
|
|
15264
15267
|
|
|
15265
15268
|
// src/components/tetrons/ResizableImage.ts
|
|
@@ -15460,9 +15463,9 @@ var ResizableVideo = Node2.create({
|
|
|
15460
15463
|
});
|
|
15461
15464
|
|
|
15462
15465
|
// src/components/tetrons/toolbar/TableContextMenu.tsx
|
|
15463
|
-
import React12, { useEffect as useEffect6, useState as
|
|
15466
|
+
import React12, { useEffect as useEffect6, useState as useState5 } from "react";
|
|
15464
15467
|
function TableContextMenu({ editor }) {
|
|
15465
|
-
const [menuPosition, setMenuPosition] =
|
|
15468
|
+
const [menuPosition, setMenuPosition] = useState5(null);
|
|
15466
15469
|
useEffect6(() => {
|
|
15467
15470
|
const handleContextMenu = (event) => {
|
|
15468
15471
|
const target = event.target;
|
|
@@ -17072,11 +17075,22 @@ function EditorContent({ apiKey }) {
|
|
|
17072
17075
|
const [isValid, setIsValid] = React13.useState(null);
|
|
17073
17076
|
const [error, setError] = React13.useState(null);
|
|
17074
17077
|
const [versions, setVersions] = React13.useState([]);
|
|
17078
|
+
const [userVersion, setUserVersion] = React13.useState(null);
|
|
17075
17079
|
const [currentVersionIndex, setCurrentVersionIndex] = React13.useState(null);
|
|
17080
|
+
function getApiBaseUrl() {
|
|
17081
|
+
if (typeof import.meta !== "undefined" && import.meta.env?.VITE_TETRONS_API_URL) {
|
|
17082
|
+
return import.meta.env.VITE_TETRONS_API_URL;
|
|
17083
|
+
}
|
|
17084
|
+
if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_TETRONS_API_URL) {
|
|
17085
|
+
return process.env.NEXT_PUBLIC_TETRONS_API_URL;
|
|
17086
|
+
}
|
|
17087
|
+
return "https://staging.tetrons.com";
|
|
17088
|
+
}
|
|
17089
|
+
const API_BASE_URL = getApiBaseUrl();
|
|
17076
17090
|
useEffect7(() => {
|
|
17077
17091
|
const validateKey = async () => {
|
|
17078
17092
|
try {
|
|
17079
|
-
const res = await fetch(
|
|
17093
|
+
const res = await fetch(`${API_BASE_URL}/api/validate`, {
|
|
17080
17094
|
method: "POST",
|
|
17081
17095
|
headers: {
|
|
17082
17096
|
"Content-Type": "application/json"
|
|
@@ -17086,6 +17100,7 @@ function EditorContent({ apiKey }) {
|
|
|
17086
17100
|
const data = await res.json();
|
|
17087
17101
|
if (!res.ok) throw new Error(data.error || "Invalid API Key");
|
|
17088
17102
|
setIsValid(true);
|
|
17103
|
+
setUserVersion(data.version);
|
|
17089
17104
|
} catch (err) {
|
|
17090
17105
|
if (err instanceof Error) {
|
|
17091
17106
|
setError(err.message || "Invalid API Key");
|
|
@@ -17186,7 +17201,7 @@ function EditorContent({ apiKey }) {
|
|
|
17186
17201
|
if (isValid === null) {
|
|
17187
17202
|
return /* @__PURE__ */ React13.createElement("div", { className: "editor-loading" }, "\u{1F50D} Validating license...");
|
|
17188
17203
|
}
|
|
17189
|
-
return /* @__PURE__ */ React13.createElement("div", { className: "editor-container" }, /* @__PURE__ */ React13.createElement("div", { className: "editor-toolbar" }, /* @__PURE__ */ React13.createElement(
|
|
17204
|
+
return /* @__PURE__ */ React13.createElement("div", { className: "editor-container" }, userVersion !== "free" && /* @__PURE__ */ React13.createElement("div", { className: "editor-toolbar" }, /* @__PURE__ */ React13.createElement(
|
|
17190
17205
|
"button",
|
|
17191
17206
|
{
|
|
17192
17207
|
type: "button",
|
|
@@ -17205,7 +17220,7 @@ function EditorContent({ apiKey }) {
|
|
|
17205
17220
|
title: `Restore Version ${idx + 1}`
|
|
17206
17221
|
},
|
|
17207
17222
|
`V${idx + 1}`
|
|
17208
|
-
)))), editor && /* @__PURE__ */ React13.createElement(TetronsToolbar, { editor }), /* @__PURE__ */ React13.createElement(
|
|
17223
|
+
)))), editor && userVersion && /* @__PURE__ */ React13.createElement(TetronsToolbar, { editor, version: userVersion }), /* @__PURE__ */ React13.createElement(
|
|
17209
17224
|
"div",
|
|
17210
17225
|
{
|
|
17211
17226
|
ref: wrapperRef,
|
|
@@ -17217,8 +17232,35 @@ function EditorContent({ apiKey }) {
|
|
|
17217
17232
|
}
|
|
17218
17233
|
|
|
17219
17234
|
// src/index.ts
|
|
17235
|
+
var API_VALID = false;
|
|
17236
|
+
var API_VERSION = "";
|
|
17237
|
+
async function initializeTetrons(apiKey) {
|
|
17238
|
+
const res = await fetch("https://staging.tetrons.com/api/validate", {
|
|
17239
|
+
method: "POST",
|
|
17240
|
+
headers: {
|
|
17241
|
+
"Content-Type": "application/json"
|
|
17242
|
+
},
|
|
17243
|
+
body: JSON.stringify({ apiKey })
|
|
17244
|
+
});
|
|
17245
|
+
if (!res.ok) {
|
|
17246
|
+
const error = await res.json();
|
|
17247
|
+
throw new Error(`API Key validation failed: ${error.error}`);
|
|
17248
|
+
}
|
|
17249
|
+
const data = await res.json();
|
|
17250
|
+
API_VALID = data.valid;
|
|
17251
|
+
API_VERSION = data.version;
|
|
17252
|
+
}
|
|
17253
|
+
function getTetronsVersion() {
|
|
17254
|
+
return API_VERSION;
|
|
17255
|
+
}
|
|
17256
|
+
function isApiKeyValid() {
|
|
17257
|
+
return API_VALID;
|
|
17258
|
+
}
|
|
17220
17259
|
var index_default = EditorContent;
|
|
17221
17260
|
export {
|
|
17222
17261
|
EditorContent,
|
|
17223
|
-
index_default as default
|
|
17262
|
+
index_default as default,
|
|
17263
|
+
getTetronsVersion,
|
|
17264
|
+
initializeTetrons,
|
|
17265
|
+
isApiKeyValid
|
|
17224
17266
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tetrons",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.22",
|
|
4
4
|
"description": "A Next.js project written in TypeScript",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@eslint/eslintrc": "^3",
|
|
42
|
-
"@tailwindcss/postcss": "^4",
|
|
42
|
+
"@tailwindcss/postcss": "^4.1.11",
|
|
43
43
|
"@types/node": "^20",
|
|
44
44
|
"@types/react": "^19",
|
|
45
45
|
"@types/react-dom": "^19",
|
|
46
|
+
"autoprefixer": "^10.4.21",
|
|
46
47
|
"copyfiles": "^2.4.1",
|
|
47
48
|
"eslint": "^9",
|
|
48
49
|
"eslint-config-next": "^15.3.2",
|
|
49
|
-
"tailwindcss": "^4",
|
|
50
50
|
"tsup": "^8.5.0",
|
|
51
51
|
"typescript": "^5"
|
|
52
52
|
},
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
"author": "Your Name",
|
|
64
64
|
"license": "MIT",
|
|
65
65
|
"exports": {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
"./style.css": "./dist/styles/tetrons.css"
|
|
66
|
+
".": {
|
|
67
|
+
"import": "./dist/index.js",
|
|
68
|
+
"require": "./dist/index.js",
|
|
69
|
+
"types": "./dist/index.d.ts"
|
|
72
70
|
},
|
|
71
|
+
"./style.css": "./dist/styles/tetrons.css"
|
|
72
|
+
},
|
|
73
73
|
"files": [
|
|
74
74
|
"dist",
|
|
75
75
|
"dist/styles/tetrons.css",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function GET(req: Request): Response;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { connectDB } from "../../../lib/db";
|
|
3
|
-
import { ApiKey } from "../../../models/ApiKey";
|
|
4
|
-
import { generateUserApiKey, getFreeApiKey } from "../../../utils/apiKeyUtils";
|
|
5
|
-
export async function POST(req) {
|
|
6
|
-
const body = await req.json();
|
|
7
|
-
const { email, organization, version } = body;
|
|
8
|
-
if (!email || !organization || !version) {
|
|
9
|
-
return NextResponse.json({ error: "Missing fields" }, { status: 400 });
|
|
10
|
-
}
|
|
11
|
-
await connectDB();
|
|
12
|
-
const lowerEmail = email.trim().toLowerCase();
|
|
13
|
-
const lowerOrg = organization.trim().toLowerCase();
|
|
14
|
-
await ApiKey.deleteMany({ email: lowerEmail, version });
|
|
15
|
-
let apiKey;
|
|
16
|
-
let expiresAt = null;
|
|
17
|
-
if (version === "free") {
|
|
18
|
-
apiKey = getFreeApiKey();
|
|
19
|
-
expiresAt = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
apiKey = generateUserApiKey(lowerEmail, lowerOrg, version);
|
|
23
|
-
}
|
|
24
|
-
await ApiKey.create({
|
|
25
|
-
email: lowerEmail,
|
|
26
|
-
organization: lowerOrg,
|
|
27
|
-
version,
|
|
28
|
-
apiKey,
|
|
29
|
-
expiresAt,
|
|
30
|
-
});
|
|
31
|
-
return NextResponse.json({ apiKey, expiresAt });
|
|
32
|
-
}
|