tetrons 2.3.33 → 2.3.34
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/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/app/api/export/route.js +0 -4
- package/dist/app/api/register/route.js +0 -32
- package/dist/app/api/save/route.js +0 -15
- 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.jsx +0 -51
- package/dist/components/UI/Button.d.ts +0 -0
- package/dist/components/UI/Button.jsx +0 -1
- package/dist/components/UI/Dropdown.d.ts +0 -0
- package/dist/components/UI/Dropdown.jsx +0 -1
- package/dist/components/tetrons/EditorContent.jsx +0 -209
- 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/src/app/page.d.ts +0 -2
- package/dist/src/app/page.jsx +0 -51
- package/dist/src/components/tetrons/toolbar/AIGroup.d.ts +0 -5
- package/dist/src/components/tetrons/toolbar/AIGroup.jsx +0 -140
- 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/index.cjs
CHANGED
|
@@ -16656,7 +16656,7 @@ function AiGroup({ editor }) {
|
|
|
16656
16656
|
const formData = new FormData();
|
|
16657
16657
|
formData.append("file", blob, "voice.webm");
|
|
16658
16658
|
try {
|
|
16659
|
-
const res = await fetch("/api/transcribe", {
|
|
16659
|
+
const res = await fetch("https://staging.tetrons.com/api/transcribe", {
|
|
16660
16660
|
method: "POST",
|
|
16661
16661
|
body: formData
|
|
16662
16662
|
});
|
|
@@ -16682,7 +16682,7 @@ function AiGroup({ editor }) {
|
|
|
16682
16682
|
setIsLoadingAI(true);
|
|
16683
16683
|
setAiError("");
|
|
16684
16684
|
try {
|
|
16685
|
-
const res = await fetch("/api/ai-action", {
|
|
16685
|
+
const res = await fetch("https://staging.tetrons.com/api/ai-action", {
|
|
16686
16686
|
method: "POST",
|
|
16687
16687
|
headers: { "Content-Type": "application/json" },
|
|
16688
16688
|
body: JSON.stringify({ content: prompt2 })
|
package/dist/index.mjs
CHANGED
|
@@ -16664,7 +16664,7 @@ function AiGroup({ editor }) {
|
|
|
16664
16664
|
const formData = new FormData();
|
|
16665
16665
|
formData.append("file", blob, "voice.webm");
|
|
16666
16666
|
try {
|
|
16667
|
-
const res = await fetch("/api/transcribe", {
|
|
16667
|
+
const res = await fetch("https://staging.tetrons.com/api/transcribe", {
|
|
16668
16668
|
method: "POST",
|
|
16669
16669
|
body: formData
|
|
16670
16670
|
});
|
|
@@ -16690,7 +16690,7 @@ function AiGroup({ editor }) {
|
|
|
16690
16690
|
setIsLoadingAI(true);
|
|
16691
16691
|
setAiError("");
|
|
16692
16692
|
try {
|
|
16693
|
-
const res = await fetch("/api/ai-action", {
|
|
16693
|
+
const res = await fetch("https://staging.tetrons.com/api/ai-action", {
|
|
16694
16694
|
method: "POST",
|
|
16695
16695
|
headers: { "Content-Type": "application/json" },
|
|
16696
16696
|
body: JSON.stringify({ content: prompt2 })
|
package/package.json
CHANGED
|
@@ -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
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
|
-
export async function POST(request) {
|
|
5
|
-
try {
|
|
6
|
-
const json = await request.json();
|
|
7
|
-
const publicDir = path.join(process.cwd(), "public");
|
|
8
|
-
const filePath = path.join(publicDir, "editor-content.json");
|
|
9
|
-
await fs.writeFile(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
10
|
-
return NextResponse.json({ message: "File saved successfully" });
|
|
11
|
-
}
|
|
12
|
-
catch (_a) {
|
|
13
|
-
return NextResponse.json({ error: "Failed to save file" }, { status: 500 });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { connectDB } from "../../../lib/db";
|
|
3
|
-
import { ApiKey } from "../../../models/ApiKey";
|
|
4
|
-
const corsHeaders = {
|
|
5
|
-
"Access-Control-Allow-Origin": "*",
|
|
6
|
-
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
7
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
|
8
|
-
};
|
|
9
|
-
export async function OPTIONS() {
|
|
10
|
-
return new Response(null, {
|
|
11
|
-
status: 204,
|
|
12
|
-
headers: corsHeaders,
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
export async function POST(req) {
|
|
16
|
-
try {
|
|
17
|
-
const body = (await req.json());
|
|
18
|
-
const apiKey = body.apiKey;
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
return NextResponse.json({ error: "API key required" }, {
|
|
21
|
-
status: 400,
|
|
22
|
-
headers: corsHeaders,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
await connectDB();
|
|
26
|
-
const keyEntry = await ApiKey.findOne({ apiKey });
|
|
27
|
-
if (!keyEntry) {
|
|
28
|
-
return NextResponse.json({ error: "Invalid API key" }, {
|
|
29
|
-
status: 401,
|
|
30
|
-
headers: corsHeaders,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
if (keyEntry.version === "free" &&
|
|
34
|
-
keyEntry.expiresAt &&
|
|
35
|
-
new Date() > new Date(keyEntry.expiresAt)) {
|
|
36
|
-
return NextResponse.json({ error: "Free trial expired" }, {
|
|
37
|
-
status: 403,
|
|
38
|
-
headers: corsHeaders,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return NextResponse.json({
|
|
42
|
-
valid: true,
|
|
43
|
-
version: keyEntry.version,
|
|
44
|
-
email: keyEntry.email,
|
|
45
|
-
organization: keyEntry.organization,
|
|
46
|
-
}, {
|
|
47
|
-
status: 200,
|
|
48
|
-
headers: corsHeaders,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
console.error("Validation Error:", err);
|
|
53
|
-
return NextResponse.json({ error: "Server error" }, {
|
|
54
|
-
status: 500,
|
|
55
|
-
headers: corsHeaders,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
package/dist/app/layout.d.ts
DELETED
package/dist/app/layout.jsx
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Geist, Geist_Mono } from "next/font/google";
|
|
2
|
-
import "./globals.css";
|
|
3
|
-
const geistSans = Geist({
|
|
4
|
-
variable: "--font-geist-sans",
|
|
5
|
-
subsets: ["latin"],
|
|
6
|
-
});
|
|
7
|
-
const geistMono = Geist_Mono({
|
|
8
|
-
variable: "--font-geist-mono",
|
|
9
|
-
subsets: ["latin"],
|
|
10
|
-
});
|
|
11
|
-
export const metadata = {
|
|
12
|
-
title: "Tetrons",
|
|
13
|
-
description: "A modern Word-style rich text editor built with Next.js",
|
|
14
|
-
icons: {
|
|
15
|
-
icon: [
|
|
16
|
-
{ url: "/favicon.ico", type: "image/x-icon" },
|
|
17
|
-
{ url: "/favicon-32x32.png", type: "image/png", sizes: "32x32" },
|
|
18
|
-
{ url: "/favicon-16x16.png", type: "image/png", sizes: "16x16" },
|
|
19
|
-
],
|
|
20
|
-
apple: "/apple-touch-icon.png",
|
|
21
|
-
},
|
|
22
|
-
manifest: "/site.webmanifest",
|
|
23
|
-
};
|
|
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>
|
|
29
|
-
</html>);
|
|
30
|
-
}
|
package/dist/app/page.jsx
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
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 fetchOrGenerateApiKey = async () => {
|
|
10
|
-
let key = localStorage.getItem("tetrons-key");
|
|
11
|
-
if (!key) {
|
|
12
|
-
try {
|
|
13
|
-
const res = await fetch("/api/register", {
|
|
14
|
-
method: "POST",
|
|
15
|
-
headers: { "Content-Type": "application/json" },
|
|
16
|
-
body: JSON.stringify({
|
|
17
|
-
email: "developer@finapsys.co.in",
|
|
18
|
-
organization: "FCSPL",
|
|
19
|
-
version: "free",
|
|
20
|
-
}),
|
|
21
|
-
});
|
|
22
|
-
const data = await res.json();
|
|
23
|
-
if (!res.ok || !data.apiKey) {
|
|
24
|
-
throw new Error(data.error || "Failed to register for API key");
|
|
25
|
-
}
|
|
26
|
-
key = data.apiKey;
|
|
27
|
-
if (key) {
|
|
28
|
-
localStorage.setItem("tetrons-key", key);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch (err) {
|
|
32
|
-
console.error("❌ Failed to fetch or register API key:", err);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
setApiKey(key);
|
|
36
|
-
setLoading(false);
|
|
37
|
-
};
|
|
38
|
-
fetchOrGenerateApiKey();
|
|
39
|
-
}, []);
|
|
40
|
-
if (loading) {
|
|
41
|
-
return <div className="text-center p-4">⏳ Loading Editor...</div>;
|
|
42
|
-
}
|
|
43
|
-
if (!apiKey) {
|
|
44
|
-
return <div className="text-red-600 text-center">❌ API key not found</div>;
|
|
45
|
-
}
|
|
46
|
-
return (<main className="flex flex-col h-screen overflow-hidden">
|
|
47
|
-
<div className="flex-1 overflow-auto flex flex-col">
|
|
48
|
-
<EditorContent apiKey={apiKey}/>
|
|
49
|
-
</div>
|
|
50
|
-
</main>);
|
|
51
|
-
}
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { Comment } from "./toolbar/extensions/Comment";
|
|
4
|
-
import { useEffect, useRef } from "react";
|
|
5
|
-
import { useEditor, EditorContent as TiptapEditorContent, } from "@tiptap/react";
|
|
6
|
-
import Document from "@tiptap/extension-document";
|
|
7
|
-
import Paragraph from "@tiptap/extension-paragraph";
|
|
8
|
-
import Text from "@tiptap/extension-text";
|
|
9
|
-
import History from "@tiptap/extension-history";
|
|
10
|
-
import Bold from "@tiptap/extension-bold";
|
|
11
|
-
import Italic from "@tiptap/extension-italic";
|
|
12
|
-
import Underline from "@tiptap/extension-underline";
|
|
13
|
-
import Strike from "@tiptap/extension-strike";
|
|
14
|
-
import Code from "@tiptap/extension-code";
|
|
15
|
-
import Blockquote from "@tiptap/extension-blockquote";
|
|
16
|
-
import HardBreak from "@tiptap/extension-hard-break";
|
|
17
|
-
import Heading from "@tiptap/extension-heading";
|
|
18
|
-
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
|
19
|
-
import TextAlign from "@tiptap/extension-text-align";
|
|
20
|
-
import Color from "@tiptap/extension-color";
|
|
21
|
-
import Highlight from "@tiptap/extension-highlight";
|
|
22
|
-
import Image from "@tiptap/extension-image";
|
|
23
|
-
import Link from "@tiptap/extension-link";
|
|
24
|
-
import TextStyle from "@tiptap/extension-text-style";
|
|
25
|
-
import ListItem from "@tiptap/extension-list-item";
|
|
26
|
-
import BulletList from "@tiptap/extension-bullet-list";
|
|
27
|
-
import OrderedList from "@tiptap/extension-ordered-list";
|
|
28
|
-
import { Subscript } from "./toolbar/marks/Subscript";
|
|
29
|
-
import { Superscript } from "./toolbar/marks/Superscript";
|
|
30
|
-
import { ResizableTable } from "./toolbar/extensions/ResizableTable";
|
|
31
|
-
import { Embed } from "./toolbar/extensions/Embed";
|
|
32
|
-
import TableRow from "@tiptap/extension-table-row";
|
|
33
|
-
import TableCell from "@tiptap/extension-table-cell";
|
|
34
|
-
import TableHeader from "@tiptap/extension-table-header";
|
|
35
|
-
import { FontFamily } from "./toolbar/extensions/FontFamily";
|
|
36
|
-
import { FontSize } from "./toolbar/extensions/FontSize";
|
|
37
|
-
import TetronsToolbar from "./toolbar/TetronsToolbar";
|
|
38
|
-
import { ResizableImage } from "./ResizableImage";
|
|
39
|
-
import { ResizableVideo } from "./ResizableVideo";
|
|
40
|
-
import TableContextMenu from "./toolbar/TableContextMenu";
|
|
41
|
-
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
|
42
|
-
import { createLowlight } from "lowlight";
|
|
43
|
-
import js from "highlight.js/lib/languages/javascript";
|
|
44
|
-
import ts from "highlight.js/lib/languages/typescript";
|
|
45
|
-
const lowlight = createLowlight();
|
|
46
|
-
lowlight.register("js", js);
|
|
47
|
-
lowlight.register("ts", ts);
|
|
48
|
-
export default function EditorContent({ apiKey }) {
|
|
49
|
-
const [isValid, setIsValid] = React.useState(null);
|
|
50
|
-
const [error, setError] = React.useState(null);
|
|
51
|
-
const [versions, setVersions] = React.useState([]);
|
|
52
|
-
const [userVersion, setUserVersion] = React.useState(null);
|
|
53
|
-
const [currentVersionIndex, setCurrentVersionIndex] = React.useState(null);
|
|
54
|
-
function getApiBaseUrl() {
|
|
55
|
-
var _a, _b;
|
|
56
|
-
if (typeof import.meta !== "undefined" &&
|
|
57
|
-
((_a = import.meta.env) === null || _a === void 0 ? void 0 : _a.VITE_TETRONS_API_URL)) {
|
|
58
|
-
return import.meta.env.VITE_TETRONS_API_URL;
|
|
59
|
-
}
|
|
60
|
-
if (typeof process !== "undefined" &&
|
|
61
|
-
((_b = process.env) === null || _b === void 0 ? void 0 : _b.NEXT_PUBLIC_TETRONS_API_URL)) {
|
|
62
|
-
return process.env.NEXT_PUBLIC_TETRONS_API_URL;
|
|
63
|
-
}
|
|
64
|
-
return "https://staging.tetrons.com";
|
|
65
|
-
}
|
|
66
|
-
const API_BASE_URL = getApiBaseUrl();
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
const validateKey = async () => {
|
|
69
|
-
try {
|
|
70
|
-
const res = await fetch(`${API_BASE_URL}/api/validate`, {
|
|
71
|
-
method: "POST",
|
|
72
|
-
headers: {
|
|
73
|
-
"Content-Type": "application/json",
|
|
74
|
-
},
|
|
75
|
-
body: JSON.stringify({ apiKey }),
|
|
76
|
-
});
|
|
77
|
-
const data = await res.json();
|
|
78
|
-
if (!res.ok)
|
|
79
|
-
throw new Error(data.error || "Invalid API Key");
|
|
80
|
-
setIsValid(true);
|
|
81
|
-
setUserVersion(data.version);
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
if (err instanceof Error) {
|
|
85
|
-
setError(err.message || "Invalid API Key");
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
setError("Invalid API Key");
|
|
89
|
-
}
|
|
90
|
-
setIsValid(false);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
validateKey();
|
|
94
|
-
}, [apiKey]);
|
|
95
|
-
const editor = useEditor({
|
|
96
|
-
extensions: [
|
|
97
|
-
Document,
|
|
98
|
-
Paragraph,
|
|
99
|
-
Text,
|
|
100
|
-
History,
|
|
101
|
-
Bold,
|
|
102
|
-
Italic,
|
|
103
|
-
Underline,
|
|
104
|
-
Strike,
|
|
105
|
-
Code,
|
|
106
|
-
Blockquote,
|
|
107
|
-
HardBreak,
|
|
108
|
-
Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
|
|
109
|
-
HorizontalRule,
|
|
110
|
-
TextStyle,
|
|
111
|
-
Color,
|
|
112
|
-
Highlight.configure({ multicolor: true }),
|
|
113
|
-
FontFamily,
|
|
114
|
-
FontSize,
|
|
115
|
-
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
116
|
-
ListItem,
|
|
117
|
-
BulletList,
|
|
118
|
-
OrderedList,
|
|
119
|
-
Subscript,
|
|
120
|
-
Superscript,
|
|
121
|
-
Image,
|
|
122
|
-
Link.configure({
|
|
123
|
-
openOnClick: false,
|
|
124
|
-
autolink: true,
|
|
125
|
-
linkOnPaste: true,
|
|
126
|
-
}),
|
|
127
|
-
ResizableTable.configure({
|
|
128
|
-
resizable: true,
|
|
129
|
-
}),
|
|
130
|
-
TableRow,
|
|
131
|
-
TableCell,
|
|
132
|
-
TableHeader,
|
|
133
|
-
Embed,
|
|
134
|
-
ResizableImage,
|
|
135
|
-
ResizableVideo,
|
|
136
|
-
Comment,
|
|
137
|
-
CodeBlockLowlight.configure({
|
|
138
|
-
lowlight,
|
|
139
|
-
HTMLAttributes: {
|
|
140
|
-
class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
|
|
141
|
-
},
|
|
142
|
-
}),
|
|
143
|
-
],
|
|
144
|
-
content: "",
|
|
145
|
-
editorProps: {
|
|
146
|
-
attributes: {
|
|
147
|
-
class: "min-h-full focus:outline-none p-0",
|
|
148
|
-
"data-placeholder": "Start typing here...",
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
immediatelyRender: false,
|
|
152
|
-
});
|
|
153
|
-
const wrapperRef = useRef(null);
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
return () => {
|
|
156
|
-
editor === null || editor === void 0 ? void 0 : editor.destroy();
|
|
157
|
-
};
|
|
158
|
-
}, [editor]);
|
|
159
|
-
const handleEditorClick = () => {
|
|
160
|
-
if (editor && !editor.isFocused) {
|
|
161
|
-
editor.commands.focus();
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
const saveVersion = () => {
|
|
165
|
-
if (!editor)
|
|
166
|
-
return;
|
|
167
|
-
const content = editor.getJSON();
|
|
168
|
-
setVersions((prev) => [...prev, JSON.stringify(content)]);
|
|
169
|
-
setCurrentVersionIndex(versions.length);
|
|
170
|
-
};
|
|
171
|
-
const restoreVersion = (index) => {
|
|
172
|
-
if (!editor)
|
|
173
|
-
return;
|
|
174
|
-
const versionContent = versions[index];
|
|
175
|
-
if (versionContent) {
|
|
176
|
-
editor.commands.setContent(JSON.parse(versionContent));
|
|
177
|
-
setCurrentVersionIndex(index);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
if (isValid === false) {
|
|
181
|
-
return <div className="editor-error">⚠️ {error}</div>;
|
|
182
|
-
}
|
|
183
|
-
if (isValid === null) {
|
|
184
|
-
return <div className="editor-loading">🔍 Validating license...</div>;
|
|
185
|
-
}
|
|
186
|
-
return (<div className="editor-container">
|
|
187
|
-
{userVersion !== "free" && (<div className="editor-toolbar">
|
|
188
|
-
<button type="button" onClick={saveVersion} disabled={!editor} className="editor-save-btn">
|
|
189
|
-
Save Version
|
|
190
|
-
</button>
|
|
191
|
-
|
|
192
|
-
<div className="editor-versions-wrapper">
|
|
193
|
-
{versions.length === 0 && (<span className="editor-no-versions">No saved versions</span>)}
|
|
194
|
-
{versions.map((_, idx) => (<button type="button" key={idx} onClick={() => restoreVersion(idx)} className={`editor-version-btn ${idx === currentVersionIndex ? "active" : ""}`} title={`Restore Version ${idx + 1}`}>
|
|
195
|
-
{`V${idx + 1}`}
|
|
196
|
-
</button>))}
|
|
197
|
-
</div>
|
|
198
|
-
</div>)}
|
|
199
|
-
|
|
200
|
-
{editor && userVersion && (<TetronsToolbar editor={editor} version={userVersion}/>)}
|
|
201
|
-
|
|
202
|
-
<div ref={wrapperRef} className="editor-content-wrapper" onClick={handleEditorClick}>
|
|
203
|
-
{editor ? (<>
|
|
204
|
-
<TiptapEditorContent editor={editor}/>
|
|
205
|
-
{editor && <TableContextMenu editor={editor}/>}
|
|
206
|
-
</>) : (<div className="editor-loading">Loading editor...</div>)}
|
|
207
|
-
</div>
|
|
208
|
-
</div>);
|
|
209
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Node } from "@tiptap/core";
|
|
2
|
-
declare module "@tiptap/core" {
|
|
3
|
-
interface Commands<ReturnType> {
|
|
4
|
-
video: {
|
|
5
|
-
setVideo: (options: {
|
|
6
|
-
src: string;
|
|
7
|
-
controls?: boolean;
|
|
8
|
-
}) => ReturnType;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export declare const ResizableVideo: Node<any, any>;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
-
var t = {};
|
|
3
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
-
t[p] = s[p];
|
|
5
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
-
t[p[i]] = s[p[i]];
|
|
9
|
-
}
|
|
10
|
-
return t;
|
|
11
|
-
};
|
|
12
|
-
import { Node } from "@tiptap/core";
|
|
13
|
-
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
14
|
-
import ResizableVideoComponent from "./ResizableVideoComponent";
|
|
15
|
-
export const ResizableVideo = Node.create({
|
|
16
|
-
name: "video",
|
|
17
|
-
group: "block",
|
|
18
|
-
draggable: true,
|
|
19
|
-
atom: true,
|
|
20
|
-
addAttributes() {
|
|
21
|
-
return {
|
|
22
|
-
src: { default: null },
|
|
23
|
-
controls: {
|
|
24
|
-
default: true,
|
|
25
|
-
parseHTML: (element) => element.hasAttribute("controls"),
|
|
26
|
-
renderHTML: (attributes) => attributes.controls ? { controls: "controls" } : {},
|
|
27
|
-
},
|
|
28
|
-
width: {
|
|
29
|
-
default: null,
|
|
30
|
-
},
|
|
31
|
-
height: {
|
|
32
|
-
default: null,
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
parseHTML() {
|
|
37
|
-
return [{ tag: "video[src]" }];
|
|
38
|
-
},
|
|
39
|
-
renderHTML({ HTMLAttributes }) {
|
|
40
|
-
const { width, height } = HTMLAttributes, rest = __rest(HTMLAttributes, ["width", "height"]);
|
|
41
|
-
const style = [];
|
|
42
|
-
if (width)
|
|
43
|
-
style.push(`width: ${width}px`);
|
|
44
|
-
if (height)
|
|
45
|
-
style.push(`height: ${height}px`);
|
|
46
|
-
return ["video", Object.assign(Object.assign({}, rest), { style: style.join("; ") })];
|
|
47
|
-
},
|
|
48
|
-
addCommands() {
|
|
49
|
-
return {
|
|
50
|
-
setVideo: (attributes) => ({ commands }) => {
|
|
51
|
-
return commands.insertContent({
|
|
52
|
-
type: this.name,
|
|
53
|
-
attrs: attributes,
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
addNodeView() {
|
|
59
|
-
return ReactNodeViewRenderer(ResizableVideoComponent);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useEffect } from "react";
|
|
2
|
-
import { NodeViewWrapper } from "@tiptap/react";
|
|
3
|
-
const ResizableVideoComponent = ({ node, updateAttributes, selected, }) => {
|
|
4
|
-
const { src, controls, width, height } = node.attrs;
|
|
5
|
-
const wrapperRef = useRef(null);
|
|
6
|
-
const videoRef = useRef(null);
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
const video = videoRef.current;
|
|
9
|
-
if (!video)
|
|
10
|
-
return;
|
|
11
|
-
const observer = new ResizeObserver(() => {
|
|
12
|
-
const w = Math.round(video.offsetWidth);
|
|
13
|
-
const h = Math.round(video.offsetHeight);
|
|
14
|
-
updateAttributes({ width: w, height: h });
|
|
15
|
-
});
|
|
16
|
-
observer.observe(video);
|
|
17
|
-
return () => observer.disconnect();
|
|
18
|
-
}, [updateAttributes]);
|
|
19
|
-
return (<NodeViewWrapper ref={wrapperRef} contentEditable={false} className={`resizable-video-wrapper ${selected ? "ProseMirror-selectednode" : ""}`} style={{
|
|
20
|
-
resize: "both",
|
|
21
|
-
overflow: "auto",
|
|
22
|
-
border: "1px solid #ccc",
|
|
23
|
-
padding: "2px",
|
|
24
|
-
display: "inline-block",
|
|
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>);
|
|
31
|
-
};
|
|
32
|
-
export default ResizableVideoComponent;
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|