tetrons 2.3.23 → 2.3.26

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.
@@ -0,0 +1,2 @@
1
+ import "../styles/tetrons.css";
2
+ export default function Home(): import("react").JSX.Element;
@@ -1,12 +1,9 @@
1
1
  "use client";
2
2
 
3
- import React from "react";
4
- import { Comment } from "./toolbar/extensions/Comment";
5
- import { useEffect, useRef } from "react";
3
+ import React, { useEffect, useRef, useState } from "react";
6
4
  import {
7
5
  useEditor,
8
6
  EditorContent as TiptapEditorContent,
9
- Editor,
10
7
  } from "@tiptap/react";
11
8
 
12
9
  import Document from "@tiptap/extension-document";
@@ -22,41 +19,40 @@ import Blockquote from "@tiptap/extension-blockquote";
22
19
  import HardBreak from "@tiptap/extension-hard-break";
23
20
  import Heading from "@tiptap/extension-heading";
24
21
  import HorizontalRule from "@tiptap/extension-horizontal-rule";
25
-
26
22
  import TextAlign from "@tiptap/extension-text-align";
27
23
  import Color from "@tiptap/extension-color";
28
24
  import Highlight from "@tiptap/extension-highlight";
29
25
  import Image from "@tiptap/extension-image";
30
26
  import Link from "@tiptap/extension-link";
31
27
  import TextStyle from "@tiptap/extension-text-style";
32
-
33
28
  import ListItem from "@tiptap/extension-list-item";
34
29
  import BulletList from "@tiptap/extension-bullet-list";
35
30
  import OrderedList from "@tiptap/extension-ordered-list";
36
- import { Subscript } from "./toolbar/marks/Subscript";
37
- import { Superscript } from "./toolbar/marks/Superscript";
38
-
39
- import { ResizableTable } from "./toolbar/extensions/ResizableTable";
40
- import { Embed } from "./toolbar/extensions/Embed";
41
31
  import TableRow from "@tiptap/extension-table-row";
42
32
  import TableCell from "@tiptap/extension-table-cell";
43
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";
44
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";
45
48
  import { FontFamily } from "./toolbar/extensions/FontFamily";
46
49
  import { FontSize } from "./toolbar/extensions/FontSize";
47
- import TetronsToolbar from "./toolbar/TetronsToolbar";
48
-
49
50
  import { ResizableImage } from "./ResizableImage";
50
51
  import { ResizableVideo } from "./ResizableVideo";
51
52
  import TableContextMenu from "./toolbar/TableContextMenu";
52
- import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
53
- import { createLowlight } from "lowlight";
54
-
55
- import js from "highlight.js/lib/languages/javascript";
56
- import ts from "highlight.js/lib/languages/typescript";
53
+ import TetronsToolbar from "./toolbar/TetronsToolbar";
57
54
 
58
55
  const lowlight = createLowlight();
59
-
60
56
  lowlight.register("js", js);
61
57
  lowlight.register("ts", ts);
62
58
 
@@ -65,16 +61,19 @@ type EditorContentProps = {
65
61
  };
66
62
 
67
63
  export default function EditorContent({ apiKey }: EditorContentProps) {
68
- const [isValid, setIsValid] = React.useState<boolean | null>(null);
69
- const [error, setError] = React.useState<string | null>(null);
70
- const [versions, setVersions] = React.useState<string[]>([]);
71
- const [userVersion, setUserVersion] = React.useState<
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<
72
70
  "free" | "pro" | "premium" | "platinum" | null
73
71
  >(null);
72
+ const [currentVersionIndex, setCurrentVersionIndex] = useState<number | null>(
73
+ null
74
+ );
74
75
 
75
- const [currentVersionIndex, setCurrentVersionIndex] = React.useState<
76
- number | null
77
- >(null);
76
+ const wrapperRef = useRef<HTMLDivElement>(null);
78
77
 
79
78
  function getApiBaseUrl(): string {
80
79
  if (
@@ -92,7 +91,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
92
91
  return "https://staging.tetrons.com";
93
92
  }
94
93
 
95
- const API_BASE_URL = getApiBaseUrl();
94
+ const API_BASE_URL = getApiBaseUrl();
96
95
 
97
96
  useEffect(() => {
98
97
  const validateKey = async () => {
@@ -103,7 +102,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
103
102
  "Content-Type": "application/json",
104
103
  },
105
104
  body: JSON.stringify({ apiKey }),
106
- });
105
+ });
107
106
 
108
107
  const data = await res.json();
109
108
  if (!res.ok) throw new Error(data.error || "Invalid API Key");
