@alpaca-editor/core 1.0.3813 → 1.0.3817

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 (207) hide show
  1. package/dist/components/ui/context-menu.d.ts +25 -0
  2. package/dist/components/ui/context-menu.js +51 -0
  3. package/dist/components/ui/context-menu.js.map +1 -0
  4. package/dist/config/config.js +4 -3
  5. package/dist/config/config.js.map +1 -1
  6. package/dist/config/types.d.ts +4 -2
  7. package/dist/editor/ComponentInfo.js +1 -1
  8. package/dist/editor/ComponentInfo.js.map +1 -1
  9. package/dist/editor/ConfirmationDialog.d.ts +1 -1
  10. package/dist/editor/ContextMenu.d.ts +1 -1
  11. package/dist/editor/ContextMenu.js +24 -9
  12. package/dist/editor/ContextMenu.js.map +1 -1
  13. package/dist/editor/EditorWarnings.js +1 -1
  14. package/dist/editor/EditorWarnings.js.map +1 -1
  15. package/dist/editor/FieldList.js +1 -1
  16. package/dist/editor/FieldList.js.map +1 -1
  17. package/dist/editor/FieldListFieldWithFallbacks.js +3 -3
  18. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  19. package/dist/editor/ImageEditor.js +2 -2
  20. package/dist/editor/ImageEditor.js.map +1 -1
  21. package/dist/editor/ItemInfo.js +2 -2
  22. package/dist/editor/ItemInfo.js.map +1 -1
  23. package/dist/editor/MainLayout.js +3 -3
  24. package/dist/editor/MainLayout.js.map +1 -1
  25. package/dist/editor/Titlebar.js +1 -1
  26. package/dist/editor/Titlebar.js.map +1 -1
  27. package/dist/editor/ai/AiTerminal.js +19 -12
  28. package/dist/editor/ai/AiTerminal.js.map +1 -1
  29. package/dist/editor/client/EditorClient.js +90 -35
  30. package/dist/editor/client/EditorClient.js.map +1 -1
  31. package/dist/editor/client/editContext.d.ts +10 -4
  32. package/dist/editor/client/editContext.js.map +1 -1
  33. package/dist/editor/client/operations.d.ts +5 -1
  34. package/dist/editor/client/operations.js +112 -17
  35. package/dist/editor/client/operations.js.map +1 -1
  36. package/dist/editor/client/pageModelBuilder.js +8 -5
  37. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  38. package/dist/editor/commands/componentCommands.js +15 -13
  39. package/dist/editor/commands/componentCommands.js.map +1 -1
  40. package/dist/editor/component-designer/ComponentDesigner.js +1 -1
  41. package/dist/editor/component-designer/ComponentDesigner.js.map +1 -1
  42. package/dist/editor/componentTreeHelper.js +3 -3
  43. package/dist/editor/componentTreeHelper.js.map +1 -1
  44. package/dist/editor/control-center/ControlCenterMenu.js +3 -3
  45. package/dist/editor/control-center/ControlCenterMenu.js.map +1 -1
  46. package/dist/editor/field-types/TreeListEditor.js +4 -4
  47. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  48. package/dist/editor/menubar/LanguageSelector.js +3 -3
  49. package/dist/editor/menubar/LanguageSelector.js.map +1 -1
  50. package/dist/editor/menubar/PageSelector.js +1 -1
  51. package/dist/editor/menubar/PageSelector.js.map +1 -1
  52. package/dist/editor/menubar/PageViewerControls.js +12 -7
  53. package/dist/editor/menubar/PageViewerControls.js.map +1 -1
  54. package/dist/editor/menubar/Separator.js +1 -1
  55. package/dist/editor/menubar/VersionSelector.js +1 -1
  56. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  57. package/dist/editor/page-editor-chrome/FrameMenu.d.ts +2 -2
  58. package/dist/editor/page-editor-chrome/FrameMenu.js +66 -22
  59. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  60. package/dist/editor/page-editor-chrome/FrameMenus.d.ts +2 -2
  61. package/dist/editor/page-editor-chrome/FrameMenus.js +2 -2
  62. package/dist/editor/page-editor-chrome/FrameMenus.js.map +1 -1
  63. package/dist/editor/page-editor-chrome/InlineEditor.d.ts +2 -2
  64. package/dist/editor/page-editor-chrome/InlineEditor.js +175 -17
  65. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  66. package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +2 -2
  67. package/dist/editor/page-editor-chrome/PageEditorChrome.js +2 -2
  68. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  69. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +5 -5
  70. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  71. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +114 -45
  72. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  73. package/dist/editor/page-viewer/EditorForm.d.ts +2 -1
  74. package/dist/editor/page-viewer/EditorForm.js +9 -8
  75. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  76. package/dist/editor/page-viewer/MiniMap.d.ts +2 -2
  77. package/dist/editor/page-viewer/MiniMap.js +2 -2
  78. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  79. package/dist/editor/page-viewer/PageViewer.d.ts +2 -2
  80. package/dist/editor/page-viewer/PageViewer.js +3 -3
  81. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  82. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -3
  83. package/dist/editor/page-viewer/PageViewerFrame.js +127 -223
  84. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  85. package/dist/editor/page-viewer/pageModelBuilder.d.ts +3 -0
  86. package/dist/editor/page-viewer/pageModelBuilder.js +299 -0
  87. package/dist/editor/page-viewer/pageModelBuilder.js.map +1 -0
  88. package/dist/editor/pageModel.d.ts +5 -0
  89. package/dist/editor/reviews/Comments.d.ts +2 -0
  90. package/dist/editor/reviews/Comments.js +26 -9
  91. package/dist/editor/reviews/Comments.js.map +1 -1
  92. package/dist/editor/reviews/DiffView.d.ts +17 -0
  93. package/dist/editor/reviews/DiffView.js +57 -0
  94. package/dist/editor/reviews/DiffView.js.map +1 -0
  95. package/dist/editor/reviews/SuggestedEdit.d.ts +4 -0
  96. package/dist/editor/reviews/SuggestedEdit.js +180 -0
  97. package/dist/editor/reviews/SuggestedEdit.js.map +1 -0
  98. package/dist/editor/services/suggestedEditsService.d.ts +17 -0
  99. package/dist/editor/services/suggestedEditsService.js +26 -0
  100. package/dist/editor/services/suggestedEditsService.js.map +1 -0
  101. package/dist/editor/sidebar/ComponentPalette.js +30 -30
  102. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  103. package/dist/editor/sidebar/ComponentTree.js +7 -6
  104. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  105. package/dist/editor/sidebar/MainContentTree.js +1 -1
  106. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  107. package/dist/editor/sidebar/SidebarView.js +3 -3
  108. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  109. package/dist/editor/ui/CopyToClipboardButton.js +2 -1
  110. package/dist/editor/ui/CopyToClipboardButton.js.map +1 -1
  111. package/dist/editor/ui/Icons.d.ts +0 -1
  112. package/dist/editor/ui/Icons.js +0 -3
  113. package/dist/editor/ui/Icons.js.map +1 -1
  114. package/dist/editor/ui/PerfectTree.js +3 -3
  115. package/dist/editor/ui/PerfectTree.js.map +1 -1
  116. package/dist/editor/ui/Section.js +1 -1
  117. package/dist/editor/ui/Section.js.map +1 -1
  118. package/dist/editor/ui/SimpleIconButton.js +3 -1
  119. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  120. package/dist/editor/ui/SimpleMenu.d.ts +1 -8
  121. package/dist/editor/ui/SimpleMenu.js +1 -1
  122. package/dist/editor/ui/SimpleMenu.js.map +1 -1
  123. package/dist/editor/utils.d.ts +2 -2
  124. package/dist/editor/utils.js +57 -9
  125. package/dist/editor/utils.js.map +1 -1
  126. package/dist/editor/views/CompareView.js +4 -13
  127. package/dist/editor/views/CompareView.js.map +1 -1
  128. package/dist/editor/views/EditView.js +2 -2
  129. package/dist/editor/views/EditView.js.map +1 -1
  130. package/dist/editor/views/SingleEditView.d.ts +2 -2
  131. package/dist/editor/views/SingleEditView.js +2 -2
  132. package/dist/editor/views/SingleEditView.js.map +1 -1
  133. package/dist/lib/safelist.js +1 -1
  134. package/dist/lib/safelist.js.map +1 -1
  135. package/dist/page-wizard/steps/BuildPageStep.js +2 -2
  136. package/dist/page-wizard/steps/BuildPageStep.js.map +1 -1
  137. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +2 -2
  138. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
  139. package/dist/splash-screen/SplashScreen.js +0 -1
  140. package/dist/splash-screen/SplashScreen.js.map +1 -1
  141. package/dist/styles.css +275 -58
  142. package/dist/types.d.ts +20 -2
  143. package/package.json +6 -2
  144. package/src/components/ui/context-menu.tsx +250 -0
  145. package/src/config/config.tsx +4 -4
  146. package/src/config/types.ts +4 -2
  147. package/src/editor/ComponentInfo.tsx +3 -5
  148. package/src/editor/ConfirmationDialog.tsx +1 -1
  149. package/src/editor/ContextMenu.tsx +68 -19
  150. package/src/editor/EditorWarnings.tsx +2 -2
  151. package/src/editor/FieldList.tsx +6 -6
  152. package/src/editor/FieldListFieldWithFallbacks.tsx +9 -9
  153. package/src/editor/ImageEditor.tsx +2 -2
  154. package/src/editor/ItemInfo.tsx +4 -4
  155. package/src/editor/MainLayout.tsx +3 -4
  156. package/src/editor/Titlebar.tsx +4 -4
  157. package/src/editor/ai/AiTerminal.tsx +31 -24
  158. package/src/editor/client/EditorClient.tsx +106 -57
  159. package/src/editor/client/editContext.ts +13 -4
  160. package/src/editor/client/operations.ts +162 -23
  161. package/src/editor/client/pageModelBuilder.ts +26 -18
  162. package/src/editor/commands/componentCommands.tsx +58 -39
  163. package/src/editor/component-designer/ComponentDesigner.tsx +1 -1
  164. package/src/editor/componentTreeHelper.tsx +3 -2
  165. package/src/editor/control-center/ControlCenterMenu.tsx +4 -4
  166. package/src/editor/field-types/TreeListEditor.tsx +11 -11
  167. package/src/editor/menubar/LanguageSelector.tsx +6 -6
  168. package/src/editor/menubar/PageSelector.tsx +11 -11
  169. package/src/editor/menubar/PageViewerControls.tsx +49 -23
  170. package/src/editor/menubar/Separator.tsx +2 -2
  171. package/src/editor/menubar/VersionSelector.tsx +1 -1
  172. package/src/editor/page-editor-chrome/FrameMenu.tsx +94 -29
  173. package/src/editor/page-editor-chrome/FrameMenus.tsx +6 -6
  174. package/src/editor/page-editor-chrome/InlineEditor.tsx +237 -26
  175. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +11 -14
  176. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +12 -15
  177. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +159 -68
  178. package/src/editor/page-viewer/EditorForm.tsx +15 -9
  179. package/src/editor/page-viewer/MiniMap.tsx +4 -4
  180. package/src/editor/page-viewer/PageViewer.tsx +6 -6
  181. package/src/editor/page-viewer/PageViewerFrame.tsx +146 -309
  182. package/src/editor/page-viewer/pageModelBuilder.ts +412 -0
  183. package/src/editor/pageModel.ts +5 -0
  184. package/src/editor/reviews/Comments.tsx +56 -15
  185. package/src/editor/reviews/DiffView.tsx +109 -0
  186. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  187. package/src/editor/services/suggestedEditsService.ts +39 -0
  188. package/src/editor/sidebar/ComponentPalette.tsx +108 -106
  189. package/src/editor/sidebar/ComponentTree.tsx +10 -5
  190. package/src/editor/sidebar/MainContentTree.tsx +1 -1
  191. package/src/editor/sidebar/SidebarView.tsx +9 -9
  192. package/src/editor/ui/CopyToClipboardButton.tsx +2 -1
  193. package/src/editor/ui/Icons.tsx +0 -18
  194. package/src/editor/ui/PerfectTree.tsx +5 -5
  195. package/src/editor/ui/Section.tsx +4 -4
  196. package/src/editor/ui/SimpleIconButton.tsx +5 -3
  197. package/src/editor/ui/SimpleMenu.tsx +5 -13
  198. package/src/editor/utils.ts +74 -17
  199. package/src/editor/views/CompareView.tsx +13 -24
  200. package/src/editor/views/EditView.tsx +2 -2
  201. package/src/editor/views/SingleEditView.tsx +3 -3
  202. package/src/lib/safelist.tsx +4 -0
  203. package/src/page-wizard/steps/BuildPageStep.tsx +18 -25
  204. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +16 -18
  205. package/src/splash-screen/SplashScreen.tsx +0 -1
  206. package/src/types.ts +20 -3
  207. package/styles.css +58 -17
