@alpaca-editor/core 1.0.4018 → 1.0.4027

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 (179) hide show
  1. package/dist/components/SimpleLanguageSelector.d.ts +2 -1
  2. package/dist/components/SimpleLanguageSelector.js +9 -2
  3. package/dist/components/SimpleLanguageSelector.js.map +1 -1
  4. package/dist/components/ui/input.js +1 -1
  5. package/dist/components/ui/input.js.map +1 -1
  6. package/dist/components/ui/tooltip.d.ts +3 -1
  7. package/dist/components/ui/tooltip.js +2 -2
  8. package/dist/components/ui/tooltip.js.map +1 -1
  9. package/dist/config/config.js +4 -0
  10. package/dist/config/config.js.map +1 -1
  11. package/dist/config/types.d.ts +2 -0
  12. package/dist/editor/ContentTree.js +1 -1
  13. package/dist/editor/ContentTree.js.map +1 -1
  14. package/dist/editor/ContextMenu.js +26 -0
  15. package/dist/editor/ContextMenu.js.map +1 -1
  16. package/dist/editor/FieldHistory.js +1 -1
  17. package/dist/editor/FieldHistory.js.map +1 -1
  18. package/dist/editor/FieldListField.js +2 -2
  19. package/dist/editor/FieldListField.js.map +1 -1
  20. package/dist/editor/Terminal.js +3 -1
  21. package/dist/editor/Terminal.js.map +1 -1
  22. package/dist/editor/ai/Agents.js +37 -18
  23. package/dist/editor/ai/Agents.js.map +1 -1
  24. package/dist/editor/ai/AiResponseMessage.js +71 -5
  25. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  26. package/dist/editor/ai/AiTerminal.d.ts +3 -1
  27. package/dist/editor/ai/AiTerminal.js +53 -24
  28. package/dist/editor/ai/AiTerminal.js.map +1 -1
  29. package/dist/editor/ai/AiToolCall.js +3 -3
  30. package/dist/editor/ai/AiToolCall.js.map +1 -1
  31. package/dist/editor/ai/EditorAiTerminal.d.ts +2 -1
  32. package/dist/editor/ai/EditorAiTerminal.js +2 -2
  33. package/dist/editor/ai/EditorAiTerminal.js.map +1 -1
  34. package/dist/editor/client/EditorClient.js +5 -1
  35. package/dist/editor/client/EditorClient.js.map +1 -1
  36. package/dist/editor/client/editContext.d.ts +2 -0
  37. package/dist/editor/client/editContext.js.map +1 -1
  38. package/dist/editor/client/operations.d.ts +1 -0
  39. package/dist/editor/client/operations.js +7 -0
  40. package/dist/editor/client/operations.js.map +1 -1
  41. package/dist/editor/commands/componentCommands.js +1 -1
  42. package/dist/editor/commands/componentCommands.js.map +1 -1
  43. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  44. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  45. package/dist/editor/field-types/MultiLineText.js +1 -1
  46. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  47. package/dist/editor/field-types/PictureFieldEditor.js +1 -1
  48. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  49. package/dist/editor/field-types/RawEditor.js +1 -1
  50. package/dist/editor/field-types/RawEditor.js.map +1 -1
  51. package/dist/editor/field-types/RichTextEditorComponent.js +16 -17
  52. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  53. package/dist/editor/field-types/SingleLineText.js +1 -1
  54. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  55. package/dist/editor/field-types/richtext/components/SimpleDropdown.d.ts +18 -0
  56. package/dist/editor/field-types/richtext/components/SimpleDropdown.js +71 -0
  57. package/dist/editor/field-types/richtext/components/SimpleDropdown.js.map +1 -0
  58. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.d.ts +5 -0
  59. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js +359 -0
  60. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js.map +1 -0
  61. package/dist/editor/field-types/richtext/components/SimpleToolbar.d.ts +16 -0
  62. package/dist/editor/field-types/richtext/components/SimpleToolbar.js +181 -0
  63. package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -0
  64. package/dist/editor/field-types/richtext/components/SimpleToolbarButton.d.ts +9 -0
  65. package/dist/editor/field-types/richtext/components/SimpleToolbarButton.js +14 -0
  66. package/dist/editor/field-types/richtext/components/SimpleToolbarButton.js.map +1 -0
  67. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +4 -0
  68. package/dist/editor/field-types/richtext/contextMenuFactory.js +193 -0
  69. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -0
  70. package/dist/editor/field-types/richtext/index.d.ts +6 -5
  71. package/dist/editor/field-types/richtext/index.js +6 -5
  72. package/dist/editor/field-types/richtext/index.js.map +1 -1
  73. package/dist/editor/field-types/richtext/types.d.ts +16 -16
  74. package/dist/editor/field-types/richtext/types.js +84 -84
  75. package/dist/editor/field-types/richtext/types.js.map +1 -1
  76. package/dist/editor/page-editor-chrome/CommentHighlighting.js +1 -1
  77. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  78. package/dist/editor/page-editor-chrome/FrameMenu.js +5 -5
  79. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  80. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +1 -1
  81. package/dist/editor/page-viewer/PageViewerFrame.js +3 -2
  82. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  83. package/dist/editor/services/agentService.d.ts +14 -4
  84. package/dist/editor/services/agentService.js.map +1 -1
  85. package/dist/editor/services/aiService.d.ts +1 -0
  86. package/dist/editor/services/aiService.js +1 -0
  87. package/dist/editor/services/aiService.js.map +1 -1
  88. package/dist/page-wizard/PageWizard.d.ts +2 -0
  89. package/dist/page-wizard/PageWizard.js +6 -13
  90. package/dist/page-wizard/PageWizard.js.map +1 -1
  91. package/dist/page-wizard/WizardSteps.js +3 -1
  92. package/dist/page-wizard/WizardSteps.js.map +1 -1
  93. package/dist/page-wizard/service.d.ts +1 -0
  94. package/dist/page-wizard/service.js +7 -0
  95. package/dist/page-wizard/service.js.map +1 -1
  96. package/dist/page-wizard/steps/ContentStep.js +210 -33
  97. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  98. package/dist/page-wizard/steps/FindItemsStep.js +11 -3
  99. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
  100. package/dist/page-wizard/steps/LayoutStep.js +1 -1
  101. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  102. package/dist/page-wizard/steps/MetaDataStep.js +1 -1
  103. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  104. package/dist/page-wizard/steps/SchottSelectImagesStep.d.ts +2 -0
  105. package/dist/page-wizard/steps/SchottSelectImagesStep.js +55 -0
  106. package/dist/page-wizard/steps/SchottSelectImagesStep.js.map +1 -0
  107. package/dist/page-wizard/steps/StructureStep.js +20 -5
  108. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  109. package/dist/page-wizard/steps/TranslateStep.d.ts +2 -0
  110. package/dist/page-wizard/steps/TranslateStep.js +413 -0
  111. package/dist/page-wizard/steps/TranslateStep.js.map +1 -0
  112. package/dist/page-wizard/utils/dataAccessor.d.ts +7 -0
  113. package/dist/page-wizard/utils/dataAccessor.js +76 -0
  114. package/dist/page-wizard/utils/dataAccessor.js.map +1 -1
  115. package/dist/revision.d.ts +2 -2
  116. package/dist/revision.js +2 -2
  117. package/dist/splash-screen/NewPage.js +5 -4
  118. package/dist/splash-screen/NewPage.js.map +1 -1
  119. package/dist/splash-screen/OpenPage.js +2 -1
  120. package/dist/splash-screen/OpenPage.js.map +1 -1
  121. package/dist/splash-screen/RecentPages.js +3 -1
  122. package/dist/splash-screen/RecentPages.js.map +1 -1
  123. package/dist/styles.css +57 -0
  124. package/package.json +5 -4
  125. package/src/components/SimpleLanguageSelector.tsx +11 -1
  126. package/src/components/ui/input.tsx +1 -1
  127. package/src/components/ui/tooltip.tsx +3 -2
  128. package/src/config/config.tsx +4 -0
  129. package/src/config/types.ts +6 -0
  130. package/src/editor/ContentTree.tsx +1 -1
  131. package/src/editor/ContextMenu.tsx +39 -0
  132. package/src/editor/FieldHistory.tsx +1 -1
  133. package/src/editor/FieldListField.tsx +2 -6
  134. package/src/editor/Terminal.tsx +5 -1
  135. package/src/editor/ai/Agents.tsx +90 -46
  136. package/src/editor/ai/AiResponseMessage.tsx +185 -24
  137. package/src/editor/ai/AiTerminal.tsx +104 -50
  138. package/src/editor/ai/AiToolCall.tsx +14 -4
  139. package/src/editor/ai/EditorAiTerminal.tsx +3 -0
  140. package/src/editor/client/EditorClient.tsx +9 -1
  141. package/src/editor/client/editContext.ts +2 -0
  142. package/src/editor/client/operations.ts +9 -0
  143. package/src/editor/commands/componentCommands.tsx +1 -1
  144. package/src/editor/field-types/ImageFieldEditor.tsx +1 -0
  145. package/src/editor/field-types/MultiLineText.tsx +1 -0
  146. package/src/editor/field-types/PictureFieldEditor.tsx +1 -0
  147. package/src/editor/field-types/RawEditor.tsx +1 -0
  148. package/src/editor/field-types/RichTextEditorComponent.tsx +27 -25
  149. package/src/editor/field-types/SingleLineText.tsx +1 -0
  150. package/src/editor/field-types/richtext/components/SimpleDropdown.tsx +165 -0
  151. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.css +261 -0
  152. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.tsx +505 -0
  153. package/src/editor/field-types/richtext/components/SimpleToolbar.tsx +362 -0
  154. package/src/editor/field-types/richtext/components/SimpleToolbarButton.tsx +36 -0
  155. package/src/editor/field-types/richtext/contextMenuFactory.tsx +264 -0
  156. package/src/editor/field-types/richtext/index.ts +6 -5
  157. package/src/editor/field-types/richtext/types.ts +168 -148
  158. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +1 -1
  159. package/src/editor/page-editor-chrome/FrameMenu.tsx +16 -11
  160. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
  161. package/src/editor/page-viewer/PageViewerFrame.tsx +4 -3
  162. package/src/editor/services/agentService.ts +16 -5
  163. package/src/editor/services/aiService.ts +4 -0
  164. package/src/page-wizard/PageWizard.tsx +10 -13
  165. package/src/page-wizard/WizardSteps.tsx +3 -1
  166. package/src/page-wizard/service.ts +11 -0
  167. package/src/page-wizard/steps/ContentStep.tsx +376 -43
  168. package/src/page-wizard/steps/FindItemsStep.tsx +23 -3
  169. package/src/page-wizard/steps/LayoutStep.tsx +1 -1
  170. package/src/page-wizard/steps/MetaDataStep.tsx +1 -1
  171. package/src/page-wizard/steps/SchottSelectImagesStep.tsx +141 -0
  172. package/src/page-wizard/steps/StructureStep.tsx +40 -5
  173. package/src/page-wizard/steps/TranslateStep.tsx +772 -0
  174. package/src/page-wizard/utils/dataAccessor.ts +85 -0
  175. package/src/revision.ts +2 -2
  176. package/src/splash-screen/NewPage.tsx +18 -3
  177. package/src/splash-screen/OpenPage.tsx +14 -1
  178. package/src/splash-screen/RecentPages.tsx +4 -2
  179. package/tsconfig.build.json +1 -0