@@ -111,19 +110,15 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
111
110
  setIsValid(true);
112
111
  setUserVersion(data.version);
113
112
  } catch (err: unknown) {
114
- if (err instanceof Error) {
115
- setError(err.message || "Invalid API Key");
116
- } else {
117
- setError("Invalid API Key");
118
- }
113
+ setError(err instanceof Error ? err.message : "Invalid API Key");
119
114
  setIsValid(false);
120
115
  }
121
116
  };
122
117
 
123
118
  validateKey();
124
- }, [apiKey]);
119
+ }, [apiKey, API_BASE_URL]);
125
120
 
126
- const editor: Editor | null = useEditor({
121
+ const editor = useEditor({
127
122
  extensions: [
128
123
  Document,
129
124
  Paragraph,
@@ -138,35 +133,28 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
138
133
  HardBreak,
139
134
  Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
140
135
  HorizontalRule,
141
-
142
136
  TextStyle,
143
137
  Color,
144
138
  Highlight.configure({ multicolor: true }),
145
139
  FontFamily,
146
140
  FontSize,
147
141
  TextAlign.configure({ types: ["heading", "paragraph"] }),
148
-
149
142
  ListItem,
150
143
  BulletList,
151
144
  OrderedList,
152
145
  Subscript,
153
146
  Superscript,
154
-
155
147
  Image,
156
148
  Link.configure({
157
149
  openOnClick: false,
158
150
  autolink: true,
159
151
  linkOnPaste: true,
160
152
  }),
161
-
162
- ResizableTable.configure({
163
- resizable: true,
164
- }),
153
+ ResizableTable.configure({ resizable: true }),
165
154
  TableRow,
166
155
  TableCell,
167
156
  TableHeader,
168
157
  Embed,
169
-
170
158
  ResizableImage,
171
159
  ResizableVideo,
172
160
  Comment,
@@ -176,6 +164,13 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
176
164
  class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
177
165
  },
178
166
  }),
167
+ ...(typo
168
+ ? [
169
+ Spellcheck.configure({
170
+ spellcheckFn: (word: string) => typo.check(word),
171
+ }),
172
+ ]
173
+ : []),
179
174
  ],
180
175
  content: "",
181
176
  editorProps: {
@@ -186,9 +181,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
186
181
  },
187
182
  immediatelyRender: false,
188
183
  });
189
-
190
- const wrapperRef = useRef<HTMLDivElement>(null);
191
-
184
+
192
185
  useEffect(() => {
193
186
  return () => {
194
187
  editor?.destroy();
@@ -215,7 +208,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
215
208
  editor.commands.setContent(JSON.parse(versionContent));
216
209
  setCurrentVersionIndex(index);
217
210
  }
218
- };
211
+ };
219
212
 
220
213
  if (isValid === false) {
221
214
  return <div className="editor-error">⚠️ {error}</div>;
@@ -225,6 +218,10 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
225
218
  return <div className="editor-loading">🔍 Validating license...</div>;
226
219
  }
227
220
 
