tetrons 2.3.26 → 2.3.28
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.d.mts +9 -0
- package/dist/app/api/ai-action/route.mjs +36 -0
- package/dist/app/api/export/route.d.mts +3 -0
- package/dist/app/api/export/route.mjs +8 -0
- package/dist/app/api/register/route.d.mts +10 -0
- package/dist/app/api/register/route.mjs +85 -0
- package/dist/app/api/save/route.d.mts +9 -0
- package/dist/app/api/save/route.mjs +18 -0
- package/dist/app/api/transcribe/route.d.mts +10 -0
- package/dist/app/api/transcribe/route.mjs +46 -0
- package/dist/app/api/validate/route.d.mts +13 -0
- package/dist/app/api/validate/route.mjs +107 -0
- package/dist/components/tetrons/ResizableImageComponent.tsx +64 -29
- package/dist/index.mjs +93 -56
- package/package.json +2 -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 -77
- 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.js +0 -36
- package/dist/components/tetrons/ResizableImage.ts +0 -39
- package/dist/components/tetrons/ResizableImageComponent.d.ts +0 -4
- package/dist/components/tetrons/ResizableImageComponent.jsx +0 -37
- 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/index.d.ts +0 -6
- package/dist/index.js +0 -16975
- package/dist/styles/styles/tetrons.css +0 -563
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/tetrons/EditorContent.tsx
|
|
2
|
-
import React14, { useEffect as useEffect8, useRef as useRef7, useState as
|
|
2
|
+
import React14, { useEffect as useEffect8, useRef as useRef7, useState as useState9 } from "react";
|
|
3
3
|
import {
|
|
4
4
|
useEditor,
|
|
5
5
|
EditorContent as TiptapEditorContent
|
|
@@ -15364,7 +15364,7 @@ import Image from "@tiptap/extension-image";
|
|
|
15364
15364
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
15365
15365
|
|
|
15366
15366
|
// src/components/tetrons/ResizableImageComponent.tsx
|
|
15367
|
-
import React, { useRef, useEffect as useEffect2 } from "react";
|
|
15367
|
+
import React, { useRef, useEffect as useEffect2, useState as useState2 } from "react";
|
|
15368
15368
|
import { NodeViewWrapper } from "@tiptap/react";
|
|
15369
15369
|
var ResizableImageComponent = ({
|
|
15370
15370
|
node,
|
|
@@ -15372,19 +15372,36 @@ var ResizableImageComponent = ({
|
|
|
15372
15372
|
selected
|
|
15373
15373
|
}) => {
|
|
15374
15374
|
const { src, alt, title, width, height } = node.attrs;
|
|
15375
|
+
const defaultWidth = width ?? 300;
|
|
15376
|
+
const defaultHeight = height ?? 200;
|
|
15377
|
+
const aspectRatio = defaultHeight > 0 ? defaultWidth / defaultHeight : 4 / 3;
|
|
15375
15378
|
const wrapperRef = useRef(null);
|
|
15376
|
-
const
|
|
15379
|
+
const [isResizing, setIsResizing] = useState2(false);
|
|
15377
15380
|
useEffect2(() => {
|
|
15378
|
-
const
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
const
|
|
15382
|
-
const
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15381
|
+
const handleMouseMove2 = (e) => {
|
|
15382
|
+
if (!isResizing || !wrapperRef.current) return;
|
|
15383
|
+
const rect = wrapperRef.current.getBoundingClientRect();
|
|
15384
|
+
const newWidth = e.clientX - rect.left;
|
|
15385
|
+
const newHeight = newWidth / aspectRatio;
|
|
15386
|
+
if (newWidth > 50 && newHeight > 50) {
|
|
15387
|
+
updateAttributes({
|
|
15388
|
+
width: Math.round(newWidth),
|
|
15389
|
+
height: Math.round(newHeight)
|
|
15390
|
+
});
|
|
15391
|
+
}
|
|
15392
|
+
};
|
|
15393
|
+
const handleMouseUp = () => {
|
|
15394
|
+
setIsResizing(false);
|
|
15395
|
+
};
|
|
15396
|
+
if (isResizing) {
|
|
15397
|
+
window.addEventListener("mousemove", handleMouseMove2);
|
|
15398
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
15399
|
+
}
|
|
15400
|
+
return () => {
|
|
15401
|
+
window.removeEventListener("mousemove", handleMouseMove2);
|
|
15402
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
15403
|
+
};
|
|
15404
|
+
}, [isResizing, updateAttributes, aspectRatio]);
|
|
15388
15405
|
return /* @__PURE__ */ React.createElement(
|
|
15389
15406
|
NodeViewWrapper,
|
|
15390
15407
|
{
|
|
@@ -15392,31 +15409,55 @@ var ResizableImageComponent = ({
|
|
|
15392
15409
|
contentEditable: false,
|
|
15393
15410
|
className: `resizable-image-wrapper ${selected ? "ProseMirror-selectednode" : ""}`,
|
|
15394
15411
|
style: {
|
|
15395
|
-
|
|
15396
|
-
overflow: "auto",
|
|
15412
|
+
position: "relative",
|
|
15397
15413
|
border: "1px solid #ccc",
|
|
15398
|
-
padding: 2,
|
|
15399
15414
|
display: "inline-block",
|
|
15400
|
-
|
|
15415
|
+
width: `${defaultWidth}px`,
|
|
15416
|
+
height: `${defaultHeight}px`,
|
|
15417
|
+
minWidth: "50px",
|
|
15418
|
+
minHeight: "50px",
|
|
15419
|
+
maxWidth: "100%",
|
|
15420
|
+
userSelect: "none",
|
|
15421
|
+
padding: 2
|
|
15401
15422
|
}
|
|
15402
15423
|
},
|
|
15403
15424
|
/* @__PURE__ */ React.createElement(
|
|
15404
15425
|
"img",
|
|
15405
15426
|
{
|
|
15406
|
-
ref: imgRef,
|
|
15407
15427
|
src,
|
|
15408
15428
|
alt: alt ?? "",
|
|
15409
15429
|
title: title ?? "",
|
|
15410
15430
|
loading: "lazy",
|
|
15411
15431
|
style: {
|
|
15412
|
-
width:
|
|
15413
|
-
height:
|
|
15432
|
+
width: "100%",
|
|
15433
|
+
height: "100%",
|
|
15434
|
+
objectFit: "contain",
|
|
15414
15435
|
display: "block",
|
|
15415
15436
|
userSelect: "none",
|
|
15416
15437
|
pointerEvents: "auto"
|
|
15417
15438
|
},
|
|
15418
15439
|
draggable: false
|
|
15419
15440
|
}
|
|
15441
|
+
),
|
|
15442
|
+
/* @__PURE__ */ React.createElement(
|
|
15443
|
+
"div",
|
|
15444
|
+
{
|
|
15445
|
+
onMouseDown: (e) => {
|
|
15446
|
+
e.preventDefault();
|
|
15447
|
+
setIsResizing(true);
|
|
15448
|
+
},
|
|
15449
|
+
style: {
|
|
15450
|
+
position: "absolute",
|
|
15451
|
+
width: "12px",
|
|
15452
|
+
height: "12px",
|
|
15453
|
+
right: 2,
|
|
15454
|
+
bottom: 2,
|
|
15455
|
+
background: "#ccc",
|
|
15456
|
+
borderRadius: "2px",
|
|
15457
|
+
cursor: "nwse-resize",
|
|
15458
|
+
zIndex: 10
|
|
15459
|
+
}
|
|
15460
|
+
}
|
|
15420
15461
|
)
|
|
15421
15462
|
);
|
|
15422
15463
|
};
|
|
@@ -15428,12 +15469,8 @@ var ResizableImage = Image.extend({
|
|
|
15428
15469
|
addAttributes() {
|
|
15429
15470
|
return {
|
|
15430
15471
|
...this.parent?.(),
|
|
15431
|
-
width: {
|
|
15432
|
-
|
|
15433
|
-
},
|
|
15434
|
-
height: {
|
|
15435
|
-
default: null
|
|
15436
|
-
}
|
|
15472
|
+
width: { default: null },
|
|
15473
|
+
height: { default: null }
|
|
15437
15474
|
};
|
|
15438
15475
|
},
|
|
15439
15476
|
renderHTML({ HTMLAttributes }) {
|
|
@@ -15558,9 +15595,9 @@ var ResizableVideo = Node13.create({
|
|
|
15558
15595
|
});
|
|
15559
15596
|
|
|
15560
15597
|
// src/components/tetrons/toolbar/TableContextMenu.tsx
|
|
15561
|
-
import React3, { useEffect as useEffect4, useState as
|
|
15598
|
+
import React3, { useEffect as useEffect4, useState as useState3 } from "react";
|
|
15562
15599
|
function TableContextMenu({ editor }) {
|
|
15563
|
-
const [menuPosition, setMenuPosition] =
|
|
15600
|
+
const [menuPosition, setMenuPosition] = useState3(null);
|
|
15564
15601
|
useEffect4(() => {
|
|
15565
15602
|
const handleContextMenu = (event) => {
|
|
15566
15603
|
const target = event.target;
|
|
@@ -15644,10 +15681,10 @@ function TableContextMenu({ editor }) {
|
|
|
15644
15681
|
}
|
|
15645
15682
|
|
|
15646
15683
|
// src/components/tetrons/toolbar/TetronsToolbar.tsx
|
|
15647
|
-
import React13, { useEffect as useEffect7, useState as
|
|
15684
|
+
import React13, { useEffect as useEffect7, useState as useState8 } from "react";
|
|
15648
15685
|
|
|
15649
15686
|
// src/components/tetrons/toolbar/ActionGroup.tsx
|
|
15650
|
-
import React5, { useEffect as useEffect5, useRef as useRef3, useState as
|
|
15687
|
+
import React5, { useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
|
|
15651
15688
|
import {
|
|
15652
15689
|
MdZoomIn,
|
|
15653
15690
|
MdZoomOut,
|
|
@@ -15680,7 +15717,7 @@ var ToolbarButton_default = ToolbarButton;
|
|
|
15680
15717
|
|
|
15681
15718
|
// src/components/tetrons/toolbar/ActionGroup.tsx
|
|
15682
15719
|
function ActionGroup({ editor }) {
|
|
15683
|
-
const [dropdownOpen, setDropdownOpen] =
|
|
15720
|
+
const [dropdownOpen, setDropdownOpen] = useState4(false);
|
|
15684
15721
|
const dropdownRef = useRef3(null);
|
|
15685
15722
|
useEffect5(() => {
|
|
15686
15723
|
const handleClickOutside = (event) => {
|
|
@@ -15925,12 +15962,12 @@ import {
|
|
|
15925
15962
|
} from "react-icons/md";
|
|
15926
15963
|
import { ImTextColor } from "react-icons/im";
|
|
15927
15964
|
import { BiSolidColorFill } from "react-icons/bi";
|
|
15928
|
-
import React7, { useEffect as useEffect6, useState as
|
|
15965
|
+
import React7, { useEffect as useEffect6, useState as useState5 } from "react";
|
|
15929
15966
|
function FontStyleGroup({ editor }) {
|
|
15930
|
-
const [textColor, setTextColor] =
|
|
15931
|
-
const [highlightColor, setHighlightColor] =
|
|
15932
|
-
const [fontFamily, setFontFamily] =
|
|
15933
|
-
const [fontSize, setFontSize] =
|
|
15967
|
+
const [textColor, setTextColor] = useState5("#000000");
|
|
15968
|
+
const [highlightColor, setHighlightColor] = useState5("#ffff00");
|
|
15969
|
+
const [fontFamily, setFontFamily] = useState5("Arial");
|
|
15970
|
+
const [fontSize, setFontSize] = useState5("16px");
|
|
15934
15971
|
useEffect6(() => {
|
|
15935
15972
|
if (!editor) return;
|
|
15936
15973
|
const updateStates = () => {
|
|
@@ -16105,7 +16142,7 @@ function FontStyleGroup({ editor }) {
|
|
|
16105
16142
|
}
|
|
16106
16143
|
|
|
16107
16144
|
// src/components/tetrons/toolbar/InsertGroup.tsx
|
|
16108
|
-
import React8, { useRef as useRef4, useState as
|
|
16145
|
+
import React8, { useRef as useRef4, useState as useState6 } from "react";
|
|
16109
16146
|
import {
|
|
16110
16147
|
MdTableChart,
|
|
16111
16148
|
MdInsertPhoto,
|
|
@@ -16118,12 +16155,12 @@ import {
|
|
|
16118
16155
|
} from "react-icons/md";
|
|
16119
16156
|
import Picker from "@emoji-mart/react";
|
|
16120
16157
|
function InsertGroup({ editor }) {
|
|
16121
|
-
const [showTableGrid, setShowTableGrid] =
|
|
16122
|
-
const [selectedRows, setSelectedRows] =
|
|
16123
|
-
const [selectedCols, setSelectedCols] =
|
|
16158
|
+
const [showTableGrid, setShowTableGrid] = useState6(false);
|
|
16159
|
+
const [selectedRows, setSelectedRows] = useState6(1);
|
|
16160
|
+
const [selectedCols, setSelectedCols] = useState6(1);
|
|
16124
16161
|
const imageInputRef = useRef4(null);
|
|
16125
16162
|
const videoInputRef = useRef4(null);
|
|
16126
|
-
const [showPicker, setShowPicker] =
|
|
16163
|
+
const [showPicker, setShowPicker] = useState6(false);
|
|
16127
16164
|
const addEmoji = (emoji) => {
|
|
16128
16165
|
editor.chain().focus().insertContent(emoji.native).run();
|
|
16129
16166
|
setShowPicker(false);
|
|
@@ -16584,19 +16621,19 @@ function FileGroup({ editor }) {
|
|
|
16584
16621
|
}
|
|
16585
16622
|
|
|
16586
16623
|
// src/components/tetrons/toolbar/AIGroup.tsx
|
|
16587
|
-
import React12, { useState as
|
|
16624
|
+
import React12, { useState as useState7, useRef as useRef6 } from "react";
|
|
16588
16625
|
import { FaMicrophone, FaStop } from "react-icons/fa";
|
|
16589
16626
|
import { Waveform } from "@uiball/loaders";
|
|
16590
16627
|
import { motion, AnimatePresence } from "framer-motion";
|
|
16591
16628
|
function AiGroup({ editor }) {
|
|
16592
|
-
const [isRecording, setIsRecording] =
|
|
16593
|
-
const [audioBlob, setAudioBlob] =
|
|
16594
|
-
const [isTranscribing, setIsTranscribing] =
|
|
16595
|
-
const [transcriptionError, setTranscriptionError] =
|
|
16596
|
-
const [showPromptInput, setShowPromptInput] =
|
|
16597
|
-
const [prompt2, setPrompt] =
|
|
16598
|
-
const [isLoadingAI, setIsLoadingAI] =
|
|
16599
|
-
const [aiError, setAiError] =
|
|
16629
|
+
const [isRecording, setIsRecording] = useState7(false);
|
|
16630
|
+
const [audioBlob, setAudioBlob] = useState7(null);
|
|
16631
|
+
const [isTranscribing, setIsTranscribing] = useState7(false);
|
|
16632
|
+
const [transcriptionError, setTranscriptionError] = useState7("");
|
|
16633
|
+
const [showPromptInput, setShowPromptInput] = useState7(false);
|
|
16634
|
+
const [prompt2, setPrompt] = useState7("");
|
|
16635
|
+
const [isLoadingAI, setIsLoadingAI] = useState7(false);
|
|
16636
|
+
const [aiError, setAiError] = useState7("");
|
|
16600
16637
|
const mediaRecorderRef = useRef6(null);
|
|
16601
16638
|
const chunksRef = useRef6([]);
|
|
16602
16639
|
const startRecording = async () => {
|
|
@@ -16750,7 +16787,7 @@ function TetronsToolbar({
|
|
|
16750
16787
|
editor,
|
|
16751
16788
|
version
|
|
16752
16789
|
}) {
|
|
16753
|
-
const [autoSave, setAutoSave] =
|
|
16790
|
+
const [autoSave, setAutoSave] = useState8(false);
|
|
16754
16791
|
useEffect7(() => {
|
|
16755
16792
|
if (!editor) return;
|
|
16756
16793
|
const handleUpdate = () => {
|
|
@@ -16784,11 +16821,11 @@ lowlight.register("js", javascript);
|
|
|
16784
16821
|
lowlight.register("ts", typescript);
|
|
16785
16822
|
function EditorContent({ apiKey }) {
|
|
16786
16823
|
const typo = useTypo();
|
|
16787
|
-
const [isValid, setIsValid] =
|
|
16788
|
-
const [error, setError] =
|
|
16789
|
-
const [versions, setVersions] =
|
|
16790
|
-
const [userVersion, setUserVersion] =
|
|
16791
|
-
const [currentVersionIndex, setCurrentVersionIndex] =
|
|
16824
|
+
const [isValid, setIsValid] = useState9(null);
|
|
16825
|
+
const [error, setError] = useState9(null);
|
|
16826
|
+
const [versions, setVersions] = useState9([]);
|
|
16827
|
+
const [userVersion, setUserVersion] = useState9(null);
|
|
16828
|
+
const [currentVersionIndex, setCurrentVersionIndex] = useState9(
|
|
16792
16829
|
null
|
|
16793
16830
|
);
|
|
16794
16831
|
const wrapperRef = useRef7(null);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tetrons",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.28",
|
|
4
4
|
"description": "A Next.js project written in TypeScript",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "next dev --turbo",
|
|
9
|
-
"build": "tsup
|
|
9
|
+
"build": "tsup && copyfiles -u 2 src/styles/*.css dist/styles && copyfiles -u 2 src/components/**/*.tsx dist/components",
|
|
10
10
|
"start": "next start",
|
|
11
11
|
"lint": "next lint"
|
|
12
12
|
},
|
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
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
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;
|