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.
Files changed (51) hide show
  1. package/dist/app/api/ai-action/route.d.mts +9 -0
  2. package/dist/app/api/ai-action/route.mjs +36 -0
  3. package/dist/app/api/export/route.d.mts +3 -0
  4. package/dist/app/api/export/route.mjs +8 -0
  5. package/dist/app/api/register/route.d.mts +10 -0
  6. package/dist/app/api/register/route.mjs +85 -0
  7. package/dist/app/api/save/route.d.mts +9 -0
  8. package/dist/app/api/save/route.mjs +18 -0
  9. package/dist/app/api/transcribe/route.d.mts +10 -0
  10. package/dist/app/api/transcribe/route.mjs +46 -0
  11. package/dist/app/api/validate/route.d.mts +13 -0
  12. package/dist/app/api/validate/route.mjs +107 -0
  13. package/dist/components/tetrons/ResizableImageComponent.tsx +64 -29
  14. package/dist/index.mjs +93 -56
  15. package/package.json +2 -2
  16. package/dist/app/page.d.ts +0 -2
  17. package/dist/components/components/UI/Button.tsx +0 -0
  18. package/dist/components/components/UI/Dropdown.tsx +0 -0
  19. package/dist/components/components/tetrons/EditorContent.tsx +0 -280
  20. package/dist/components/components/tetrons/ResizableImageComponent.tsx +0 -77
  21. package/dist/components/components/tetrons/ResizableVideoComponent.tsx +0 -56
  22. package/dist/components/tetrons/EditorContent.d.ts +0 -6
  23. package/dist/components/tetrons/ResizableImage.d.ts +0 -1
  24. package/dist/components/tetrons/ResizableImage.js +0 -36
  25. package/dist/components/tetrons/ResizableImage.ts +0 -39
  26. package/dist/components/tetrons/ResizableImageComponent.d.ts +0 -4
  27. package/dist/components/tetrons/ResizableImageComponent.jsx +0 -37
  28. package/dist/components/tetrons/ResizableVideo.ts +0 -66
  29. package/dist/components/tetrons/extensions/Spellcheck.ts +0 -50
  30. package/dist/components/tetrons/helpers.ts +0 -0
  31. package/dist/components/tetrons/toolbar/AIGroup.tsx +0 -209
  32. package/dist/components/tetrons/toolbar/ActionGroup.tsx +0 -218
  33. package/dist/components/tetrons/toolbar/ClipboardGroup.tsx +0 -58
  34. package/dist/components/tetrons/toolbar/FileGroup.tsx +0 -66
  35. package/dist/components/tetrons/toolbar/FontStyleGroup.tsx +0 -194
  36. package/dist/components/tetrons/toolbar/InsertGroup.tsx +0 -267
  37. package/dist/components/tetrons/toolbar/ListAlignGroup.tsx +0 -69
  38. package/dist/components/tetrons/toolbar/MiscGroup.tsx +0 -104
  39. package/dist/components/tetrons/toolbar/TableContextMenu.tsx +0 -91
  40. package/dist/components/tetrons/toolbar/TetronsToolbar.tsx +0 -77
  41. package/dist/components/tetrons/toolbar/ToolbarButton.tsx +0 -36
  42. package/dist/components/tetrons/toolbar/extensions/Comment.ts +0 -72
  43. package/dist/components/tetrons/toolbar/extensions/Embed.ts +0 -113
  44. package/dist/components/tetrons/toolbar/extensions/FontFamily.ts +0 -43
  45. package/dist/components/tetrons/toolbar/extensions/FontSize.ts +0 -43
  46. package/dist/components/tetrons/toolbar/extensions/ResizableTable.ts +0 -16
  47. package/dist/components/tetrons/toolbar/marks/Subscript.ts +0 -45
  48. package/dist/components/tetrons/toolbar/marks/Superscript.ts +0 -45
  49. package/dist/index.d.ts +0 -6
  50. package/dist/index.js +0 -16975
  51. package/dist/styles/styles/tetrons.css +0 -563