221
+ if (!typo) {
222
+ return <div className="editor-loading">📖 Loading dictionary...</div>;
223
+ }
224
+
228
225
  return (
229
226
  <div className="editor-container">
230
227
  {userVersion !== "free" && (
@@ -239,22 +236,23 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
239
236
  </button>
240
237
 
241
238
  <div className="editor-versions-wrapper">
242
- {versions.length === 0 && (
239
+ {versions.length === 0 ? (
243
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
+ ))
244
255
  )}
245
- {versions.map((_, idx) => (
246
- <button
247
- type="button"
248
- key={idx}
249
- onClick={() => restoreVersion(idx)}
250
- className={`editor-version-btn ${
251
- idx === currentVersionIndex ? "active" : ""
252
- }`}
253
- title={`Restore Version ${idx + 1}`}
254
- >
255
- {`V${idx + 1}`}
256
- </button>
257
- ))}
258
256
  </div>
259
257
  </div>
260
258
  )}
@@ -271,7 +269,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
271
269
  {editor ? (
272
270
  <>
273
271
  <TiptapEditorContent editor={editor} />
274
- {editor && <TableContextMenu editor={editor} />}
272
+ <TableContextMenu editor={editor} />
275
273
  </>
276
274
  ) : (
277
275
  <div className="editor-loading">Loading editor...</div>
@@ -0,0 +1,6 @@
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,12 +1,9 @@
1
1
  "use client";
2
2
 
3
- import React from "react";
4
- import { Comment } from "./toolbar/extensions/Comment";
5
- import { useEffect, useRef } from "react";
3
+ import React, { useEffect, useRef, useState } from "react";
6
4
  import {
7
5
  useEditor,
8
6
  EditorContent as TiptapEditorContent,
9
- Editor,
10
7
  } from "@tiptap/react";
11
8
 
12
9
  import Document from "@tiptap/extension-document";
@@ -22,41 +19,40 @@ import Blockquote from "@tiptap/extension-blockquote";
22
19
  import HardBreak from "@tiptap/extension-hard-break";
23
20
  import Heading from "@tiptap/extension-heading";
24
21
  import HorizontalRule from "@tiptap/extension-horizontal-rule";
25
-
26
22
  import TextAlign from "@tiptap/extension-text-align";
27
23
  import Color from "@tiptap/extension-color";
28
24
  import Highlight from "@tiptap/extension-highlight";
29
25
  import Image from "@tiptap/extension-image";
30
26
  import Link from "@tiptap/extension-link";
31
27
  import TextStyle from "@tiptap/extension-text-style";
32
-
33
28
  import ListItem from "@tiptap/extension-list-item";
34
29
  import BulletList from "@tiptap/extension-bullet-list";
35
30
  import OrderedList from "@tiptap/extension-ordered-list";
36
- import { Subscript } from "./toolbar/marks/Subscript";
37
- import { Superscript } from "./toolbar/marks/Superscript";
38
-
39
- import { ResizableTable } from "./toolbar/extensions/ResizableTable";
40
- import { Embed } from "./toolbar/extensions/Embed";
41
31
  import TableRow from "@tiptap/extension-table-row";
42
32
  import TableCell from "@tiptap/extension-table-cell";
43
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";
44
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";
45
48
  import { FontFamily } from "./toolbar/extensions/FontFamily";
46
49
  import { FontSize } from "./toolbar/extensions/FontSize";
47
- import TetronsToolbar from "./toolbar/TetronsToolbar";
48
-
49
50
  import { ResizableImage } from "./ResizableImage";
50
51
  import { ResizableVideo } from "./ResizableVideo";
51
52
  import TableContextMenu from "./toolbar/TableContextMenu";
52
- import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
53
- import { createLowlight } from "lowlight";
54
-
55
- import js from "highlight.js/lib/languages/javascript";
56
- import ts from "highlight.js/lib/languages/typescript";
53
+ import TetronsToolbar from "./toolbar/TetronsToolbar";
57
54
 
58
55
  const lowlight = createLowlight();
59
-
60
56
  lowlight.register("js", js);
61
57
  lowlight.register("ts", ts);
62
58
 
@@ -65,16 +61,19 @@ type EditorContentProps = {
65
61
  };
66
62
 
67
63
  export default function EditorContent({ apiKey }: EditorContentProps) {
68
- const [isValid, setIsValid] = React.useState<boolean | null>(null);
69
- const [error, setError] = React.useState<string | null>(null);
70
- const [versions, setVersions] = React.useState<string[]>([]);
71
- const [userVersion, setUserVersion] = React.useState<
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<
72
70
  "free" | "pro" | "premium" | "platinum" | null
73
71
  >(null);
72
+ const [currentVersionIndex, setCurrentVersionIndex] = useState<number | null>(
73
+ null
74
+ );
74
75
 
75
- const [currentVersionIndex, setCurrentVersionIndex] = React.useState<
76
- number | null
77
- >(null);
76
+ const wrapperRef = useRef<HTMLDivElement>(null);
78
77
 
79
78
  function getApiBaseUrl(): string {
80
79
  if (
@@ -92,7 +91,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
92
91
  return "https://staging.tetrons.com";
93
92
  }
94
93
 
95
- const API_BASE_URL = getApiBaseUrl();
94
+ const API_BASE_URL = getApiBaseUrl();
96
95
 
97
96
  useEffect(() => {
98
97
  const validateKey = async () => {
@@ -103,7 +102,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
103
102
  "Content-Type": "application/json",
104
103
  },
105
104
  body: JSON.stringify({ apiKey }),
106
- });
105
+ });
107
106
 
108
107
  const data = await res.json();
109
108
  if (!res.ok) throw new Error(data.error || "Invalid API Key");
@@ -111,19 +110,15 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
111
110
  setIsValid(true);
112
111
  setUserVersion(data.version);
113
112
  } catch (err: unknown) {
114
- if (err instanceof Error) {
115
- setError(err.message || "Invalid API Key");
116
- } else {
117
- setError("Invalid API Key");
118
- }
113
+ setError(err instanceof Error ? err.message : "Invalid API Key");
119
114
  setIsValid(false);
120
115
  }
121
116
  };
122
117
 
123
118
  validateKey();
124
- }, [apiKey]);
119
+ }, [apiKey, API_BASE_URL]);
125
120
 