@@ -0,0 +1,505 @@
1
+ import React, {
2
+ useCallback,
3
+ useRef,
4
+ useState,
5
+ useEffect,
6
+ forwardRef,
7
+ } from "react";
8
+ import {
9
+ ReactSlateProps,
10
+ RichTextEditorProfile,
11
+ SLATE_MARKS,
12
+ SLATE_BLOCKS,
13
+ SLATE_ALIGNMENTS,
14
+ } from "../types";
15
+ import { SimpleToolbar } from "./SimpleToolbar";
16
+ import { classNames } from "primereact/utils";
17
+ import "./SimpleRichTextEditor.css";
18
+
19
+ interface SimpleRichTextEditorState {
20
+ selection: Range | null;
21
+ activeFormats: Set<string>;
22
+ currentBlock: string;
23
+ currentAlignment: string;
24
+ }
25
+
26
+ export const SimpleRichTextEditor = forwardRef<HTMLDivElement, ReactSlateProps>(
27
+ (props, ref) => {
28
+ const {
29
+ value = "",
30
+ onChange,
31
+ onFocus,
32
+ onBlur,
33
+ readOnly = false,
34
+ placeholder = "Enter some text...",
35
+ profile,
36
+ } = props;
37
+
38
+ const editorRef = useRef<HTMLDivElement>(null);
39
+ const [state, setState] = useState<SimpleRichTextEditorState>({
40
+ selection: null,
41
+ activeFormats: new Set(),
42
+ currentBlock: "paragraph",
43
+ currentAlignment: "left",
44
+ });
45
+
46
+ const editorProfile = profile || {
47
+ toolbar: {
48
+ groups: [],
49
+ },
50
+ };
51
+
52
+ // Update content when value prop changes (only when editor is not focused)
53
+ useEffect(() => {
54
+ if (
55
+ editorRef.current &&
56
+ editorRef.current.innerHTML !== value &&
57
+ document.activeElement !== editorRef.current
58
+ ) {
59
+ editorRef.current.innerHTML = value || "";
60
+ }
61
+ }, [value]);
62
+
63
+ // Initialize content on mount
64
+ useEffect(() => {
65
+ if (editorRef.current && !editorRef.current.innerHTML && value) {
66
+ editorRef.current.innerHTML = value;
67
+ }
68
+ }, []);
69
+
70
+ // Save current selection
71
+ const saveSelection = useCallback((): Range | null => {
72
+ const selection = window.getSelection();
73
+ if (selection && selection.rangeCount > 0) {
74
+ return selection.getRangeAt(0);
75
+ }
76
+ return null;
77
+ }, []);
78
+
79
+ // Restore selection
80
+ const restoreSelection = useCallback((range: Range) => {
81
+ const selection = window.getSelection();
82
+ if (selection) {
83
+ selection.removeAllRanges();
84
+ selection.addRange(range);
85
+ }
86
+ }, []);
87
+
88
+ // Update editor state based on current selection
89
+ const updateEditorState = useCallback(() => {
90
+ if (!editorRef.current) return;
91
+
92
+ const selection = window.getSelection();
93
+ if (!selection || selection.rangeCount === 0) return;
94
+
95
+ const range = selection.getRangeAt(0);
96
+ const activeFormats = new Set<string>();
97
+
98
+ // Check for active marks
99
+ Object.keys(SLATE_MARKS).forEach((markId) => {
100
+ const markConfig = SLATE_MARKS[markId];
101
+ if (document.queryCommandState(getExecCommandForMark(markId))) {
102
+ activeFormats.add(markId);
103
+ }
104
+ });
105
+
106
+ // Check current block type
107
+ let currentBlock = "paragraph";
108
+ const parentElement =
109
+ range.commonAncestorContainer.nodeType === Node.TEXT_NODE
110
+ ? range.commonAncestorContainer.parentElement
111
+ : (range.commonAncestorContainer as Element);
112
+
113
+ if (parentElement) {
114
+ const blockElement = parentElement.closest(
115
+ "h1, h2, h3, h4, h5, h6, p, div",
116
+ );
117
+ if (blockElement) {
118
+ switch (blockElement.tagName.toLowerCase()) {
119
+ case "h1":
120
+ currentBlock = "heading-1";
121
+ break;
122
+ case "h2":
123
+ currentBlock = "heading-2";
124
+ break;
125
+ case "h3":
126
+ currentBlock = "heading-3";
127
+ break;
128
+ case "h4":
129
+ currentBlock = "heading-4";
130
+ break;
131
+ case "h5":
132
+ currentBlock = "heading-5";
133
+ break;
134
+ case "h6":
135
+ currentBlock = "heading-6";
136
+ break;
137
+ case "p":
138
+ currentBlock = "paragraph";
139
+ break;
140
+ default:
141
+ currentBlock = "no-tag";
142
+ break;
143
+ }
144
+ }
145
+ }
146
+
147
+ // Check current alignment
148
+ let currentAlignment = "left";
149
+ const computedStyle = window.getComputedStyle(
150
+ parentElement || editorRef.current,
151
+ );
152
+ const textAlign = computedStyle.textAlign;
153
+ if (["center", "right", "justify"].includes(textAlign)) {
154
+ currentAlignment = textAlign;
155
+ }
156
+
157
+ setState({
158
+ selection: range,
159
+ activeFormats,
160
+ currentBlock,
161
+ currentAlignment,
162
+ });
163
+ }, []);
164
+
165
+ // Get execCommand equivalent for marks
166
+ const getExecCommandForMark = (markId: string): string => {
167
+ switch (markId) {
168
+ case "bold":
169
+ return "bold";
170
+ case "italic":
171
+ return "italic";
172
+ case "underline":
173
+ return "underline";
174
+ case "strikethrough":
175
+ return "strikeThrough";
176
+ case "subscript":
177
+ return "subscript";
178
+ case "superscript":
179
+ return "superscript";
180
+ default:
181
+ return "";
182
+ }
183
+ };
184
+
185
+ // Apply mark formatting
186
+ const toggleMark = useCallback(
187
+ (markId: string) => {
188
+ if (readOnly) return;
189
+
190
+ editorRef.current?.focus();
191
+ const command = getExecCommandForMark(markId);
192
+
193
+ if (command) {
194
+ document.execCommand(command, false);
195
+ } else if (markId === "extrabold") {
196
+ // Custom handling for extrabold
197
+ const selection = window.getSelection();
198
+ if (selection && selection.rangeCount > 0) {
199
+ const range = selection.getRangeAt(0);
200
+ const selectedText = range.toString();
201
+
202
+ if (selectedText) {
203
+ const span = document.createElement("span");
204
+ span.className = "extrabold";
205
+ span.textContent = selectedText;
206
+ range.deleteContents();
207
+ range.insertNode(span);
208
+
209
+ // Move cursor after the inserted span
210
+ range.setStartAfter(span);
211
+ range.collapse(true);
212
+ selection.removeAllRanges();
213
+ selection.addRange(range);
214
+ }
215
+ }
216
+ }
217
+
218
+ setTimeout(() => {
219
+ updateEditorState();
220
+ if (onChange && editorRef.current) {
221
+ onChange(editorRef.current.innerHTML);
222
+ }
223
+ }, 0);
224
+ },
225
+ [readOnly, onChange, updateEditorState],
226
+ );
227
+
228
+ // Apply block formatting
229
+ const toggleBlock = useCallback(
230
+ (blockId: string) => {
231
+ if (readOnly) return;
232
+
233
+ editorRef.current?.focus();
234
+
235
+ if (blockId === "no-tag") {
236
+ document.execCommand("formatBlock", false, "div");
237
+ } else {
238
+ const blockConfig = SLATE_BLOCKS[blockId];
239
+ if (blockConfig && blockConfig.htmlTag !== "no-tag") {
240
+ document.execCommand(
241
+ "formatBlock",
242
+ false,
243
+ `<${blockConfig.htmlTag}>`,
244
+ );
245
+ }
246
+ }
247
+
248
+ setTimeout(() => {
249
+ updateEditorState();
250
+ if (onChange && editorRef.current) {
251
+ onChange(editorRef.current.innerHTML);
252
+ }
253
+ }, 0);
254
+ },
255
+ [readOnly, onChange, updateEditorState],
256
+ );
257
+
258
+ // Apply alignment
259
+ const toggleAlign = useCallback(
260
+ (alignment: string) => {
261
+ if (readOnly) return;
262
+
263
+ editorRef.current?.focus();
264
+
265
+ let command = "";
266
+ switch (alignment) {
267
+ case "left":
268
+ command = "justifyLeft";
269
+ break;
270
+ case "center":
271
+ command = "justifyCenter";
272
+ break;
273
+ case "right":
274
+ command = "justifyRight";
275
+ break;
276
+ case "justify":
277
+ command = "justifyFull";
278
+ break;
279
+ }
280
+
281
+ if (command) {
282
+ document.execCommand(command, false);
283
+ }
284
+
285
+ setTimeout(() => {
286
+ updateEditorState();
287
+ if (onChange && editorRef.current) {
288
+ onChange(editorRef.current.innerHTML);
289
+ }
290
+ }, 100);
291
+ },
292
+ [readOnly, onChange, updateEditorState],
293
+ );
294
+
295
+ // Apply list formatting
296
+ const toggleList = useCallback(
297
+ (listType: "unordered" | "ordered") => {
298
+ if (readOnly) return;
299
+
300
+ editorRef.current?.focus();
301
+
302
+ const command =
303
+ listType === "unordered"
304
+ ? "insertUnorderedList"
305
+ : "insertOrderedList";
306
+ document.execCommand(command, false);
307
+
308
+ setTimeout(() => {
309
+ updateEditorState();
310
+ if (onChange && editorRef.current) {
311
+ onChange(editorRef.current.innerHTML);
312
+ }
313
+ }, 100);
314
+ },
315
+ [readOnly, onChange, updateEditorState],
316
+ );
317
+
318
+ // Strip formatting
319
+ const stripFormatting = useCallback(() => {
320
+ if (readOnly) return;
321
+
322
+ editorRef.current?.focus();
323
+ document.execCommand("removeFormat", false);
324
+
325
+ setTimeout(() => {
326
+ updateEditorState();
327
+ if (onChange && editorRef.current) {
328
+ onChange(editorRef.current.innerHTML);
329
+ }
330
+ }, 100);
331
+ }, [readOnly, onChange, updateEditorState]);
332
+
333
+ // Handle input changes
334
+ const handleInput = useCallback(() => {
335
+ // Don't immediately update editor state to avoid selection interference
336
+ if (onChange && editorRef.current) {
337
+ onChange(editorRef.current.innerHTML);
338
+ }
339
+ // Update state after a brief delay
340
+ setTimeout(() => {
341
+ updateEditorState();
342
+ }, 50);
343
+ }, [onChange, updateEditorState]);
344
+
345
+ // Handle selection changes (debounced to avoid interference)
346
+ const handleSelectionChange = useCallback(() => {
347
+ if (document.activeElement === editorRef.current) {
348
+ // Debounce to avoid interfering with user selection
349
+ setTimeout(() => {
350
+ if (document.activeElement === editorRef.current) {
351
+ updateEditorState();
352
+ }
353
+ }, 100);
354
+ }
355
+ }, [updateEditorState]);
356
+
357
+ // Handle focus
358
+ const handleFocus = useCallback(() => {
359
+ // Delay updateEditorState to allow natural cursor positioning
360
+ setTimeout(() => {
361
+ updateEditorState();
362
+ }, 50);
363
+ if (onFocus) {
364
+ onFocus();
365
+ }
366
+ }, [onFocus, updateEditorState]);
367
+
368
+ // Handle blur
369
+ const handleBlur = useCallback(() => {
370
+ if (onBlur) {
371
+ onBlur();
372
+ }
373
+ }, [onBlur]);
374
+
375
+ // Handle key down for shortcuts
376
+ const handleKeyDown = useCallback(
377
+ (event: React.KeyboardEvent) => {
378
+ if (readOnly) return;
379
+
380
+ // Handle keyboard shortcuts
381
+ if (event.ctrlKey || event.metaKey) {
382
+ switch (event.key.toLowerCase()) {
383
+ case "b":
384
+ event.preventDefault();
385
+ toggleMark("bold");
386
+ break;
387
+ case "i":
388
+ event.preventDefault();
389
+ toggleMark("italic");
390
+ break;
391
+ case "u":
392
+ event.preventDefault();
393
+ toggleMark("underline");
394
+ break;
395
+ }
396
+ }
397
+
398
+ // Handle Enter key for lists
399
+ if (event.key === "Enter") {
400
+ const selection = window.getSelection();
401
+ if (selection && selection.rangeCount > 0) {
402
+ const range = selection.getRangeAt(0);
403
+ const listItem =
404
+ range.startContainer.nodeType === Node.TEXT_NODE
405
+ ? range.startContainer.parentElement?.closest("li")
406
+ : (range.startContainer as Element).closest("li");
407
+
408
+ if (listItem && !listItem.textContent?.trim()) {
409
+ // If we're in an empty list item, break out of the list
410
+ event.preventDefault();
411
+ document.execCommand("outdent", false);
412
+ }
413
+ }
414
+ }
415
+ },
416
+ [readOnly, toggleMark],
417
+ );
418
+
419
+ // Set up selection change listener
420
+ useEffect(() => {
421
+ document.addEventListener("selectionchange", handleSelectionChange);
422
+ return () => {
423
+ document.removeEventListener("selectionchange", handleSelectionChange);
424
+ };
425
+ }, [handleSelectionChange]);
426
+
427
+ // Check if a mark is active
428
+ const isMarkActive = useCallback(
429
+ (markId: string): boolean => {
430
+ return state.activeFormats.has(markId);
431
+ },
432
+ [state.activeFormats],
433
+ );
434
+
435
+ // Check if a block is active
436
+ const isBlockActive = useCallback(
437
+ (blockId: string): boolean => {
438
+ return state.currentBlock === blockId;
439
+ },
440
+ [state.currentBlock],
441
+ );
442
+
443
+ // Check if an alignment is active
444
+ const isAlignActive = useCallback(
445
+ (alignment: string): boolean => {
446
+ return state.currentAlignment === alignment;
447
+ },
448
+ [state.currentAlignment],
449
+ );
450
+
451
+ // Check if a list is active
452
+ const isListActive = useCallback(
453
+ (listType: "unordered" | "ordered"): boolean => {
454
+ return document.queryCommandState(
455
+ listType === "unordered"
456
+ ? "insertUnorderedList"
457
+ : "insertOrderedList",
458
+ );
459
+ },
460
+ [],
461
+ );
462
+
463
+ return (
464
+ <div className={`simple-rich-text-editor ${props.className || ""}`}>
465
+ {!readOnly && (
466
+ <SimpleToolbar
467
+ profile={editorProfile}
468
+ isMarkActive={isMarkActive}
469
+ isBlockActive={isBlockActive}
470
+ isAlignActive={isAlignActive}
471
+ isListActive={isListActive}
472
+ toggleMark={toggleMark}
473
+ toggleBlock={toggleBlock}
474
+ toggleAlign={toggleAlign}
475
+ toggleList={toggleList}
476
+ stripFormatting={stripFormatting}
477
+ />
478
+ )}
479
+ <div
480
+ ref={editorRef}
481
+ contentEditable={!readOnly}
482
+ className={classNames(
483
+ "simple-editor-content",
484
+ readOnly ? "bg-gray-4" : "bg-gray-5",
485
+ "focus-shadow p-2",
486
+ )}
487
+ onInput={handleInput}
488
+ onFocus={handleFocus}
489
+ onBlur={handleBlur}
490
+ onKeyDown={handleKeyDown}
491
+ style={{
492
+ minHeight: "100px",
493
+ outline: "none",
494
+ }}
495
+ suppressContentEditableWarning={true}
496
+ data-placeholder={placeholder}
497
+ />
498
+ </div>
499
+ );
500
+ },
501
+ );
502
+
503
+ SimpleRichTextEditor.displayName = "SimpleRichTextEditor";
504
+
505
+ export default SimpleRichTextEditor;