@@ -1,56 +0,0 @@
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;
@@ -1,6 +0,0 @@
1
- import React from "react";
2
- type EditorContentProps = {
3
- apiKey: string;
4
- };
5
- export default function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
6
- export {};
@@ -1 +0,0 @@
1
- export declare const ResizableImage: import("@tiptap/react").Node<import("@tiptap/extension-image").ImageOptions, any>;
@@ -1,36 +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 Image from "@tiptap/extension-image";
13
- import { ReactNodeViewRenderer } from "@tiptap/react";
14
- import ResizableImageComponent from "./ResizableImageComponent";
15
- export const ResizableImage = Image.extend({
16
- name: "resizableImage",
17
- addAttributes() {
18
- var _a;
19
- return Object.assign(Object.assign({}, (_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)), { width: { default: null }, height: { default: null } });
20
- },
21
- renderHTML({ HTMLAttributes }) {
22
- const { width, height } = HTMLAttributes, rest = __rest(HTMLAttributes, ["width", "height"]);
23
- const style = [];
24
- if (width)
25
- style.push(`width: ${width}px`);
26
- if (height)
27
- style.push(`height: ${height}px`);
28
- return [
29
- "img",
30
- Object.assign(Object.assign({}, rest), { style: style.join("; ") }),
31
- ];
32
- },
33
- addNodeView() {
34
- return ReactNodeViewRenderer(ResizableImageComponent);
35
- },
36
- });
@@ -1,39 +0,0 @@
1
- import Image from "@tiptap/extension-image";
2
- import { ReactNodeViewRenderer } from "@tiptap/react";
3
- import ResizableImageComponent from "./ResizableImageComponent";
4
-
5
- export const ResizableImage = Image.extend({
6
- name: "resizableImage",
7
-
8
- addAttributes() {
9
- return {
10
- ...this.parent?.(),
11
- width: {
12
- default: null,
13
- },
14
- height: {
15
- default: null,
16
- },
17
- };
18
- },
19
-
20
- renderHTML({ HTMLAttributes }) {
21
- const { width, height, ...rest } = HTMLAttributes;
22
- const style = [];
23
-
24
- if (width) style.push(`width: ${width}px`);
25
- if (height) style.push(`height: ${height}px`);
26
-
27
- return [
28
- "img",
29
- {
30
- ...rest,
31
- style: style.join("; "),
32
- },
33
- ];
34
- },
35
-
36
- addNodeView() {
37
- return ReactNodeViewRenderer(ResizableImageComponent);
38
- },
39
- });
@@ -1,4 +0,0 @@
1
- import React from "react";
2
- import { ReactNodeViewProps } from "@tiptap/react";
3
- declare const ResizableImageComponent: React.FC<ReactNodeViewProps<HTMLElement>>;
4
- export default ResizableImageComponent;
@@ -1,37 +0,0 @@
1
- import React, { useRef, useEffect } from "react";
2
- import { NodeViewWrapper } from "@tiptap/react";
3
- const ResizableImageComponent = ({ node, updateAttributes, selected, }) => {
4
- const { src, alt, title, width, height } = node.attrs;
5
- const wrapperRef = useRef(null);
6
- const imgRef = useRef(null);
7
- useEffect(() => {
8
- const img = imgRef.current;
9
- if (!img)
10
- return;
11
- const observer = new ResizeObserver(() => {
12
- const w = Math.round(img.offsetWidth);
13
- const h = Math.round(img.offsetHeight);
14
- updateAttributes({ width: w, height: h });
15
- });
16
- observer.observe(img);
17
- return () => observer.disconnect();
18
- }, [updateAttributes]);
19
- return (<NodeViewWrapper ref={wrapperRef} contentEditable={false} className={`resizable-image-wrapper ${selected ? "ProseMirror-selectednode" : ""}`} style={{
20
- resize: "both",
21
- overflow: "auto",
22
- border: "1px solid #ccc",
23
- padding: 2,
24
- display: "inline-block",
25
- maxWidth: "100%",
26
- }}>
27
- {/* eslint-disable-next-line @next/next/no-img-element */}
28
- <img ref={imgRef} src={src} alt={alt !== null && alt !== void 0 ? alt : ""} title={title !== null && title !== void 0 ? title : ""} loading="lazy" style={{
29
- width: width ? `${width}px` : "auto",
30
- height: height ? `${height}px` : "auto",
31
- display: "block",
32
- userSelect: "none",
33
- pointerEvents: "auto",
34
- }} draggable={false}/>
35
- </NodeViewWrapper>);
36
- };
37
- export default ResizableImageComponent;
@@ -1,66 +0,0 @@
1
- import { Node } from "@tiptap/core";
2
- import { ReactNodeViewRenderer } from "@tiptap/react";
3
- import ResizableVideoComponent from "./ResizableVideoComponent";
4
-
5
- declare module "@tiptap/core" {
6
- interface Commands<ReturnType> {
7
- video: {
8
- setVideo: (options: { src: string; controls?: boolean }) => ReturnType;
9
- };
10
- }
11
- }
12
-
13
- export const ResizableVideo = Node.create({
14
- name: "video",
15
- group: "block",
16
- draggable: true,
17
- atom: true,
18
-
19
- addAttributes() {
20
- return {
21
- src: { default: null },
22
- controls: {
23
- default: true,
24
- parseHTML: (element) => element.hasAttribute("controls"),
25
- renderHTML: (attributes) =>
26
- 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
-
40
- renderHTML({ HTMLAttributes }) {
41
- const { width, height, ...rest } = HTMLAttributes;
42
- const style = [];
43
-
44
- if (width) style.push(`width: ${width}px`);
45
- if (height) style.push(`height: ${height}px`);
46
-
47
- return ["video", { ...rest, style: style.join("; ") }];
48
- },
49
-
50
- addCommands() {
51
- return {
52
- setVideo:
53
- (attributes) =>
54
- ({ commands }) => {
55
- return commands.insertContent({
56
- type: this.name,
57
- attrs: attributes,
58
- });
59
- },
60
- };
61
- },
62
-
63
- addNodeView() {
64
- return ReactNodeViewRenderer(ResizableVideoComponent);
65
- },
66
- });
@@ -1,50 +0,0 @@
1
- import { Mark, markInputRule } from "@tiptap/core";
2
-
3
- export interface SpellcheckOptions {
4
- spellcheckFn: (word: string) => boolean;
5
- }
6
-
7
- const wordMatchRegex = () => /(?:^|\s)([a-zA-Z]{2,})(?=\s|$)/g;
8
-
9
- export const Spellcheck = Mark.create<SpellcheckOptions>({
10
- name: "spellcheck",
11
-
12
- addOptions() {
13
- return {
14
- spellcheckFn: () => true,
15
- };
16
- },
17
-
18
- addInputRules() {
19
- return [
20
- markInputRule({
21
- find: wordMatchRegex(),
22
- type: this.type,
23
- getAttributes: (match) =>
24
- this.options.spellcheckFn(match[1])
25
- ? false
26
- : { "data-spellcheck": "true" },
27
- }),
28
- ];
29
- },
30
-
31
- parseHTML() {
32
- return [
33
- {
34
- tag: "span[data-spellcheck]",
35
- },
36
- ];
37
- },
38
-
39
- renderHTML({ HTMLAttributes }) {
40
- return [
41
- "span",
42
- {
43
- ...HTMLAttributes,
44
- style: "text-decoration: red wavy underline;",
45
- "data-spellcheck": "true",
46
- },
47
- 0,
48
- ];
49
- },
50
- });
File without changes
@@ -1,209 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState, useRef } from "react";
4
- import { Editor } from "@tiptap/react";
5
- import { FaMicrophone, FaStop } from "react-icons/fa";
6
- import { Waveform } from "@uiball/loaders";
7
- import { motion, AnimatePresence } from "framer-motion";
8
-
9
- export default function AiGroup({ editor }: { editor: Editor }) {
10
- const [isRecording, setIsRecording] = useState(false);
11
- const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
12
- const [isTranscribing, setIsTranscribing] = useState(false);
13
- const [transcriptionError, setTranscriptionError] = useState("");
14
-
15
- const [showPromptInput, setShowPromptInput] = useState(false);
16
- const [prompt, setPrompt] = useState("");
17
- const [isLoadingAI, setIsLoadingAI] = useState(false);
18
- const [aiError, setAiError] = useState("");
19
-
20
- const mediaRecorderRef = useRef<MediaRecorder | null>(null);
21
- const chunksRef = useRef<BlobPart[]>([]);
22
-
23
- const startRecording = async () => {
24
- setTranscriptionError("");
25
- setAudioBlob(null);
26
-
27
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
28
- const mediaRecorder = new MediaRecorder(stream);
29
- mediaRecorderRef.current = mediaRecorder;
30
- chunksRef.current = [];
31
-
32
- mediaRecorder.ondataavailable = (e) => {
33
- if (e.data.size > 0) chunksRef.current.push(e.data);
34
- };
35
-
36
- mediaRecorder.onstop = () => {
37
- const blob = new Blob(chunksRef.current, { type: "audio/webm" });
38
- setAudioBlob(blob);
39
- transcribeAudio(blob);
40
- };
41
-
42
- mediaRecorder.start();
43
- setIsRecording(true);
44
- };
45
-
46
- const stopRecording = () => {
47
- mediaRecorderRef.current?.stop();
48
- setIsRecording(false);
49
- };
50
-
51
- const transcribeAudio = async (blob: Blob) => {
52
- setIsTranscribing(true);
53
- setTranscriptionError("");
54
-
55
- const formData = new FormData();
56
- formData.append("file", blob, "voice.webm");
57
-
58
- try {
59
- const res = await fetch("/api/transcribe", {
60
- method: "POST",
61
- body: formData,
62
- });
63
-
64
- const data = await res.json();
65
- if (!res.ok || !data.transcript) {
66
- throw new Error(data.error || "Failed to transcribe");
67
- }
68
-
69
- editor.commands.insertContent(data.transcript);
70
- } catch (e) {
71
- console.error(e);
72
- setTranscriptionError("Transcription failed. Please try again.");
73
- } finally {
74
- setIsTranscribing(false);
75
- }
76
- };
77
-
78
- const handleAiClick = () => {
79
- setShowPromptInput(true);
80
- setPrompt("");
81
- setAiError("");
82
- };
83
-
84
- const handlePromptSubmit = async () => {
85
- if (!prompt.trim()) return;
86
- setIsLoadingAI(true);
87
- setAiError("");
88
-
89
- try {
90
- const res = await fetch("/api/ai-action", {
91
- method: "POST",
92
- headers: { "Content-Type": "application/json" },
93
- body: JSON.stringify({ content: prompt }),
94
- });
95
-
96
- const data = await res.json();
97
-
98
- if (!res.ok || !data.response) {
99
- throw new Error(data.error || "AI failed to generate content");
100
- }
101
-
102
- editor.commands.insertContent(data.response);
103
- setShowPromptInput(false);
104
- } catch (e) {
105
- console.error(e);
106
- setAiError("Failed to generate content. Try again.");
107
- } finally {
108
- setIsLoadingAI(false);
109
- }
110
- };
111
-
112
- return (
113
- <div className="group relative space-y-3">
114
- <div className="flex gap-2 items-center">
115
- {!isRecording ? (
116
- <button
117
- type="button"
118
- onClick={startRecording}
119
- className="icon-btn"
120
- title="Start Voice Input"
121
- >
122
- <FaMicrophone size={18} />
123
- </button>
124
- ) : (
125
- <button
126
- type="button"
127
- onClick={stopRecording}
128
- className="icon-btn stop-btn"
129
- title="Stop Recording"
130
- >
131
- <FaStop size={18} />
132
- </button>
133
- )}
134
-
135
- <button
136
- type="button"
137
- onClick={handleAiClick}
138
- className="ai-button"
139
- title="AI Assist"
140
- >
141
- AI
142
- </button>
143
- </div>
144
-
145
- {isRecording && (
146
- <div className="flex flex-col items-center">
147
- <Waveform size={30} lineWeight={3.5} speed={1} color="#4F46E5" />
148
- <p className="text-sm mt-1 text-gray-600">Recording...</p>
149
- </div>
150
- )}
151
-
152
- {isTranscribing && (
153
- <p className="text-sm text-gray-500">Transcribing...</p>
154
- )}
155
-
156
- {transcriptionError && (
157
- <p className="text-sm text-red-600">{transcriptionError}</p>
158
- )}
159
-
160
- {audioBlob && (
161
- <div className="mt-2">
162
- <audio controls src={URL.createObjectURL(audioBlob)} />
163
- </div>
164
- )}
165
-
166
- <AnimatePresence>
167
- {showPromptInput && (
168
- <motion.div
169
- className="ai-modal-backdrop"
170
- initial={{ opacity: 0 }}
171
- animate={{ opacity: 1 }}
172
- exit={{ opacity: 0 }}
173
- >
174
- <motion.div
175
- className="ai-modal-content"
176
- initial={{ scale: 0.9, opacity: 0 }}
177
- animate={{ scale: 1, opacity: 1 }}
178
- exit={{ scale: 0.9, opacity: 0 }}
179
- >
180
- <h2 className="ai-modal-title">AI Prompt</h2>
181
- <textarea
182
- className="ai-modal-textarea"
183
- value={prompt}
184
- onChange={(e) => setPrompt(e.target.value)}
185
- placeholder="Enter your prompt here..."
186
- />
187
- {aiError && <p className="ai-modal-error">{aiError}</p>}
188
- <div className="ai-modal-actions">
189
- <button
190
- onClick={() => setShowPromptInput(false)}
191
- className="ai-cancel-btn"
192
- >
193
- Cancel
194
- </button>
195
- <button
196
- onClick={handlePromptSubmit}
197
- disabled={isLoadingAI}
198
- className="ai-submit-btn"
199
- >
200
- {isLoadingAI ? "Generating..." : "Submit"}
201
- </button>
202
- </div>
203
- </motion.div>
204
- </motion.div>
205
- )}
206
- </AnimatePresence>
207
- </div>
208
- );
209
- }