@@ -0,0 +1,316 @@
1
+ import { SuggestedEdit as SuggestedEditType } from "../../types";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { useEditContext } from "../client/editContext";
4
+ import {
5
+ deleteSuggestedEdit,
6
+ createOrUpdateSuggestedEdit,
7
+ } from "../services/suggestedEditsService";
8
+ import { Button } from "../../components/ui/button";
9
+ import { formatDate } from "../utils";
10
+ import { SimpleIconButton } from "../ui/SimpleIconButton";
11
+ import { OverlayPanel } from "primereact/overlaypanel";
12
+ import { DiffView } from "./DiffView";
13
+ // Import lucide icons (adjust names as needed)
14
+ import { Trash2, GalleryVertical, Check, Brush, XCircle } from "lucide-react";
15
+ // Import patch functions from the diff library.
16
+ import { createPatch, applyPatch } from "diff";
17
+ import { cn } from "../../lib/utils";
18
+
19
+ export function SuggestedEditComponent({ edit }: { edit: SuggestedEditType }) {
20
+ const editContext = useEditContext();
21
+ const ref = useRef<HTMLDivElement>(null);
22
+ const overlayPanelRef = useRef<OverlayPanel>(null);
23
+ const [item, setItem] = useState<any>(null);
24
+ const [patchPossible, setPatchPossible] = useState<boolean>(true);
25
+
26
+ const [patchWarning, setPatchWarning] = useState<string>("");
27
+ const [applied, setApplied] = useState<boolean>(false);
28
+
29
+ const [ignoreFormatting, setIgnoreFormatting] = useState(true);
30
+ const [clipUnchanged, setClipUnchanged] = useState(true);
31
+
32
+ const canApply = editContext?.mode === "edit" && edit.status !== "Applied";
33
+
34
+ // Load the full item from the repository.
35
+ useEffect(() => {
36
+ if (
37
+ editContext?.itemsRepository &&
38
+ edit.itemId &&
39
+ edit.mainItemLanguage &&
40
+ edit.mainItemVersion !== undefined
41
+ ) {
42
+ editContext.itemsRepository
43
+ .getItem({
44
+ id: edit.itemId,
45
+ language: edit.mainItemLanguage,
46
+ version: edit.mainItemVersion,
47
+ })
48
+ .then((loadedItem) => {
49
+ setItem(loadedItem);
50
+ // When the item loads, check if the patch is applicable.
51
+ checkAndComputePatch(loadedItem);
52
+ })
53
+ .catch((err) => {
54
+ console.error("Error loading item:", err);
55
+ });
56
+ }
57
+ }, [
58
+ edit.itemId,
59
+ edit.mainItemLanguage,
60
+ edit.mainItemVersion,
61
+ editContext?.itemsRepository,
62
+ ]);
63
+
64
+ // Determine if this suggested edit is selected based on the focused field.
65
+ const isSelected =
66
+ editContext?.focusedField &&
67
+ editContext.focusedField.fieldId === edit.fieldId &&
68
+ editContext.focusedField.item.id === edit.itemId;
69
+
70
+ useEffect(() => {
71
+ if (isSelected && ref.current) {
72
+ ref.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
73
+ }
74
+ }, [isSelected]);
75
+
76
+ // When a suggested edit is clicked, update the focused field and select the item.
77
+ function handleSelectSuggestedEdit() {
78
+ if (edit.fieldId) {
79
+ editContext?.setFocusedField(
80
+ {
81
+ fieldId: edit.fieldId,
82
+ item: {
83
+ id: edit.itemId,
84
+ language: edit.mainItemLanguage,
85
+ version: edit.mainItemVersion,
86
+ },
87
+ },
88
+ false,
89
+ );
90
+ // Also select the item.
91
+ editContext?.select?.([edit.itemId]);
92
+ }
93
+ }
94
+
95
+ // Render contextual info by retrieving item and field names from the loaded item.
96
+ const renderContextInfo = () => {
97
+ const itemName = item ? item.name : null;
98
+ const fieldName =
99
+ item && item.fields
100
+ ? item.fields.find(
101
+ (f: { id: string; name?: string }) => f.id === edit.fieldId,
102
+ )?.name
103
+ : null;
104
+ if (!itemName && !fieldName) return null;
105
+ return (
106
+ <div className="mt-3 flex items-center border-t pt-3 text-xs">
107
+ {itemName && <div className="text-2xs text-gray-500">{itemName}</div>}
108
+ {fieldName && itemName && (
109
+ <div className="text-2xs mx-2 text-gray-500">&gt;</div>
110
+ )}
111
+ {fieldName && <div className="text-2xs text-gray-500">{fieldName}</div>}
112
+ </div>
113
+ );
114
+ };
115
+
116
+ // Allow deletion only if the current user is the author.
117
+ const canDelete = edit.author === editContext?.user?.name;
118
+
119
+ // Render the header with author info, creation date, and control buttons.
120
+ const renderHeader = () => {
121
+ return (
122
+ <div className="mb-3 flex items-start justify-between">
123
+ <div>
124
+ <div className="text-xs font-bold text-gray-900" title={edit.author}>
125
+ {edit.authorDisplayName || edit.author}
126
+ </div>
127
+ <div className="text-xs text-gray-500">
128
+ {edit.created ? formatDate(new Date(edit.created)) : ""}
129
+ </div>
130
+ </div>
131
+ <div className="flex items-center gap-1">
132
+ {/* Show apply patch control if not yet applied */}
133
+ {canApply && !applied && patchPossible && (
134
+ <SimpleIconButton
135
+ className="text-gray-500"
136
+ icon={<Check size={14} />}
137
+ label="Apply"
138
+ onClick={handleApplyPatch}
139
+ />
140
+ )}
141
+ {applied && (
142
+ <i
143
+ className="pi pi-check text-bold px-1 text-xs text-green-500"
144
+ title={
145
+ "Applied by " +
146
+ edit.updatedBy +
147
+ " (" +
148
+ formatDate(new Date(edit.updated!)) +
149
+ ")"
150
+ }
151
+ ></i>
152
+ )}
153
+ {canDelete && (
154
+ <SimpleIconButton
155
+ className="text-gray-500"
156
+ icon={<Trash2 size={14} />}
157
+ label="Delete"
158
+ onClick={(e: any) => overlayPanelRef.current?.toggle(e)}
159
+ />
160
+ )}
161
+ <OverlayPanel ref={overlayPanelRef}>
162
+ <Button
163
+ className="m-2"
164
+ variant="outline"
165
+ onClick={async () => {
166
+ await deleteSuggestedEdit(edit);
167
+ }}
168
+ >
169
+ Delete
170
+ </Button>
171
+ </OverlayPanel>
172
+ </div>
173
+ </div>
174
+ );
175
+ };
176
+
177
+ // Render toggle buttons using SimpleIconButtons.
178
+ const renderDiffToggleButtons = () => {
179
+ return (
180
+ <div className="mb-2 flex gap-2">
181
+ <SimpleIconButton
182
+ icon={<Brush size={14} className="p-0.5" />}
183
+ label="Ignore Formatting"
184
+ onClick={() => setIgnoreFormatting((prev) => !prev)}
185
+ className={cn("text-gray-500", ignoreFormatting ? "bg-gray-200" : "")}
186
+ />
187
+ <SimpleIconButton
188
+ icon={<GalleryVertical size={14} className="p-0.5" />}
189
+ label="Clip"
190
+ onClick={() => setClipUnchanged((prev) => !prev)}
191
+ className={cn("text-gray-500", clipUnchanged ? "bg-gray-200" : "")}
192
+ />
193
+ </div>
194
+ );
195
+ };
196
+
197
+ // Helper: Check if the patch is applicable to the current field value.
198
+ async function checkAndComputePatch(loadedItem: any) {
199
+ const field = loadedItem?.fields?.find(
200
+ (f: { id: string }) => f.id === edit.fieldId,
201
+ );
202
+ if (!field) return;
203
+ const currentValue: string = field.rawValue || "";
204
+ const patch = createPatch("field", edit.oldValue, edit.newValue);
205
+ const patchedCandidate = applyPatch(currentValue, patch);
206
+ if (patchedCandidate === false || typeof patchedCandidate !== "string") {
207
+ setPatchPossible(false);
208
+ setPatchWarning("Patch cannot be applied cleanly.");
209
+ } else {
210
+ setPatchPossible(true);
211
+ setPatchWarning("");
212
+ }
213
+ }
214
+
215
+ // Handler for applying the patch using editContext.operations.editField.
216
+ async function handleApplyPatch() {
217
+ if (!patchPossible) return;
218
+ if (!editContext) return;
219
+
220
+ // Recalculate the patch immediately before applying
221
+ const field = item?.fields?.find(
222
+ (f: { id: string }) => f.id === edit.fieldId,
223
+ );
224
+ if (!field) return;
225
+ const currentValue: string = field.rawValue || "";
226
+ const patch = createPatch("field", edit.oldValue, edit.newValue);
227
+ const patchedCandidate = applyPatch(currentValue, patch);
228
+
229
+ if (patchedCandidate === false || typeof patchedCandidate !== "string") {
230
+ setPatchWarning(
231
+ "Patch cannot be applied cleanly to current field value.",
232
+ );
233
+ return;
234
+ }
235
+
236
+ await editContext.operations.editField({
237
+ field: {
238
+ fieldId: edit.fieldId,
239
+ item: {
240
+ id: edit.itemId,
241
+ language: edit.mainItemLanguage,
242
+ version: edit.mainItemVersion,
243
+ },
244
+ },
245
+ value: patchedCandidate,
246
+ rawValue: patchedCandidate,
247
+ refresh: "immediate",
248
+ });
249
+ // Update the suggestion status to "Applied" and persist the update.
250
+ edit.status = "Applied";
251
+ await createOrUpdateSuggestedEdit(edit);
252
+ setApplied(true);
253
+ setPatchWarning("");
254
+ }
255
+
256
+ // Handler for replacing the field content completely using editContext.operations.editField.
257
+ async function handleReplaceCompletely() {
258
+ if (!editContext) return;
259
+ await editContext.operations.editField({
260
+ field: {
261
+ fieldId: edit.fieldId,
262
+ item: {
263
+ id: edit.itemId,
264
+ language: edit.mainItemLanguage,
265
+ version: edit.mainItemVersion,
266
+ },
267
+ },
268
+ value: edit.newValue,
269
+ rawValue: edit.newValue,
270
+ refresh: "immediate",
271
+ });
272
+ edit.status = "Applied";
273
+ await createOrUpdateSuggestedEdit(edit);
274
+ setApplied(true);
275
+ setPatchWarning("");
276
+ }
277
+
278
+ return (
279
+ <div
280
+ ref={ref}
281
+ key={edit.id}
282
+ data-testid="suggested-edit"
283
+ className={`mb-3 cursor-pointer rounded-lg border-2 bg-white p-3 shadow-sm hover:bg-gray-50 ${
284
+ isSelected ? "border-blue-500" : "border-transparent"
285
+ }`}
286
+ onClick={handleSelectSuggestedEdit}
287
+ >
288
+ {renderHeader()}
289
+ {renderDiffToggleButtons()}
290
+
291
+ <div className="text-sm whitespace-pre-wrap text-gray-700">
292
+ <DiffView
293
+ oldText={edit.oldValue}
294
+ newText={edit.newValue}
295
+ ignoreFormatting={ignoreFormatting}
296
+ clipUnchanged={clipUnchanged}
297
+ clipThreshold={50}
298
+ clipContext={10}
299
+ />
300
+ {canApply && patchWarning && (
301
+ <div className="mt-1 text-xs text-red-500">
302
+ {patchWarning}
303
+ <a
304
+ className="ml-2 cursor-pointer underline"
305
+ onClick={handleReplaceCompletely}
306
+ >
307
+ Click here to replace the field content.
308
+ </a>
309
+ </div>
310
+ )}
311
+ </div>
312
+
313
+ {renderContextInfo()}
314
+ </div>
315
+ );
316
+ }
@@ -0,0 +1,39 @@
1
+ import { get, post } from "./serviceHelper";
2
+ import { SuggestedEdit } from "../../types";
3
+
4
+ /**
5
+ * Creates or updates a suggested edit.
6
+ */
7
+ export function createOrUpdateSuggestedEdit(suggestedEdit: SuggestedEdit) {
8
+ return post("/alpaca/editor/suggested-edits/add", suggestedEdit);
9
+ }
10
+
11
+ /**
12
+ * Retrieves all suggested edits for a given main item based on its ID, language, and version.
13
+ */
14
+ export function getSuggestedEdits(
15
+ mainItemId: string,
16
+ mainItemLanguage: string,
17
+ mainItemVersion: number,
18
+ ) {
19
+ return get<SuggestedEdit[]>(
20
+ `/alpaca/editor/suggested-edits/index?mainItemId=${mainItemId}&mainItemLanguage=${mainItemLanguage}&mainItemVersion=${mainItemVersion}`,
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Retrieves a single suggested edit by its unique identifier.
26
+ */
27
+ export function getSuggestedEditById(id: string) {
28
+ return get<SuggestedEdit>(`/alpaca/editor/suggested-edits/get?id=${id}`);
29
+ }
30
+
31
+ /**
32
+ * Deletes the given suggested edit.
33
+ */
34
+ export function deleteSuggestedEdit(suggestedEdit: SuggestedEdit) {
35
+ return post(
36
+ `/alpaca/editor/suggested-edits/delete?id=${suggestedEdit.id}`,
37
+ {},
38
+ );
39
+ }
@@ -25,121 +25,123 @@ export function ComponentPalette({}: {}) {
25
25
  acc[group].push(value);
26
26
  return acc;
27
27
  },
28
- {} as { [key: string]: InsertOption[] }
28
+ {} as { [key: string]: InsertOption[] },
29
29
  );
30
30
 
31
31
  return (
32
- <div className="h-full relative tour-component-palette">
33
- <div className="overflow-y-auto absolute inset-0">
34
- <div className="p-4">
35
- <InputText
36
- ref={filterRef}
37
- className="w-full text-sm"
38
- placeholder="Filter components"
39
- onChange={(e) => setFilter(e.target.value)}
40
- />
41
- </div>
42
- {Object.keys(insertOptionsByGroup)
43
- .sort()
44
- .map((group, index) => {
45
- const options = insertOptionsByGroup[group]!.filter(
46
- (x) => !x.isHidden
47
- ).filter(
48
- (x) => x.name.toLowerCase().indexOf(filter.toLowerCase()) > -1
49
- );
50
- if (options.length === 0) return;
51
- return (
52
- <div
53
- className={classNames(
54
- "m-4",
55
- index > 0 ? " border-t border-gray-200" : ""
56
- )}
57
- key={group}
58
- >
59
- <div className="text-xs font-bold mt-2 mb-3 flex items-center gap-2">
60
- <ArrowDownIcon /> {group}
61
- </div>
62
- <div className="grid gap-4 grid-cols-[repeat(auto-fill,_minmax(70px,_1fr))]">
63
- {options.map((option) => {
64
- function handleDragStart(
65
- event: React.DragEvent<HTMLDivElement>
66
- ): void {
67
- editContext?.dragStart({
68
- type: "template",
69
- typeId: option.typeId,
70
- name: option.name,
71
- });
72
- event.stopPropagation();
73
- }
32
+ <div className="tour-component-palette relative flex h-full flex-col">
33
+ <div className="p-4">
34
+ <InputText
35
+ ref={filterRef}
36
+ className="w-full text-sm"
37
+ placeholder="Filter components"
38
+ onChange={(e) => setFilter(e.target.value)}
39
+ />
40
+ </div>
41
+ <div className="relative flex-1">
42
+ <div className="absolute inset-0 overflow-y-auto">
43
+ {Object.keys(insertOptionsByGroup)
44
+ .sort()
45
+ .map((group, index) => {
46
+ const options = insertOptionsByGroup[group]!.filter(
47
+ (x) => !x.isHidden,
48
+ ).filter(
49
+ (x) => x.name.toLowerCase().indexOf(filter.toLowerCase()) > -1,
50
+ );
51
+ if (options.length === 0) return;
52
+ return (
53
+ <div
54
+ className={classNames(
55
+ "m-4",
56
+ index > 0 ? "border-t border-gray-200" : "",
57
+ )}
58
+ key={group}
59
+ >
60
+ <div className="mt-2 mb-3 flex items-center gap-2 text-xs font-bold">
61
+ <ArrowDownIcon /> {group}
62
+ </div>
63
+ <div className="grid grid-cols-[repeat(auto-fill,_minmax(70px,_1fr))] gap-4">
64
+ {options.map((option) => {
65
+ function handleDragStart(
66
+ event: React.DragEvent<HTMLDivElement>,
67
+ ): void {
68
+ editContext?.dragStart({
69
+ type: "template",
70
+ typeId: option.typeId,
71
+ name: option.name,
72
+ });
73
+ event.stopPropagation();
74
+ }
74
75
 
75
- function handleDragEnd(): void {
76
- editContext!.dragEnd();
77
- editContext!.setSelectedForInsertion("");
78
- }
76
+ function handleDragEnd(): void {
77
+ editContext!.dragEnd();
78
+ editContext!.setSelectedForInsertion("");
79
+ }
79
80
 
80
- const isDraggable = !option.isInvalid;
81
+ const isDraggable = !option.isInvalid;
81
82
 
82
- return (
83
- <div
84
- id={"insert-component-" + option.typeId}
85
- onClick={() => {
86
- editContext!.setSelectedForInsertion(option.typeId);
87
- }}
88
- draggable={isDraggable}
89
- onDragStart={handleDragStart}
90
- onDragEnd={handleDragEnd}
91
- className={classNames(
92
- "p-2 border border-gray-200 flex items-center flex-col gap-2 text-center text-xs relative rounded break-all ",
93
- isDraggable ? "cursor-grab" : "",
94
- editContext.selectedForInsertion === option.typeId
95
- ? "border-sky-500"
96
- : ""
97
- )}
98
- key={option.typeId}
99
- data-testid="insert-component-option"
100
- >
101
- {option.svg && (
102
- <div
103
- className="w-8 h-8"
104
- dangerouslySetInnerHTML={{ __html: option.svg }}
105
- ></div>
106
- )}
107
- {!option.svg && (
108
- <img
109
- src={getAbsoluteIconUrl(option.icon)}
110
- width="32"
111
- height="32"
112
- ></img>
113
- )}
83
+ return (
84
+ <div
85
+ id={"insert-component-" + option.typeId}
86
+ onClick={() => {
87
+ editContext!.setSelectedForInsertion(option.typeId);
88
+ }}
89
+ draggable={isDraggable}
90
+ onDragStart={handleDragStart}
91
+ onDragEnd={handleDragEnd}
92
+ className={classNames(
93
+ "relative flex flex-col items-center gap-2 rounded border border-gray-200 p-2 text-center text-xs break-all",
94
+ isDraggable ? "cursor-grab" : "",
95
+ editContext.selectedForInsertion === option.typeId
96
+ ? "border-sky-500"
97
+ : "",
98
+ )}
99
+ key={option.typeId}
100
+ data-testid="insert-component-option"
101
+ >
102
+ {option.svg && (
103
+ <div
104
+ className="h-8 w-8"
105
+ dangerouslySetInnerHTML={{ __html: option.svg }}
106
+ ></div>
107
+ )}
108
+ {!option.svg && (
109
+ <img
110
+ src={getAbsoluteIconUrl(option.icon)}
111
+ width="32"
112
+ height="32"
113
+ ></img>
114
+ )}
114
115
 
115
- <div className="text-xs">
116
- {option.name
117
- .split(new RegExp(`(${filter})`, "i"))
118
- .map((part: string, i: number) =>
119
- part.toLowerCase() === filter.toLowerCase() &&
120
- part ? (
121
- <u className="font-bold" key={i}>
122
- {part}
123
- </u>
124
- ) : (
125
- part && <span key={i}>{part}</span>
126
- )
127
- )}
128
- </div>
116
+ <div className="text-xs">
117
+ {option.name
118
+ .split(new RegExp(`(${filter})`, "i"))
119
+ .map((part: string, i: number) =>
120
+ part.toLowerCase() === filter.toLowerCase() &&
121
+ part ? (
122
+ <u className="font-bold" key={i}>
123
+ {part}
124
+ </u>
125
+ ) : (
126
+ part && <span key={i}>{part}</span>
127
+ ),
128
+ )}
129
+ </div>
129
130
 
130
- {option.isInvalid && (
131
- <i
132
- className="pi pi-exclamation-triangle text-red-500 absolute top-0 right-0"
133
- title={option.message}
134
- />
135
- )}
136
- </div>
137
- );
138
- })}
131
+ {option.isInvalid && (
132
+ <i
133
+ className="pi pi-exclamation-triangle absolute top-0 right-0 text-red-500"
134
+ title={option.message}
135
+ />
136
+ )}
137
+ </div>
138
+ );
139
+ })}
140
+ </div>
139
141
  </div>
140
- </div>
141
- );
142
- })}
142
+ );
143
+ })}
144
+ </div>
143
145
  </div>
144
146
  </div>
145
147
  );
@@ -177,7 +177,7 @@ export function ComponentTree({}) {
177
177
  });