126
- const editor: Editor | null = useEditor({
121
+ const editor = useEditor({
127
122
  extensions: [
128
123
  Document,
129
124
  Paragraph,
@@ -138,35 +133,28 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
138
133
  HardBreak,
139
134
  Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
140
135
  HorizontalRule,
141
-
142
136
  TextStyle,
143
137
  Color,
144
138
  Highlight.configure({ multicolor: true }),
145
139
  FontFamily,
146
140
  FontSize,
147
141
  TextAlign.configure({ types: ["heading", "paragraph"] }),
148
-
149
142
  ListItem,
150
143
  BulletList,
151
144
  OrderedList,
152
145
  Subscript,
153
146
  Superscript,
154
-
155
147
  Image,
156
148
  Link.configure({
157
149
  openOnClick: false,
158
150
  autolink: true,
159
151
  linkOnPaste: true,
160
152
  }),
161
-
162
- ResizableTable.configure({
163
- resizable: true,
164
- }),
153
+ ResizableTable.configure({ resizable: true }),
165
154
  TableRow,
166
155
  TableCell,
167
156
  TableHeader,
168
157
  Embed,
169
-
170
158
  ResizableImage,
171
159
  ResizableVideo,
172
160
  Comment,
@@ -176,6 +164,13 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
176
164
  class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
177
165
  },
178
166
  }),
167
+ ...(typo
168
+ ? [
169
+ Spellcheck.configure({
170
+ spellcheckFn: (word: string) => typo.check(word),
171
+ }),
172
+ ]
173
+ : []),
179
174
  ],
180
175
  content: "",
181
176
  editorProps: {
@@ -186,9 +181,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
186
181
  },
187
182
  immediatelyRender: false,
188
183
  });
189
-
190
- const wrapperRef = useRef<HTMLDivElement>(null);
191
-
184
+
192
185
  useEffect(() => {
193
186
  return () => {
194
187
  editor?.destroy();
@@ -215,7 +208,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
215
208
  editor.commands.setContent(JSON.parse(versionContent));
216
209
  setCurrentVersionIndex(index);
217
210
  }
218
- };
211
+ };
219
212
 
220
213
  if (isValid === false) {
221
214
  return <div className="editor-error">⚠️ {error}</div>;
@@ -225,6 +218,10 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
225
218
  return <div className="editor-loading">🔍 Validating license...</div>;
226
219
  }
227
220
 
221
+ if (!typo) {
222
+ return <div className="editor-loading">📖 Loading dictionary...</div>;
223
+ }
224
+
228
225
  return (
229
226
  <div className="editor-container">
230
227
  {userVersion !== "free" && (
@@ -239,22 +236,23 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
239
236
  </button>
240
237
 
241
238
  <div className="editor-versions-wrapper">
242
- {versions.length === 0 && (
239
+ {versions.length === 0 ? (
243
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
+ ))
244
255
  )}
245
- {versions.map((_, idx) => (
246
- <button
247
- type="button"
248
- key={idx}
249
- onClick={() => restoreVersion(idx)}
250
- className={`editor-version-btn ${
251
- idx === currentVersionIndex ? "active" : ""
252
- }`}
253
- title={`Restore Version ${idx + 1}`}
254
- >
255
- {`V${idx + 1}`}
256
- </button>
257
- ))}
258
256
  </div>
259
257
  </div>
260
258
  )}
@@ -271,7 +269,7 @@ export default function EditorContent({ apiKey }: EditorContentProps) {
271
269
  {editor ? (
272
270
  <>
273
271
  <TiptapEditorContent editor={editor} />
274
- {editor && <TableContextMenu editor={editor} />}
272
+ <TableContextMenu editor={editor} />
275
273
  </>
276
274
  ) : (
277
275
  <div className="editor-loading">Loading editor...</div>
@@ -0,0 +1 @@
1
+ export declare const ResizableImage: import("@tiptap/react").Node<import("@tiptap/extension-image").ImageOptions, any>;
@@ -0,0 +1,36 @@
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
+ });
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { ReactNodeViewProps } from "@tiptap/react";
3
+ declare const ResizableImageComponent: React.FC<ReactNodeViewProps<HTMLElement>>;
4
+ export default ResizableImageComponent;
@@ -0,0 +1,37 @@
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;
@@ -0,0 +1,50 @@
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
+ });