@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,362 @@
1
+ import React, { useCallback } from "react";
2
+ import {
3
+ RichTextEditorProfile,
4
+ ToolbarGroupConfig,
5
+ ToolbarOptionConfig,
6
+ SLATE_MARKS,
7
+ SLATE_BLOCKS,
8
+ SLATE_ALIGNMENTS,
9
+ } from "../types";
10
+ import { SimpleToolbarButton } from "./SimpleToolbarButton";
11
+ import { SimpleDropdown } from "./SimpleDropdown";
12
+
13
+ interface SimpleToolbarProps {
14
+ profile: RichTextEditorProfile;
15
+ isMarkActive: (markId: string) => boolean;
16
+ isBlockActive: (blockId: string) => boolean;
17
+ isAlignActive: (alignment: string) => boolean;
18
+ isListActive: (listType: "unordered" | "ordered") => boolean;
19
+ toggleMark: (markId: string) => void;
20
+ toggleBlock: (blockId: string) => void;
21
+ toggleAlign: (alignment: string) => void;
22
+ toggleList: (listType: "unordered" | "ordered") => void;
23
+ stripFormatting: () => void;
24
+ }
25
+
26
+ interface ToolbarOption {
27
+ id: string;
28
+ type: string;
29
+ label: string;
30
+ icon: React.ReactNode;
31
+ isActive: boolean;
32
+ onSelect: () => void;
33
+ }
34
+
35
+ export const SimpleToolbar: React.FC<SimpleToolbarProps> = ({
36
+ profile,
37
+ isMarkActive,
38
+ isBlockActive,
39
+ isAlignActive,
40
+ isListActive,
41
+ toggleMark,
42
+ toggleBlock,
43
+ toggleAlign,
44
+ toggleList,
45
+ stripFormatting,
46
+ }) => {
47
+ // Create toolbar option from configuration
48
+ const createToolbarOption = useCallback(
49
+ (option: ToolbarOptionConfig): ToolbarOption | null => {
50
+ switch (option.type) {
51
+ case "mark":
52
+ const markConfig = SLATE_MARKS[option.id];
53
+ if (markConfig) {
54
+ return {
55
+ id: option.id,
56
+ type: option.type,
57
+ label: markConfig.label,
58
+ icon: markConfig.icon,
59
+ isActive: isMarkActive(option.id),
60
+ onSelect: () => toggleMark(option.id),
61
+ };
62
+ }
63
+ return null;
64
+
65
+ case "block":
66
+ const blockConfig = SLATE_BLOCKS[option.id];
67
+ if (blockConfig) {
68
+ return {
69
+ id: option.id,
70
+ type: option.type,
71
+ label: blockConfig.label,
72
+ icon: blockConfig.icon,
73
+ isActive: isBlockActive(option.id),
74
+ onSelect: () => toggleBlock(option.id),
75
+ };
76
+ }
77
+ return null;
78
+
79
+ case "alignment":
80
+ const alignConfig = SLATE_ALIGNMENTS[option.id];
81
+ if (alignConfig) {
82
+ return {
83
+ id: option.id,
84
+ type: option.type,
85
+ label: alignConfig.label,
86
+ icon: alignConfig.icon,
87
+ isActive: isAlignActive(alignConfig.value),
88
+ onSelect: () => toggleAlign(alignConfig.value),
89
+ };
90
+ }
91
+ return null;
92
+
93
+ case "list":
94
+ const listType =
95
+ option.id === "unordered-list" ? "unordered" : "ordered";
96
+ return {
97
+ id: option.id,
98
+ type: option.type,
99
+ label:
100
+ option.id === "unordered-list"
101
+ ? "Bulleted List"
102
+ : "Numbered List",
103
+ icon: option.id === "unordered-list" ? "•" : "1.",
104
+ isActive: isListActive(listType),
105
+ onSelect: () => toggleList(listType),
106
+ };
107
+
108
+ case "divider":
109
+ return {
110
+ id: "divider",
111
+ type: "divider",
112
+ label: "Divider",
113
+ icon: null,
114
+ isActive: false,
115
+ onSelect: () => {},
116
+ };
117
+
118
+ case "strip":
119
+ return {
120
+ id: "strip-formatting",
121
+ type: "strip",
122
+ label: "Strip Formatting",
123
+ icon: "✗",
124
+ isActive: false,
125
+ onSelect: () => stripFormatting(),
126
+ };
127
+
128
+ default:
129
+ return null;
130
+ }
131
+ },
132
+ [
133
+ isMarkActive,
134
+ isBlockActive,
135
+ isAlignActive,
136
+ isListActive,
137
+ toggleMark,
138
+ toggleBlock,
139
+ toggleAlign,
140
+ toggleList,
141
+ ],
142
+ );
143
+
144
+ // Split options by dividers
145
+ const splitOptionsByDividers = (
146
+ options: ToolbarOption[],
147
+ ): ToolbarOption[][] => {
148
+ const groups: ToolbarOption[][] = [];
149
+ let currentGroup: ToolbarOption[] = [];
150
+
151
+ options.forEach((option) => {
152
+ if (option.type === "divider") {
153
+ if (currentGroup.length > 0) {
154
+ groups.push(currentGroup);
155
+ currentGroup = [];
156
+ }
157
+ } else {
158
+ currentGroup.push(option);
159
+ }
160
+ });
161
+
162
+ if (currentGroup.length > 0) {
163
+ groups.push(currentGroup);
164
+ }
165
+
166
+ return groups;
167
+ };
168
+
169
+ // Render a toolbar group
170
+ const renderToolbarGroup = (group: ToolbarGroupConfig, rowIndex: number) => {
171
+ if (!group.options || group.options.length === 0) return null;
172
+
173
+ // Create toolbar options from configuration
174
+ const validOptions = group.options
175
+ .map(createToolbarOption)
176
+ .filter((option): option is ToolbarOption => option !== null);
177
+
178
+ if (validOptions.length === 0) return null;
179
+
180
+ const groupKey = `${group.id}-${rowIndex}`;
181
+
182
+ // Render as buttons
183
+ if (group.display === "buttons" || !group.display) {
184
+ const subGroups = splitOptionsByDividers(validOptions);
185
+ return (
186
+ <div key={groupKey} className="toolbar-group">
187
+ {subGroups.map((subGroup, subGroupIndex) => (
188
+ <div
189
+ key={`subgroup-${subGroupIndex}`}
190
+ className="toolbar-button-group"
191
+ >
192
+ {subGroup.map((option) => (
193
+ <SimpleToolbarButton
194
+ key={`${option.type}-${option.id}`}
195
+ icon={option.icon}
196
+ active={option.isActive}
197
+ title={option.label}
198
+ onMouseDown={(event: React.MouseEvent<HTMLButtonElement>) => {
199
+ event.preventDefault();
200
+ option.onSelect();
201
+ }}
202
+ />
203
+ ))}
204
+ </div>
205
+ ))}
206
+ </div>
207
+ );
208
+ }
209
+
210
+ // Render as dropdown
211
+ if (group.display === "dropdown") {
212
+ const blockOptions = validOptions.filter(
213
+ (option) => option.type === "block",
214
+ );
215
+
216
+ // If there's only one block option, render it as a disabled-style button
217
+ if (blockOptions.length === 1) {
218
+ const singleOption = blockOptions[0];
219
+ if (!singleOption) return null;
220
+ return (
221
+ <div key={groupKey} className="toolbar-group">
222
+ <div className="toolbar-dropdown-container">
223
+ <button className="toolbar-dropdown-button" disabled>
224
+ {group.label ? (
225
+ <>
226
+ <span className="toolbar-dropdown-content">
227
+ {group.label}: {singleOption.label}
228
+ </span>
229
+ <span className="toolbar-dropdown-arrow">▼</span>
230
+ </>
231
+ ) : group.showIconsOnly ? (
232
+ <>
233
+ <span className="toolbar-dropdown-icon">
234
+ {singleOption.icon}
235
+ </span>
236
+ <span className="toolbar-dropdown-arrow">▼</span>
237
+ </>
238
+ ) : (
239
+ <>
240
+ <span className="toolbar-dropdown-icon">
241
+ {singleOption.icon && (
242
+ <span className="toolbar-dropdown-icon">
243
+ {singleOption.icon}
244
+ </span>
245
+ )}
246
+ <span className="toolbar-dropdown-content">
247
+ {singleOption.label}
248
+ </span>
249
+ </span>
250
+ <span className="toolbar-dropdown-arrow">▼</span>
251
+ </>
252
+ )}
253
+ </button>
254
+ </div>
255
+ </div>
256
+ );
257
+ }
258
+
259
+ // Multiple options - render normally with dropdown
260
+ const isActive = validOptions.some((option) => option.isActive);
261
+ return (
262
+ <div key={groupKey} className="toolbar-group">
263
+ <div className="toolbar-dropdown-container">
264
+ <SimpleDropdown
265
+ options={validOptions}
266
+ label={group.label}
267
+ showIconsOnly={group.showIconsOnly}
268
+ isActive={isActive}
269
+ title={
270
+ group.label
271
+ ? `${group.label} formatting options`
272
+ : "Formatting options"
273
+ }
274
+ />
275
+ </div>
276
+ </div>
277
+ );
278
+ }
279
+
280
+ return null;
281
+ };
282
+
283
+ if (!profile.toolbar?.groups || profile.toolbar.groups.length === 0) {
284
+ return null;
285
+ }
286
+
287
+ // Group toolbar items by row
288
+ const groupsByRow = profile.toolbar.groups.reduce(
289
+ (acc, group, index) => {
290
+ const row = group.row !== undefined ? group.row : index;
291
+ if (!acc[row]) acc[row] = [];
292
+ acc[row].push(group);
293
+ return acc;
294
+ },
295
+ {} as Record<number, typeof profile.toolbar.groups>,
296
+ );
297
+
298
+ // Create strip formatting option that's always available
299
+ const stripFormattingOption: ToolbarOption = {
300
+ id: "strip-formatting",
301
+ type: "strip",
302
+ label: "Strip Formatting",
303
+ icon: "✗",
304
+ isActive: false,
305
+ onSelect: () => stripFormatting(),
306
+ };
307
+
308
+ // Render each row
309
+ return (
310
+ <div className="toolbar">
311
+ {Object.entries(groupsByRow)
312
+ .sort(([a], [b]) => parseInt(a) - parseInt(b))
313
+ .map(([rowIndex, rowGroups]) => (
314
+ <div key={`row-${rowIndex}`} className="toolbar-row">
315
+ {rowGroups.map((group) =>
316
+ renderToolbarGroup(group, parseInt(rowIndex)),
317
+ )}
318
+ {/* Add strip formatting button to the first row */}
319
+ {parseInt(rowIndex) === 0 && (
320
+ <div className="toolbar-group">
321
+ <div className="toolbar-button-group">
322
+ <SimpleToolbarButton
323
+ key="strip-formatting"
324
+ icon={stripFormattingOption.icon}
325
+ active={stripFormattingOption.isActive}
326
+ title={stripFormattingOption.label}
327
+ onMouseDown={(
328
+ event: React.MouseEvent<HTMLButtonElement>,
329
+ ) => {
330
+ event.preventDefault();
331
+ stripFormattingOption.onSelect();
332
+ }}
333
+ />
334
+ </div>
335
+ </div>
336
+ )}
337
+ </div>
338
+ ))}
339
+ {/* If no groups exist, still show strip formatting */}
340
+ {Object.keys(groupsByRow).length === 0 && (
341
+ <div className="toolbar-row">
342
+ <div className="toolbar-group">
343
+ <div className="toolbar-button-group">
344
+ <SimpleToolbarButton
345
+ key="strip-formatting"
346
+ icon={stripFormattingOption.icon}
347
+ active={stripFormattingOption.isActive}
348
+ title={stripFormattingOption.label}
349
+ onMouseDown={(event: React.MouseEvent<HTMLButtonElement>) => {
350
+ event.preventDefault();
351
+ stripFormattingOption.onSelect();
352
+ }}
353
+ />
354
+ </div>
355
+ </div>
356
+ </div>
357
+ )}
358
+ </div>
359
+ );
360
+ };
361
+
362
+ export default SimpleToolbar;
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+
3
+ interface SimpleToolbarButtonProps {
4
+ icon: React.ReactNode;
5
+ active: boolean;
6
+ title?: string;
7
+ onMouseDown?: (event: React.MouseEvent<HTMLButtonElement>) => void;
8
+ }
9
+
10
+ export const SimpleToolbarButton: React.FC<SimpleToolbarButtonProps> = ({
11
+ icon,
12
+ active,
13
+ title,
14
+ onMouseDown,
15
+ }) => {
16
+ return (
17
+ <button
18
+ className={`toolbar-button ${active ? "active" : ""}`}
19
+ onMouseDown={onMouseDown}
20
+ title={title}
21
+ style={{
22
+ padding: "5px 10px",
23
+ margin: "0 2px",
24
+ background: active ? "#ffffff" : "transparent",
25
+ border: "none",
26
+ borderRadius: "3px",
27
+ cursor: "pointer",
28
+ boxShadow: active ? "0 1px 2px rgba(0,0,0,0.1)" : "none",
29
+ }}
30
+ >
31
+ {icon}
32
+ </button>
33
+ );
34
+ };
35
+
36
+ export default SimpleToolbarButton;
@@ -0,0 +1,264 @@
1
+ import { MenuItem } from "../../../config/types";
2
+ import { Field } from "../../pageModel";
3
+ import { EditContextType } from "../../client/editContext";
4
+ import { getRichTextProfile } from "../../services/contentService";
5
+ import { getCachedParsedProfile } from "./utils/profileServiceCache";
6
+ import {
7
+ RichTextEditorProfile,
8
+ SLATE_MARKS,
9
+ SLATE_BLOCKS,
10
+ SLATE_ALIGNMENTS,
11
+ RichTextField,
12
+ } from "./types";
13
+
14
+ // Helper function to execute formatting commands on the contentEditable element in iframe
15
+ function executeFormatCommand(
16
+ command: string,
17
+ iframeDocument: Document,
18
+ value?: string,
19
+ ): boolean {
20
+ try {
21
+ // Focus the contentEditable element first
22
+ const activeElement = iframeDocument.activeElement;
23
+ if (
24
+ activeElement &&
25
+ (activeElement as HTMLElement).contentEditable === "true"
26
+ ) {
27
+ (activeElement as HTMLElement).focus();
28
+ }
29
+
30
+ // Execute the command on the iframe document
31
+ console.log("Executing command:", command, value);
32
+ return iframeDocument.execCommand(command, false, value);
33
+ } catch (error) {
34
+ console.error(`Failed to execute command: ${command}`, error);
35
+ return false;
36
+ }
37
+ }
38
+
39
+ // Helper function to get the current contentEditable element for this field in iframe
40
+ function getFieldEditableElement(
41
+ fieldId: string,
42
+ iframeDocument: Document,
43
+ ): HTMLElement | null {
44
+ // Look for the contentEditable element with the matching field ID in iframe
45
+ const element = iframeDocument.querySelector(
46
+ `[data-fieldid="${fieldId}"][contenteditable="true"]`,
47
+ ) as HTMLElement;
48
+ return element;
49
+ }
50
+
51
+ // Trigger a save by calling the editContext operations directly
52
+ // Wait for DOM to update before reading content to avoid race conditions
53
+ async function triggerSave(
54
+ element: HTMLElement,
55
+ field: Field,
56
+ editContext: EditContextType,
57
+ ): Promise<void> {
58
+ try {
59
+ // Wait for DOM updates to complete before reading content
60
+ await new Promise((resolve) => {
61
+ // Use a shorter delay - just enough for execCommand to complete
62
+ setTimeout(resolve, 50);
63
+ });
64
+
65
+ const isRichText = element.getAttribute("data-is-richtext") === "true";
66
+ const value = isRichText ? element.innerHTML : element.innerText;
67
+
68
+ await editContext.operations.editField({
69
+ field: field.descriptor,
70
+ value: value,
71
+ refresh: "none",
72
+ });
73
+ } catch (error) {
74
+ console.error("Failed to save field value:", error);
75
+ }
76
+ }
77
+
78
+ export async function createRichTextContextMenu(
79
+ field: Field,
80
+ editContext: EditContextType,
81
+ ): Promise<MenuItem[]> {
82
+ const richTextField = field as RichTextField;
83
+ const profilePath = richTextField.customProperties?.profile;
84
+
85
+ if (!profilePath) {
86
+ return [];
87
+ }
88
+
89
+ // Get the iframe document
90
+ const iframeDocument =
91
+ editContext.pageView?.editorIframeRef?.current?.contentWindow?.document;
92
+ if (!iframeDocument) {
93
+ console.error("Could not find iframe document for rich text editing");
94
+ return [];
95
+ }
96
+
97
+ try {
98
+ // Get the rich text profile using the cached parsed profile
99
+ const profile: RichTextEditorProfile | null = await getCachedParsedProfile(
100
+ profilePath,
101
+ getRichTextProfile,
102
+ );
103
+
104
+ if (!profile?.toolbar?.groups) {
105
+ return [];
106
+ }
107
+
108
+ // Create format submenu items based on the profile
109
+ const formatMenuItems: MenuItem[] = [];
110
+
111
+ // Process each toolbar group to extract formatting options
112
+ for (const group of profile.toolbar.groups) {
113
+ if (group.options && group.options.length > 0) {
114
+ // Collect valid items for this group first
115
+ const groupItems: MenuItem[] = [];
116
+
117
+ // Process each option in the group
118
+ for (const option of group.options) {
119
+ let menuItem: MenuItem | null = null;
120
+
121
+ switch (option.type) {
122
+ case "mark":
123
+ const markConfig = SLATE_MARKS[option.id];
124
+ if (markConfig) {
125
+ let command = "";
126
+ // Only support specific formatting commands
127
+ switch (option.id) {
128
+ case "bold":
129
+ command = "bold";
130
+ break;
131
+ case "italic":
132
+ command = "italic";
133
+ break;
134
+ case "underline":
135
+ command = "underline";
136
+ break;
137
+ case "strikethrough":
138
+ command = "strikeThrough";
139
+ break;
140
+ case "subscript":
141
+ command = "subscript";
142
+ break;
143
+ case "superscript":
144
+ command = "superscript";
145
+ break;
146
+ default:
147
+ // Skip unsupported commands
148
+ continue;
149
+ }
150
+
151
+ menuItem = {
152
+ id: `format-mark-${option.id}`,
153
+ label: markConfig.label,
154
+ icon: (
155
+ <span
156
+ style={{ fontFamily: "monospace", fontWeight: "bold" }}
157
+ >
158
+ {markConfig.icon}
159
+ </span>
160
+ ),
161
+ command: async () => {
162
+ try {
163
+ const element = getFieldEditableElement(
164
+ field.id,
165
+ iframeDocument,
166
+ );
167
+ if (element) {
168
+ element.focus();
169
+ const success = executeFormatCommand(
170
+ command,
171
+ iframeDocument,
172
+ );
173
+ if (success) {
174
+ await triggerSave(element, field, editContext);
175
+ }
176
+ }
177
+ } catch (error) {
178
+ console.error(
179
+ `Failed to apply mark formatting: ${option.id}`,
180
+ error,
181
+ );
182
+ }
183
+ },
184
+ };
185
+ }
186
+ break;
187
+
188
+ // Block, alignment, and list formatting not supported
189
+ case "block":
190
+ case "alignment":
191
+ case "list":
192
+ // Skip unsupported formatting types
193
+ continue;
194
+ }
195
+
196
+ if (menuItem) {
197
+ groupItems.push(menuItem);
198
+ }
199
+ }
200
+
201
+ // Only add group items if we have any valid items for this group
202
+ if (groupItems.length > 0) {
203
+ // Add separator if we already have items from previous groups
204
+ if (formatMenuItems.length > 0) {
205
+ formatMenuItems.push({
206
+ id: `${group.id}-separator`,
207
+ separator: true,
208
+ });
209
+ }
210
+
211
+ // Add all valid items from this group
212
+ formatMenuItems.push(...groupItems);
213
+ }
214
+ }
215
+ }
216
+
217
+ // Build the context menu items
218
+ const menuItems: MenuItem[] = [];
219
+
220
+ // Add the Format submenu if we have any items
221
+ if (formatMenuItems.length > 0) {
222
+ menuItems.push({
223
+ id: "format-submenu",
224
+ label: "Format",
225
+ icon: (
226
+ <span style={{ fontFamily: "monospace", fontWeight: "bold" }}>
227
+ F
228
+ </span>
229
+ ),
230
+ items: formatMenuItems,
231
+ });
232
+ }
233
+
234
+ // Add Strip Formatting command
235
+ menuItems.push({
236
+ id: "strip-formatting",
237
+ label: "Strip Formatting",
238
+ icon: (
239
+ <span style={{ fontFamily: "monospace", fontWeight: "bold" }}>
240
+
241
+ </span>
242
+ ),
243
+ command: async () => {
244
+ try {
245
+ const element = getFieldEditableElement(field.id, iframeDocument);
246
+ if (element) {
247
+ element.focus();
248
+ const success = executeFormatCommand("removeFormat", iframeDocument);
249
+ if (success) {
250
+ await triggerSave(element, field, editContext);
251
+ }
252
+ }
253
+ } catch (error) {
254
+ console.error("Failed to strip formatting:", error);
255
+ }
256
+ },
257
+ });
258
+
259
+ return menuItems;
260
+ } catch (error) {
261
+ console.error("Error creating RichText context menu:", error);
262
+ return [];
263
+ }
264
+ }
@@ -1,5 +1,6 @@
1
- export * from './components/ReactSlate';
2
- export * from './types';
3
- export * from './utils/profileServiceCache';
4
- export * from './hooks/useProfileCache';
5
- export * from './hooks/useRichTextProfile';
1
+ export * from "./components/ReactSlate";
2
+ export * from "./components/SimpleRichTextEditor";
3
+ export * from "./types";
4
+ export * from "./utils/profileServiceCache";
5
+ export * from "./hooks/useProfileCache";
6
+ export * from "./hooks/useRichTextProfile";