178
178
  }
179
179
 
180
- if (c.datasourceItem && c.datasourceItem?.id !== c.id) {
180
+ if (c.isShared) {
181
181
  node.tags!.push({
182
182
  id: "linked",
183
183
  severity: "warning",
@@ -274,13 +274,17 @@ export function ComponentTree({}) {
274
274
  };
275
275
 
276
276
  // Handle tree context menu
277
- const handleTreeContextMenu = (item: any, event: React.MouseEvent) => {
278
- const componentId = nodeDictionary[item.id]?.componentId;
277
+ const handleTreeContextMenu = (
278
+ node: TreeNode<Component>,
279
+ event: React.MouseEvent,
280
+ ) => {
281
+ const componentId = nodeDictionary[node.key]?.componentId;
279
282
 
280
283
  let selectedComponentIds = editContext?.selection || [];
281
284
 
282
285
  if (componentId) {
283
286
  if (selectedComponentIds.indexOf(componentId) < 0) {
287
+ console.log("selecting", componentId);
284
288
  editContextRef.current?.select([componentId]);
285
289
  selectedComponentIds = [componentId];
286
290
  }
@@ -297,8 +301,8 @@ export function ComponentTree({}) {
297
301
  .map((key) => nodeDictionary[key]!.data)
298
302
  .filter(isDefinedEntity);
299
303
 
300
- if (selectedEntities.length === 0 && item.data) {
301
- selectedEntities = [item.data];
304
+ if (selectedEntities.length === 0 && nodeDictionary[node.key]?.data) {
305
+ selectedEntities = [nodeDictionary[node.key]!.data as Component];
302
306
  }
303
307
 
304
308
  let commands = getComponentCommands(
@@ -402,6 +406,7 @@ export function ComponentTree({}) {
402
406
  expandedKeys={expandedItems}
403
407
  onToggleExpand={handleToggle}
404
408
  onSelect={handleTreeSelection}
409
+ onContextMenu={handleTreeContextMenu}
405
410
  enableDragAndDrop={true}
406
411
  onDragOverZone={(parent, index, event) =>
407
412
  handleDragOverZone(parent as CustomTreeNode | null, index, event)
@@ -78,7 +78,7 @@ export function MainContentTree({
78
78
  insertOptions?.find(
79
79
  (insertOption: InsertOption) =>
80
80
  insertOption.typeId == x.templateId ||
81
- insertOption.compatibleTemplates?.find(
81
+ insertOption.compatibleTypeIds?.find(
82
82
  (compatibleTemplate) => compatibleTemplate == x.templateId,
83
83
  ) !== undefined,
84
84
  ) !== undefined