@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.
- package/dist/components/ui/context-menu.d.ts +25 -0
- package/dist/components/ui/context-menu.js +51 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/config/config.js +4 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +4 -2
- package/dist/editor/ComponentInfo.js +1 -1
- package/dist/editor/ComponentInfo.js.map +1 -1
- package/dist/editor/ConfirmationDialog.d.ts +1 -1
- package/dist/editor/ContextMenu.d.ts +1 -1
- package/dist/editor/ContextMenu.js +24 -9
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/EditorWarnings.js +1 -1
- package/dist/editor/EditorWarnings.js.map +1 -1
- package/dist/editor/FieldList.js +1 -1
- package/dist/editor/FieldList.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +3 -3
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/ImageEditor.js +2 -2
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/ItemInfo.js +2 -2
- package/dist/editor/ItemInfo.js.map +1 -1
- package/dist/editor/MainLayout.js +3 -3
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/Titlebar.js +1 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AiTerminal.js +19 -12
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/client/EditorClient.js +90 -35
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +10 -4
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/operations.d.ts +5 -1
- package/dist/editor/client/operations.js +112 -17
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +8 -5
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +15 -13
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/component-designer/ComponentDesigner.js +1 -1
- package/dist/editor/component-designer/ComponentDesigner.js.map +1 -1
- package/dist/editor/componentTreeHelper.js +3 -3
- package/dist/editor/componentTreeHelper.js.map +1 -1
- package/dist/editor/control-center/ControlCenterMenu.js +3 -3
- package/dist/editor/control-center/ControlCenterMenu.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +4 -4
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/menubar/LanguageSelector.js +3 -3
- package/dist/editor/menubar/LanguageSelector.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +1 -1
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/PageViewerControls.js +12 -7
- package/dist/editor/menubar/PageViewerControls.js.map +1 -1
- package/dist/editor/menubar/Separator.js +1 -1
- package/dist/editor/menubar/VersionSelector.js +1 -1
- package/dist/editor/menubar/VersionSelector.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.d.ts +2 -2
- package/dist/editor/page-editor-chrome/FrameMenu.js +66 -22
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenus.d.ts +2 -2
- package/dist/editor/page-editor-chrome/FrameMenus.js +2 -2
- package/dist/editor/page-editor-chrome/FrameMenus.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.d.ts +2 -2
- package/dist/editor/page-editor-chrome/InlineEditor.js +175 -17
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +2 -2
- package/dist/editor/page-editor-chrome/PageEditorChrome.js +2 -2
- package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +5 -5
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +114 -45
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.d.ts +2 -1
- package/dist/editor/page-viewer/EditorForm.js +9 -8
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/MiniMap.d.ts +2 -2
- package/dist/editor/page-viewer/MiniMap.js +2 -2
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +2 -2
- package/dist/editor/page-viewer/PageViewer.js +3 -3
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -3
- package/dist/editor/page-viewer/PageViewerFrame.js +127 -223
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageModelBuilder.d.ts +3 -0
- package/dist/editor/page-viewer/pageModelBuilder.js +299 -0
- package/dist/editor/page-viewer/pageModelBuilder.js.map +1 -0
- package/dist/editor/pageModel.d.ts +5 -0
- package/dist/editor/reviews/Comments.d.ts +2 -0
- package/dist/editor/reviews/Comments.js +26 -9
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/DiffView.d.ts +17 -0
- package/dist/editor/reviews/DiffView.js +57 -0
- package/dist/editor/reviews/DiffView.js.map +1 -0
- package/dist/editor/reviews/SuggestedEdit.d.ts +4 -0
- package/dist/editor/reviews/SuggestedEdit.js +180 -0
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -0
- package/dist/editor/services/suggestedEditsService.d.ts +17 -0
- package/dist/editor/services/suggestedEditsService.js +26 -0
- package/dist/editor/services/suggestedEditsService.js.map +1 -0
- package/dist/editor/sidebar/ComponentPalette.js +30 -30
- package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +7 -6
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/MainContentTree.js +1 -1
- package/dist/editor/sidebar/MainContentTree.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.js +3 -3
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/ui/CopyToClipboardButton.js +2 -1
- package/dist/editor/ui/CopyToClipboardButton.js.map +1 -1
- package/dist/editor/ui/Icons.d.ts +0 -1
- package/dist/editor/ui/Icons.js +0 -3
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/ui/PerfectTree.js +3 -3
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/Section.js +1 -1
- package/dist/editor/ui/Section.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.js +3 -1
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/ui/SimpleMenu.d.ts +1 -8
- package/dist/editor/ui/SimpleMenu.js +1 -1
- package/dist/editor/ui/SimpleMenu.js.map +1 -1
- package/dist/editor/utils.d.ts +2 -2
- package/dist/editor/utils.js +57 -9
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/views/CompareView.js +4 -13
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/editor/views/EditView.js +2 -2
- package/dist/editor/views/EditView.js.map +1 -1
- package/dist/editor/views/SingleEditView.d.ts +2 -2
- package/dist/editor/views/SingleEditView.js +2 -2
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/lib/safelist.js +1 -1
- package/dist/lib/safelist.js.map +1 -1
- package/dist/page-wizard/steps/BuildPageStep.js +2 -2
- package/dist/page-wizard/steps/BuildPageStep.js.map +1 -1
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +2 -2
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
- package/dist/splash-screen/SplashScreen.js +0 -1
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +275 -58
- package/dist/types.d.ts +20 -2
- package/package.json +6 -2
- package/src/components/ui/context-menu.tsx +250 -0
- package/src/config/config.tsx +4 -4
- package/src/config/types.ts +4 -2
- package/src/editor/ComponentInfo.tsx +3 -5
- package/src/editor/ConfirmationDialog.tsx +1 -1
- package/src/editor/ContextMenu.tsx +68 -19
- package/src/editor/EditorWarnings.tsx +2 -2
- package/src/editor/FieldList.tsx +6 -6
- package/src/editor/FieldListFieldWithFallbacks.tsx +9 -9
- package/src/editor/ImageEditor.tsx +2 -2
- package/src/editor/ItemInfo.tsx +4 -4
- package/src/editor/MainLayout.tsx +3 -4
- package/src/editor/Titlebar.tsx +4 -4
- package/src/editor/ai/AiTerminal.tsx +31 -24
- package/src/editor/client/EditorClient.tsx +106 -57
- package/src/editor/client/editContext.ts +13 -4
- package/src/editor/client/operations.ts +162 -23
- package/src/editor/client/pageModelBuilder.ts +26 -18
- package/src/editor/commands/componentCommands.tsx +58 -39
- package/src/editor/component-designer/ComponentDesigner.tsx +1 -1
- package/src/editor/componentTreeHelper.tsx +3 -2
- package/src/editor/control-center/ControlCenterMenu.tsx +4 -4
- package/src/editor/field-types/TreeListEditor.tsx +11 -11
- package/src/editor/menubar/LanguageSelector.tsx +6 -6
- package/src/editor/menubar/PageSelector.tsx +11 -11
- package/src/editor/menubar/PageViewerControls.tsx +49 -23
- package/src/editor/menubar/Separator.tsx +2 -2
- package/src/editor/menubar/VersionSelector.tsx +1 -1
- package/src/editor/page-editor-chrome/FrameMenu.tsx +94 -29
- package/src/editor/page-editor-chrome/FrameMenus.tsx +6 -6
- package/src/editor/page-editor-chrome/InlineEditor.tsx +237 -26
- package/src/editor/page-editor-chrome/PageEditorChrome.tsx +11 -14
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +12 -15
- package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +159 -68
- package/src/editor/page-viewer/EditorForm.tsx +15 -9
- package/src/editor/page-viewer/MiniMap.tsx +4 -4
- package/src/editor/page-viewer/PageViewer.tsx +6 -6
- package/src/editor/page-viewer/PageViewerFrame.tsx +146 -309
- package/src/editor/page-viewer/pageModelBuilder.ts +412 -0
- package/src/editor/pageModel.ts +5 -0
- package/src/editor/reviews/Comments.tsx +56 -15
- package/src/editor/reviews/DiffView.tsx +109 -0
- package/src/editor/reviews/SuggestedEdit.tsx +316 -0
- package/src/editor/services/suggestedEditsService.ts +39 -0
- package/src/editor/sidebar/ComponentPalette.tsx +108 -106
- package/src/editor/sidebar/ComponentTree.tsx +10 -5
- package/src/editor/sidebar/MainContentTree.tsx +1 -1
- package/src/editor/sidebar/SidebarView.tsx +9 -9
- package/src/editor/ui/CopyToClipboardButton.tsx +2 -1
- package/src/editor/ui/Icons.tsx +0 -18
- package/src/editor/ui/PerfectTree.tsx +5 -5
- package/src/editor/ui/Section.tsx +4 -4
- package/src/editor/ui/SimpleIconButton.tsx +5 -3
- package/src/editor/ui/SimpleMenu.tsx +5 -13
- package/src/editor/utils.ts +74 -17
- package/src/editor/views/CompareView.tsx +13 -24
- package/src/editor/views/EditView.tsx +2 -2
- package/src/editor/views/SingleEditView.tsx +3 -3
- package/src/lib/safelist.tsx +4 -0
- package/src/page-wizard/steps/BuildPageStep.tsx +18 -25
- package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +16 -18
- package/src/splash-screen/SplashScreen.tsx +0 -1
- package/src/types.ts +20 -3
- 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">></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="
|
|
33
|
-
<div className="
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
function handleDragEnd(): void {
|
|
77
|
+
editContext!.dragEnd();
|
|
78
|
+
editContext!.setSelectedForInsertion("");
|
|
79
|
+
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
const isDraggable = !option.isInvalid;
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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.
|
|
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 = (
|
|
278
|
-
|
|
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 &&
|
|
301
|
-
selectedEntities = [
|
|
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.
|
|
81
|
+
insertOption.compatibleTypeIds?.find(
|
|
82
82
|
(compatibleTemplate) => compatibleTemplate == x.templateId,
|
|
83
83
|
) !== undefined,
|
|
84
84
|
) !== undefined
|