@cyberyozh/text-editor 0.3.2 → 0.4.0

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.
@@ -158,6 +158,40 @@
158
158
  padding: 24px;
159
159
  }
160
160
 
161
+ /* src/editor/components/dialog-error/dialog-error.css */
162
+ .dialog-error {
163
+ position: relative;
164
+ padding: 8px 12px;
165
+ border: 1px solid var(--ui-error-border);
166
+ border-radius: 4px;
167
+ background-color: var(--ui-error-bg);
168
+ color: var(--ui-error-text);
169
+ font-size: 13px;
170
+ }
171
+ .dialog-error-list {
172
+ margin: 0;
173
+ padding-left: 16px;
174
+ }
175
+ .dialog-error-clear-button {
176
+ display: flex;
177
+ position: absolute;
178
+ top: 8px;
179
+ right: 8px;
180
+ align-items: center;
181
+ justify-content: center;
182
+ width: 20px;
183
+ height: 20px;
184
+ padding: 0;
185
+ border: none;
186
+ background-color: transparent;
187
+ cursor: pointer;
188
+ }
189
+ .dialog-error-clear-button svg {
190
+ width: 14px;
191
+ height: 14px;
192
+ color: var(--ui-error-text);
193
+ }
194
+
161
195
  /* src/editor/dialogs/add-katex-dialog/add-katex-dialog.css */
