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.
- package/dist/app/api/ai-action/route.cjs +61 -0
- package/dist/app/api/ai-action/route.d.mts +9 -0
- package/dist/app/api/ai-action/route.d.ts +9 -0
- package/dist/app/api/ai-action/route.mjs +36 -0
- package/dist/app/api/export/route.cjs +33 -0
- package/dist/app/api/export/route.d.mts +3 -0
- package/dist/app/api/export/route.d.ts +3 -0
- package/dist/app/api/export/route.mjs +8 -0
- package/dist/app/api/register/route.cjs +120 -0
- package/dist/app/api/register/route.d.mts +10 -0
- package/dist/app/api/register/route.d.ts +10 -0
- package/dist/app/api/register/route.mjs +85 -0
- package/dist/app/api/save/route.cjs +53 -0
- package/dist/app/api/save/route.d.mts +9 -0
- package/dist/app/api/save/route.d.ts +9 -0
- package/dist/app/api/save/route.mjs +18 -0
- package/dist/app/api/transcribe/route.cjs +72 -0
- package/dist/app/api/transcribe/route.d.mts +10 -0
- package/dist/app/api/transcribe/route.d.ts +10 -0
- package/dist/app/api/transcribe/route.mjs +46 -0
- package/dist/app/api/validate/route.cjs +143 -0
- package/dist/app/api/validate/route.d.mts +13 -0
- package/dist/app/api/validate/route.d.ts +13 -0
- package/dist/app/api/validate/route.mjs +107 -0
- package/dist/index.d.ts +12 -6
- package/package.json +3 -2
- package/dist/app/page.d.ts +0 -2
- package/dist/components/components/UI/Button.tsx +0 -0
- package/dist/components/components/UI/Dropdown.tsx +0 -0
- package/dist/components/components/tetrons/EditorContent.tsx +0 -280
- package/dist/components/components/tetrons/ResizableImageComponent.tsx +0 -112
- package/dist/components/components/tetrons/ResizableVideoComponent.tsx +0 -56
- package/dist/components/tetrons/EditorContent.d.ts +0 -6
- package/dist/components/tetrons/ResizableImage.d.ts +0 -1
- package/dist/components/tetrons/ResizableImage.ts +0 -35
- package/dist/components/tetrons/ResizableImageComponent.d.ts +0 -4
- package/dist/components/tetrons/ResizableImageComponent.jsx +0 -73
- package/dist/components/tetrons/ResizableVideo.ts +0 -66
- package/dist/components/tetrons/extensions/Spellcheck.ts +0 -50
- package/dist/components/tetrons/helpers.ts +0 -0
- package/dist/components/tetrons/toolbar/AIGroup.tsx +0 -209
- package/dist/components/tetrons/toolbar/ActionGroup.tsx +0 -218
- package/dist/components/tetrons/toolbar/ClipboardGroup.tsx +0 -58
- package/dist/components/tetrons/toolbar/FileGroup.tsx +0 -66
- package/dist/components/tetrons/toolbar/FontStyleGroup.tsx +0 -194
- package/dist/components/tetrons/toolbar/InsertGroup.tsx +0 -267
- package/dist/components/tetrons/toolbar/ListAlignGroup.tsx +0 -69
- package/dist/components/tetrons/toolbar/MiscGroup.tsx +0 -104
- package/dist/components/tetrons/toolbar/TableContextMenu.tsx +0 -91
- package/dist/components/tetrons/toolbar/TetronsToolbar.tsx +0 -77
- package/dist/components/tetrons/toolbar/ToolbarButton.tsx +0 -36
- package/dist/components/tetrons/toolbar/extensions/Comment.ts +0 -72
- package/dist/components/tetrons/toolbar/extensions/Embed.ts +0 -113
- package/dist/components/tetrons/toolbar/extensions/FontFamily.ts +0 -43
- package/dist/components/tetrons/toolbar/extensions/FontSize.ts +0 -43
- package/dist/components/tetrons/toolbar/extensions/ResizableTable.ts +0 -16
- package/dist/components/tetrons/toolbar/marks/Subscript.ts +0 -45
- package/dist/components/tetrons/toolbar/marks/Superscript.ts +0 -45
- package/dist/styles/styles/tetrons.css +0 -563
- /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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
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
|
|
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
|
},
|
package/dist/app/page.d.ts
DELETED
|
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
|
-
}
|