@alpaca-editor/core 1.0.3815 → 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/config/config.js +1 -1
- package/dist/config/config.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/Titlebar.js +1 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/client/EditorClient.js +71 -31
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +9 -3
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/operations.d.ts +5 -1
- package/dist/editor/client/operations.js +97 -3
- package/dist/editor/client/operations.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/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 +21 -15
- 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-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 -2
- package/dist/editor/page-viewer/PageViewerFrame.js +12 -12
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- 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/ui/PerfectTree.js +3 -3
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.js +3 -1
- package/dist/editor/ui/SimpleIconButton.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/styles.css +36 -2
- package/dist/types.d.ts +18 -0
- package/package.json +4 -1
- package/src/config/config.tsx +2 -2
- package/src/editor/EditorWarnings.tsx +2 -2
- package/src/editor/FieldList.tsx +6 -6
- package/src/editor/FieldListFieldWithFallbacks.tsx +9 -9
- package/src/editor/Titlebar.tsx +4 -4
- package/src/editor/client/EditorClient.tsx +83 -51
- package/src/editor/client/editContext.ts +12 -3
- package/src/editor/client/operations.ts +146 -9
- package/src/editor/component-designer/ComponentDesigner.tsx +1 -1
- 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 +18 -17
- package/src/editor/page-editor-chrome/FrameMenus.tsx +6 -6
- package/src/editor/page-editor-chrome/InlineEditor.tsx +233 -22
- package/src/editor/page-editor-chrome/PageEditorChrome.tsx +11 -14
- 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 +19 -13
- 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/ui/PerfectTree.tsx +5 -5
- package/src/editor/ui/SimpleIconButton.tsx +5 -3
- 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 +2 -0
- package/src/page-wizard/steps/BuildPageStep.tsx +18 -25
- package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +16 -18
- package/src/types.ts +19 -0
|
@@ -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
|
+
}
|
|
@@ -313,8 +313,8 @@ const NodeContent = memo(
|
|
|
313
313
|
if (node.hasChildren && node.children === null) {
|
|
314
314
|
return (
|
|
315
315
|
<ProgressSpinner
|
|
316
|
-
style={{ width: "
|
|
317
|
-
className="text-gray-500"
|
|
316
|
+
style={{ width: "16px", height: "16px" }}
|
|
317
|
+
className="m-0 text-gray-500"
|
|
318
318
|
/>
|
|
319
319
|
);
|
|
320
320
|
}
|
|
@@ -322,7 +322,7 @@ const NodeContent = memo(
|
|
|
322
322
|
return (
|
|
323
323
|
<span
|
|
324
324
|
onClick={handleToggle}
|
|
325
|
-
className={`mr-
|
|
325
|
+
className={`mr-0.5 inline-block transform cursor-pointer text-gray-500 transition duration-150 select-none ${
|
|
326
326
|
isExpanded ? "rotate-90" : "rotate-0"
|
|
327
327
|
}`}
|
|
328
328
|
>
|
|
@@ -371,10 +371,10 @@ const NodeContent = memo(
|
|
|
371
371
|
{node.hasChildren || node.children?.length ? (
|
|
372
372
|
renderToggle()
|
|
373
373
|
) : (
|
|
374
|
-
<div className="w-[
|
|
374
|
+
<div className="w-[14px]" />
|
|
375
375
|
)}
|
|
376
376
|
<div
|
|
377
|
-
className={`rounded-md border border-transparent p-0.5 hover:border-gray-300 ${
|
|
377
|
+
className={`rounded-md border border-transparent p-0.5 pr-1.5 hover:border-gray-300 ${
|
|
378
378
|
isDragOver ? "bg-sky-200" : isSelected ? "bg-blue-100" : ""
|
|
379
379
|
}`}
|
|
380
380
|
onClick={handleSelect}
|
|
@@ -26,11 +26,13 @@ export function SimpleIconButton({
|
|
|
26
26
|
disabled={disabled}
|
|
27
27
|
className={classNames(
|
|
28
28
|
typeof icon === "string" ? icon + " p-[6px]" : "p-[4px]",
|
|
29
|
-
"
|
|
30
|
-
disabled
|
|
29
|
+
"rounded-full text-gray-400",
|
|
30
|
+
disabled
|
|
31
|
+
? "cursor-none text-gray-300"
|
|
32
|
+
: "cursor-pointer hover:bg-gray-200",
|
|
31
33
|
className,
|
|
32
34
|
size === "large" ? "text-lg" : "text-xs",
|
|
33
|
-
selected ? "bg-gray-200" : ""
|
|
35
|
+
selected ? "bg-gray-200" : "",
|
|
34
36
|
)}
|
|
35
37
|
onClick={(ev) => {
|
|
36
38
|
if (!disabled) onClick(ev);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import { useEditContext } from "../client/editContext";
|
|
2
|
+
import { EditorMode, useEditContext } from "../client/editContext";
|
|
3
3
|
import { LanguageSelector } from "../menubar/LanguageSelector";
|
|
4
4
|
import { VersionSelector } from "../menubar/VersionSelector";
|
|
5
5
|
import { FullItem, ItemDescriptor, Version } from "../pageModel";
|
|
@@ -57,7 +57,7 @@ export function CompareView() {
|
|
|
57
57
|
editContext.currentItemDescriptor?.version;
|
|
58
58
|
|
|
59
59
|
const language = editContext.itemLanguages.find(
|
|
60
|
-
(x) => x.languageCode === urlLanguage
|
|
60
|
+
(x) => x.languageCode === urlLanguage,
|
|
61
61
|
);
|
|
62
62
|
|
|
63
63
|
if (!language || !language.versions) {
|
|
@@ -108,14 +108,14 @@ export function CompareView() {
|
|
|
108
108
|
newParams.set("compareLanguage", compareToItemDescriptor.language);
|
|
109
109
|
newParams.set(
|
|
110
110
|
"compareVersion",
|
|
111
|
-
compareToItemDescriptor.version.toString()
|
|
111
|
+
compareToItemDescriptor.version.toString(),
|
|
112
112
|
);
|
|
113
113
|
router.push(`?${newParams.toString()}`);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const originalSelector = (
|
|
118
|
-
<div className="flex-1
|
|
118
|
+
<div className="flex flex-1 flex-shrink-0 items-center justify-center border-b bg-gray-100 p-1 text-sm text-gray-600">
|
|
119
119
|
<LanguageSelector
|
|
120
120
|
selectedLanguage={editContext.contentEditorItem?.descriptor?.language}
|
|
121
121
|
showAllLanguagesSwitch={false}
|
|
@@ -150,7 +150,7 @@ export function CompareView() {
|
|
|
150
150
|
);
|
|
151
151
|
|
|
152
152
|
const compareToSelector = (
|
|
153
|
-
<div className="flex-1
|
|
153
|
+
<div className="flex flex-1 flex-shrink-0 items-center justify-center border-b bg-gray-100 p-1 text-sm text-gray-600">
|
|
154
154
|
<LanguageSelector
|
|
155
155
|
selectedLanguage={compareTo?.language}
|
|
156
156
|
showAllLanguagesSwitch={false}
|
|
@@ -189,13 +189,13 @@ export function CompareView() {
|
|
|
189
189
|
comparePageViewContext.device === ""
|
|
190
190
|
)
|
|
191
191
|
return (
|
|
192
|
-
<div className="flex
|
|
192
|
+
<div className="flex h-full w-full flex-col items-stretch">
|
|
193
193
|
<div className="flex w-full">
|
|
194
194
|
{originalSelector}
|
|
195
195
|
{compareToSelector}
|
|
196
196
|
</div>
|
|
197
|
-
<div className="flex-1
|
|
198
|
-
<div className="absolute
|
|
197
|
+
<div className="relative flex-1">
|
|
198
|
+
<div className="absolute h-full w-full">
|
|
199
199
|
<ItemEditor
|
|
200
200
|
pageViewContext={editContext.pageView}
|
|
201
201
|
compareToItem={compareToItem}
|
|
@@ -208,42 +208,31 @@ export function CompareView() {
|
|
|
208
208
|
return (
|
|
209
209
|
<Allotment className="flex h-full w-full">
|
|
210
210
|
<Allotment.Pane>
|
|
211
|
-
<div className="flex
|
|
211
|
+
<div className="flex h-full w-full flex-col items-stretch">
|
|
212
212
|
{originalSelector}
|
|
213
213
|
<SingleEditView
|
|
214
214
|
key="original"
|
|
215
215
|
name="original"
|
|
216
|
-
|
|
216
|
+
compareView={false}
|
|
217
217
|
pageViewContext={editContext.pageView}
|
|
218
218
|
itemDescriptor={editContext.contentEditorItem?.descriptor}
|
|
219
219
|
/>
|
|
220
220
|
</div>
|
|
221
221
|
</Allotment.Pane>
|
|
222
222
|
<Allotment.Pane>
|
|
223
|
-
<div className="flex
|
|
223
|
+
<div className="flex h-full w-full flex-col items-stretch">
|
|
224
224
|
{compareToSelector}
|
|
225
225
|
{compareTo?.language && (
|
|
226
226
|
<SingleEditView
|
|
227
227
|
key="compareTo"
|
|
228
228
|
name="compareTo"
|
|
229
|
-
|
|
230
|
-
compareToItem?.id ===
|
|
231
|
-
editContext.contentEditorItem?.descriptor.id &&
|
|
232
|
-
compareToItem?.language ===
|
|
233
|
-
editContext.contentEditorItem?.descriptor.language &&
|
|
234
|
-
compareToItem?.version ===
|
|
235
|
-
editContext.contentEditorItem?.descriptor.version
|
|
236
|
-
? editContext?.previewMode
|
|
237
|
-
? "view"
|
|
238
|
-
: "edit"
|
|
239
|
-
: "compare"
|
|
240
|
-
}
|
|
229
|
+
compareView={true}
|
|
241
230
|
pageViewContext={comparePageViewContext}
|
|
242
231
|
itemDescriptor={compareTo}
|
|
243
232
|
/>
|
|
244
233
|
)}
|
|
245
234
|
{!compareTo?.language && (
|
|
246
|
-
<div className="flex
|
|
235
|
+
<div className="flex h-full w-full flex-col items-center justify-center">
|
|
247
236
|
<div className="text-gray-400">
|
|
248
237
|
Select a language and version to compare
|
|
249
238
|
</div>
|
|
@@ -8,7 +8,7 @@ export function EditView() {
|
|
|
8
8
|
|
|
9
9
|
if (!editContext.contentEditorItem?.descriptor)
|
|
10
10
|
return (
|
|
11
|
-
<div className="grid items-center justify-center
|
|
11
|
+
<div className="grid h-full w-full items-center justify-center">
|
|
12
12
|
<div className="text-sm text-gray-500">Not item selected</div>
|
|
13
13
|
</div>
|
|
14
14
|
);
|
|
@@ -19,7 +19,7 @@ export function EditView() {
|
|
|
19
19
|
<SingleEditView
|
|
20
20
|
key="single"
|
|
21
21
|
name="single"
|
|
22
|
-
|
|
22
|
+
compareView={false}
|
|
23
23
|
pageViewContext={editContext.pageView}
|
|
24
24
|
itemDescriptor={editContext.contentEditorItem?.descriptor}
|
|
25
25
|
/>
|
|
@@ -9,12 +9,12 @@ import { ItemEditor } from "./ItemEditor";
|
|
|
9
9
|
export function SingleEditView({
|
|
10
10
|
pageViewContext,
|
|
11
11
|
itemDescriptor,
|
|
12
|
-
|
|
12
|
+
compareView,
|
|
13
13
|
name,
|
|
14
14
|
}: {
|
|
15
15
|
pageViewContext: PageViewContext;
|
|
16
16
|
itemDescriptor?: ItemDescriptor;
|
|
17
|
-
|
|
17
|
+
compareView: boolean;
|
|
18
18
|
name: string;
|
|
19
19
|
}) {
|
|
20
20
|
const editContext = useEditContext();
|
|
@@ -34,9 +34,9 @@ export function SingleEditView({
|
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<PageViewer
|
|
37
|
-
mode={mode}
|
|
38
37
|
pageViewContext={pageViewContext}
|
|
39
38
|
showFormEditor={true}
|
|
39
|
+
compareView={compareView}
|
|
40
40
|
name={name}
|
|
41
41
|
followEditsDefault={true}
|
|
42
42
|
/>
|
package/src/lib/safelist.tsx
CHANGED
|
@@ -5,10 +5,12 @@ export function Safelist() {
|
|
|
5
5
|
<div className="bg-purple-400"></div>
|
|
6
6
|
<div className="bg-sky-400"></div>
|
|
7
7
|
<div className="bg-red-400"></div>
|
|
8
|
+
<div className="bg-teal-400"></div>
|
|
8
9
|
<div className="border-orange-400"></div>
|
|
9
10
|
<div className="border-purple-400"></div>
|
|
10
11
|
<div className="border-sky-400"></div>
|
|
11
12
|
<div className="border-red-400"></div>
|
|
13
|
+
<div className="border-teal-400"></div>
|
|
12
14
|
</>
|
|
13
15
|
);
|
|
14
16
|
}
|