162
196
  .katex-dialog-title {
163
197
  margin: 0 0 16px 0;
@@ -298,14 +332,6 @@
298
332
  .link-dialog-input::placeholder {
299
333
  color: var(--tiptap-color-text-placeholder);
300
334
  }
301
- .link-dialog-error {
302
- padding: 8px 12px;
303
- border: 1px solid var(--ui-error-border);
304
- border-radius: 4px;
305
- background-color: var(--ui-error-bg);
306
- color: var(--ui-error-text);
307
- font-size: 13px;
308
- }
309
335
  .link-dialog-actions {
310
336
  display: flex;
311
337
  justify-content: flex-end;
@@ -423,14 +449,6 @@
423
449
  .video-dialog-platform-list li {
424
450
  margin: 4px 0;
425
451
  }
426
- .video-dialog-error {
427
- padding: 8px 12px;
428
- border: 1px solid var(--ui-error-border);
429
- border-radius: 4px;
430
- background-color: var(--ui-error-bg);
431
- color: var(--ui-error-text);
432
- font-size: 13px;
433
- }
434
452
  .video-dialog-actions {
435
453
  display: flex;
436
454
  justify-content: flex-end;
@@ -563,34 +581,8 @@
563
581
  background: var(--ui-secondary-bg);
564
582
  color: var(--ui-text-primary);
565
583
  }
566
- .image-dialog-error {
567
- position: relative;
584
+ .image-dialog-error-spacing {
568
585
  margin-bottom: 16px;
569
- padding: 8px 12px;
570
- border: 1px solid var(--ui-error-border);
571
- border-radius: 4px;
572
- background-color: var(--ui-error-bg);
573
- color: var(--ui-error-text);
574
- font-size: 13px;
575
- }
576
- .image-dialog-error-clear-button {
577
- display: flex;
578
- position: absolute;
579
- top: 8px;
580
- right: 8px;
581
- align-items: center;
582
- justify-content: center;
583
- width: 20px;
584
- height: 20px;
585
- padding: 0;
586
- border: none;
587
- background-color: transparent;
588
- cursor: pointer;
589
- }
590
- .image-dialog-error-clear-button svg {
591
- width: 14px;
592
- height: 14px;
593
- color: var(--ui-error-text);
594
586
  }
595
587
 
596
588
  /* src/styles/components.css */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberyozh/text-editor",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Rich text editor library based on Tiptap for CyberYozh",
5
5
  "author": "CyberYozh",
6
6
  "license": "MIT",
@@ -0,0 +1,35 @@
1
+ .dialog-error {
2
+ position: relative;
3
+ padding: 8px 12px;
4
+ border: 1px solid var(--ui-error-border);
5
+ border-radius: 4px;
6
+ background-color: var(--ui-error-bg);
7
+ color: var(--ui-error-text);
8
+ font-size: 13px;
9
+ }
10
+
11
+ .dialog-error-list {
12
+ margin: 0;
13
+ padding-left: 16px;
14
+ }
15
+
16
+ .dialog-error-clear-button {
17
+ display: flex;
18
+ position: absolute;
19
+ top: 8px;
20
+ right: 8px;
21
+ align-items: center;
22
+ justify-content: center;
23
+ width: 20px;
24
+ height: 20px;
25
+ padding: 0;
26
+ border: none;
27
+ background-color: transparent;
28
+ cursor: pointer;
29
+ }
30
+
31
+ .dialog-error-clear-button svg {
32
+ width: 14px;
33
+ height: 14px;
34
+ color: var(--ui-error-text);
35
+ }
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import { isCombinedError } from "@/utils";
4
+
5
+ import { TiptapX } from "../../../icons";
6
+ import { useTiptapTranslations } from "../../context";
7
+ import { useMemo } from "react";
8
+
9
+ interface DialogErrorProps {
10
+ error: unknown;
11
+ onClose?: () => void;
12
+ className?: string;
13
+ }
14
+
15
+ export const DialogError = ({ error, onClose, className }: DialogErrorProps) => {
16
+ const translations = useTiptapTranslations();
17
+
18
+ const renderedMessage = useMemo(() => {
19
+ if (typeof error === "string") {
20
+ return error || translations.common.unknownError;
21
+ }
22
+
23
+ if (isCombinedError(error)) {
24
+ const safeErrors = error.cause.filter((e) => Boolean(e.message));
25
+ const errorsToDisplay =
26
+ safeErrors.length === error.cause.length
27
+ ? safeErrors
28
+ : [new Error(translations.common.unknownError), ...safeErrors];
29
+
30
+ if (errorsToDisplay.length === 1) {
31
+ return errorsToDisplay[0].message;
32
+ }
33
+
34
+ return (
35
+ <ul className="dialog-error-list">
36
+ {errorsToDisplay.map((cause, index) => (
37
+ <li key={`${index}_${cause.message}`}>{cause.message}</li>
38
+ ))}
39
+ </ul>
40
+ );
41
+ }
42
+
43
+ if (error instanceof Error) {
44
+ return error.message || translations.common.unknownError;
45
+ }
46
+
47
+ return translations.common.unknownError;
48
+ }, [error, translations]);
49
+
50
+ return (
51
+ <div className={`dialog-error ${className || ""}`}>
52
+ {renderedMessage}
53
+ {onClose && (
54
+ <button
55
+ type="button"
56
+ className="dialog-error-clear-button"
57
+ onClick={onClose}
58
+ aria-label={translations.common.clearError}
59
+ >
60
+ <TiptapX />
61
+ </button>
62
+ )}
63
+ </div>
64
+ );
65
+ };
@@ -0,0 +1 @@
1
+ export { DialogError } from './dialog-error';
@@ -1,3 +1,4 @@
1
1
  export { Button } from './button';
2
2
  export { Input } from './input';
3
3
  export { CommonModal } from './common-modal';
4
+ export { DialogError } from './dialog-error';
@@ -48,15 +48,6 @@
48
48
  color: var(--tiptap-color-text-placeholder);
49
49
  }
50
50
 
51
- .link-dialog-error {
52
- padding: 8px 12px;
53
- border: 1px solid var(--ui-error-border);
54
- border-radius: 4px;
55
- background-color: var(--ui-error-bg);
56
- color: var(--ui-error-text);
57
- font-size: 13px;
58
- }
59
-
60
51
  .link-dialog-actions {
61
52
  display: flex;
62
53
  justify-content: flex-end;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ChangeEvent, useEffect, useRef, useState } from "react";
4
4
 
5
- import { CommonModal } from "../../components";
5
+ import { CommonModal, DialogError } from "../../components";
6
6
  import { useTiptapTranslations } from "../../context";
7
7
  import { LinkDialogContextProps } from "@/types/link";
8
8
 
@@ -186,7 +186,7 @@ export const AddLinkDialog = ({
186
186
  <p className="link-dialog-help-text">{translations.link.addNofollowHelp}</p>
187
187
  </div>
188
188
 
189
- {error && <div className="link-dialog-error">{error}</div>}
189
+ {error && <DialogError error={error} />}
190
190
  </div>
191
191
 
192
192
  <div className="link-dialog-actions">
@@ -83,15 +83,6 @@
83
83
  margin: 4px 0;
84
84
  }
85
85
 
86
- .video-dialog-error {
87
- padding: 8px 12px;
88
- border: 1px solid var(--ui-error-border);
89
- border-radius: 4px;
90
- background-color: var(--ui-error-bg);
91
- color: var(--ui-error-text);
92
- font-size: 13px;
93
- }
94
-
95
86
  .video-dialog-actions {
96
87
  display: flex;
97
88
  justify-content: flex-end;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ChangeEvent, useEffect, useRef, useState } from "react";
4
4
 
5
- import { CommonModal } from "../../components";
5
+ import { CommonModal, DialogError } from "../../components";
6
6
  import { detectVideoHosting, VideoHosting } from "../../../utils/detect-video-hosting";
7
7
  import { useTiptapTranslations } from "../../context";
8
8
 
@@ -128,7 +128,7 @@ export const AddVideoDialog = ({ open, onClose, onVideoAdd }: AddVideoDialogProp
128
128
  </ul>
129
129
  </div>
130
130
 
131
- {error && <div className="video-dialog-error">{error}</div>}
131
+ {error && <DialogError error={error} />}
132
132
  </div>
133
133
 
134
134
  <div className="video-dialog-actions">
@@ -101,34 +101,6 @@
101
101
  color: var(--ui-text-primary);
102
102
  }
103
103
 
104
- .image-dialog-error {
105
- position: relative;
104
+ .image-dialog-error-spacing {
106
105
  margin-bottom: 16px;
107
- padding: 8px 12px;
108
- border: 1px solid var(--ui-error-border);
109
- border-radius: 4px;
110
- background-color: var(--ui-error-bg);
111
- color: var(--ui-error-text);
112
- font-size: 13px;
113
- }
114
-
115
- .image-dialog-error-clear-button {
116
- display: flex;
117
- position: absolute;
118
- top: 8px;
119
- right: 8px;
120
- align-items: center;
121
- justify-content: center;
122
- width: 20px;
123
- height: 20px;
124
- padding: 0;
125
- border: none;
126
- background-color: transparent;
127
- cursor: pointer;
128
- }
129
-
130
- .image-dialog-error-clear-button svg {
131
- width: 14px;
132
- height: 14px;
133
- color: var(--ui-error-text);
134
106
  }
@@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
4
4
 
5
5
  import { TiptapX } from "../../../icons";
6
6
 
7
- import { Button, CommonModal, Input } from "../../components";
7
+ import { Button, CommonModal, DialogError, Input } from "../../components";
8
8
  import { ImageUploadHandler, TiptapImageAttrs, ImageGetHandler, FigureAttrs } from "../../../types";
9
9
  import { resolveImageSrc } from "../../../utils/image-src-resolver";
10
10
  import { useTiptapTranslations } from "../../context";
@@ -219,7 +219,7 @@ export const ImageDialog = ({
219
219
 
220
220
  handleClose();
221
221
  } catch (error) {
222
- setFileUploadError(error instanceof Error ? error : new Error(translations.imageDialog.unknownError));
222
+ setFileUploadError(error instanceof Error ? error : new Error(translations.common.unknownError));
223
223
  } finally {
224
224
  setIsUploading(false);
225
225
  }
@@ -334,17 +334,11 @@ export const ImageDialog = ({
334
334
  </div>
335
335
  </div>
336
336
  {fileUploadError && (
337
- <div className="image-dialog-error">
338
- {fileUploadError.message || translations.imageDialog.unknownError}
339
- <button
340
- type="button"
341
- className="image-dialog-error-clear-button"
342
- onClick={() => setFileUploadError(null)}
343
- aria-label={translations.imageDialog.clearError}
344
- >
345
- <TiptapX />
346
- </button>
347
- </div>
337
+ <DialogError
338
+ error={fileUploadError}
339
+ onClose={() => setFileUploadError(null)}
340
+ className="image-dialog-error-spacing"
341
+ />
348
342
  )}
349
343
  <div className="image-dialog-actions">
350
344
  <Button
@@ -2,3 +2,4 @@ export { Tiptap, type TiptapProps, type KatexDialogContextProps } from "./editor
2
2
  export type { SupportedLanguage as TiptapSupportedLanguages } from "../utils/get-translations";
3
3
  export { type JSONContent } from "@tiptap/react";
4
4
  export { htmlToTiptapContent } from "../utils/html-to-tiptap-content";
5
+ export { type CombinedErrors as TipTapCombinedErrors } from '@/types/common'
@@ -2,6 +2,7 @@
2
2
  @import "../editor/components/button/button.css";
3
3
  @import "../editor/components/input/input.css";
4
4
  @import "../editor/components/common-modal/common-modal.css";
5
+ @import "../editor/components/dialog-error/dialog-error.css";
5
6
 
6
7
  /* Dialog Styles */
7
8
  @import "../editor/dialogs/add-katex-dialog/add-katex-dialog.css";
@@ -113,9 +113,7 @@
113
113
  "captionPlaceholder": "Description under image...",
114
114
  "save": "Save",
115
115
  "add": "Add",
116
- "invalidFileType": "File is not an image",
117
- "unknownError": "Unknown error",
118
- "clearError": "Clear error"
116
+ "invalidFileType": "Invalid file format."
119
117
  },
120
118
  "videoUpload": {
121
119
  "title": "Add video",
@@ -160,6 +158,8 @@
160
158
  "words": ["word", "words", "words"]
161
159
  },
162
160
  "common": {
163
- "cancel": "Cancel"
161
+ "cancel": "Cancel",
162
+ "unknownError": "Unknown error",
163
+ "clearError": "Clear error"
164
164
  }
165
165
  }
@@ -113,9 +113,7 @@
113
113
  "captionPlaceholder": "Описание под изображением...",
114
114
  "save": "Сохранить",
115
115
  "add": "Добавить",
116
- "invalidFileType": "Файл не является изображением",
117
- "unknownError": "Неизвестная ошибка",
118
- "clearError": "Сбросить ошибку"
116
+ "invalidFileType": "Неверный формат файла."
119
117
  },
120
118
  "videoUpload": {
121
119
  "title": "Добавить видео",
@@ -160,6 +158,8 @@
160
158
  "words": ["слово", "слова", "слов"]
161
159
  },
162
160
  "common": {
163
- "cancel": "Отменить"
161
+ "cancel": "Отменить",
162
+ "unknownError": "Неизвестная ошибка",
163
+ "clearError": "Сбросить ошибку"
164
164
  }
165
165
  }
@@ -0,0 +1,3 @@
1
+ export interface CombinedErrors extends Error {
2
+ cause: Array<Error>
3
+ }
@@ -1 +1,2 @@
1
+ export * from './common';
1
2
  export * from './image-handler';
@@ -1,2 +1,3 @@
1
1
  export { copyToClipboard } from './copy-to-clipboard';
2
2
  export { pluralize } from './pluralize';
3
+ export { isCombinedError } from './is-combined-error';
@@ -0,0 +1,9 @@
1
+ import { CombinedErrors } from "@/types";
2
+
3
+ export function isCombinedError(error: unknown): error is CombinedErrors {
4
+ return (
5
+ error instanceof Error &&
6
+ Array.isArray(error?.cause) &&
7
+ error.cause.length > 0 && error.cause.every((e) => e instanceof Error)
8
+ );
9
+ }