@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
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
EditContextType,
|
|
22
22
|
EditedField,
|
|
23
23
|
SelectionRange,
|
|
24
|
+
EditorMode,
|
|
24
25
|
} from "./editContext";
|
|
25
26
|
|
|
26
27
|
import { EditorConfiguration, MenuItem } from "../../config/types";
|
|
@@ -80,6 +81,7 @@ import {
|
|
|
80
81
|
ValidationResult,
|
|
81
82
|
WorkboxItem,
|
|
82
83
|
Comment,
|
|
84
|
+
SuggestedEdit,
|
|
83
85
|
} from "../../types";
|
|
84
86
|
|
|
85
87
|
import { post } from "../services/serviceHelper";
|
|
@@ -112,6 +114,7 @@ import { AiTerminalOptions } from "../ai/AiTerminal";
|
|
|
112
114
|
import { useReviews } from "../reviews/useReviews";
|
|
113
115
|
import uuid from "react-uuid";
|
|
114
116
|
import { flushSync } from "react-dom";
|
|
117
|
+
import { getSuggestedEdits } from "../services/suggestedEditsService";
|
|
115
118
|
|
|
116
119
|
export type FieldAction = {
|
|
117
120
|
field: FieldDescriptor;
|
|
@@ -237,6 +240,9 @@ export function EditorClient({
|
|
|
237
240
|
const [itemVersions, setItemVersions] = useState<Version[]>([]);
|
|
238
241
|
const [modifiedFields, setModifiedFields] = useState<ModifiedField[]>([]);
|
|
239
242
|
const [comments, setComments] = useState<Comment[]>([]);
|
|
243
|
+
const [suggestedEdits, setSuggestedEdits] = useState<SuggestedEdit[]>([]);
|
|
244
|
+
const [showSuggestedEdits, setShowSuggestedEdits] = useState(false);
|
|
245
|
+
const [showSuggestedEditsDiff, setShowSuggestedEditsDiff] = useState(false);
|
|
240
246
|
const [showComments, setShowComments] = useState<boolean>(() => {
|
|
241
247
|
const savedShowComments =
|
|
242
248
|
typeof window !== "undefined"
|
|
@@ -269,21 +275,20 @@ export function EditorClient({
|
|
|
269
275
|
const [workboxItems, setWorkboxItems] = useState<WorkboxItem[]>([]);
|
|
270
276
|
const [isTourActive, setIsTourActive] = useState(false);
|
|
271
277
|
|
|
272
|
-
const [
|
|
273
|
-
const [statusMessage, setStatusMessage] = useState<React.ReactNode>("");
|
|
278
|
+
const [mode, setMode] = useState<EditorMode>("edit");
|
|
274
279
|
|
|
275
|
-
const
|
|
280
|
+
const [statusMessage, setStatusMessage] = useState<React.ReactNode>("");
|
|
276
281
|
|
|
277
282
|
useEffect(() => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}, [mode]);
|
|
283
|
+
const queryMode = searchParams.get("mode");
|
|
284
|
+
if (queryMode) setMode(queryMode as EditorMode);
|
|
285
|
+
}, [searchParams]);
|
|
282
286
|
|
|
283
287
|
useEffect(() => {
|
|
284
|
-
if (
|
|
288
|
+
if (mode === "suggestions") {
|
|
289
|
+
setViewName("reviews");
|
|
285
290
|
}
|
|
286
|
-
}, [
|
|
291
|
+
}, [mode]);
|
|
287
292
|
|
|
288
293
|
useEffect(() => {
|
|
289
294
|
if (
|
|
@@ -431,6 +436,23 @@ export function EditorClient({
|
|
|
431
436
|
});
|
|
432
437
|
}
|
|
433
438
|
|
|
439
|
+
if (message.type === "suggested-edit-updated") {
|
|
440
|
+
setSuggestedEdits((x) => {
|
|
441
|
+
const index = x.findIndex(
|
|
442
|
+
(s) => s.id === message.payload.suggestedEdit.id,
|
|
443
|
+
);
|
|
444
|
+
if (index !== -1) x[index] = message.payload.suggestedEdit;
|
|
445
|
+
else x.push(message.payload.suggestedEdit);
|
|
446
|
+
return x;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (message.type === "suggested-edit-deleted") {
|
|
451
|
+
setSuggestedEdits((x) => {
|
|
452
|
+
return x.filter((s) => s.id !== message.payload.id);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
434
456
|
if (message.type === "edit-operation") {
|
|
435
457
|
const op = message.payload as EditOperation;
|
|
436
458
|
|
|
@@ -652,6 +674,22 @@ export function EditorClient({
|
|
|
652
674
|
});
|
|
653
675
|
}, [currentItemDescriptor]);
|
|
654
676
|
|
|
677
|
+
// Assuming currentItemDescriptor, ui, state, handleErrorResult, and setSuggestedEdits
|
|
678
|
+
// are available in your component context.
|
|
679
|
+
const loadSuggestedEdits = useCallback(async () => {
|
|
680
|
+
if (!contentEditorItem) return;
|
|
681
|
+
|
|
682
|
+
const result = await getSuggestedEdits(
|
|
683
|
+
contentEditorItem.descriptor.id,
|
|
684
|
+
contentEditorItem.descriptor.language,
|
|
685
|
+
contentEditorItem.descriptor.version,
|
|
686
|
+
);
|
|
687
|
+
|
|
688
|
+
if (handleErrorResult(result, ui, state)) return;
|
|
689
|
+
|
|
690
|
+
setSuggestedEdits(result.data || []);
|
|
691
|
+
}, [contentEditorItem]);
|
|
692
|
+
|
|
655
693
|
const page = pageViewContext.page;
|
|
656
694
|
|
|
657
695
|
useEffect(() => {
|
|
@@ -730,6 +768,7 @@ export function EditorClient({
|
|
|
730
768
|
useEffect(() => {
|
|
731
769
|
if (!currentItemDescriptor) return;
|
|
732
770
|
loadComments();
|
|
771
|
+
loadSuggestedEdits();
|
|
733
772
|
}, [currentItemDescriptor]);
|
|
734
773
|
|
|
735
774
|
useEffect(() => {
|
|
@@ -757,15 +796,11 @@ export function EditorClient({
|
|
|
757
796
|
current.set("compare", "true");
|
|
758
797
|
}
|
|
759
798
|
|
|
760
|
-
|
|
761
|
-
current.set("mode", "preview");
|
|
762
|
-
} else {
|
|
763
|
-
current.delete("mode");
|
|
764
|
-
}
|
|
799
|
+
current.set("mode", mode);
|
|
765
800
|
|
|
766
801
|
const newUrl = `${pathname}?${current.toString()}`;
|
|
767
802
|
router.push(newUrl, { scroll: false });
|
|
768
|
-
}, [currentItemDescriptor, viewName, compareMode,
|
|
803
|
+
}, [currentItemDescriptor, viewName, compareMode, mode]);
|
|
769
804
|
|
|
770
805
|
useEffect(() => {
|
|
771
806
|
async function load() {
|
|
@@ -877,11 +912,7 @@ export function EditorClient({
|
|
|
877
912
|
current.set("compare", "true");
|
|
878
913
|
}
|
|
879
914
|
|
|
880
|
-
|
|
881
|
-
current.set("mode", "preview");
|
|
882
|
-
} else {
|
|
883
|
-
current.delete("mode");
|
|
884
|
-
}
|
|
915
|
+
current.set("mode", mode);
|
|
885
916
|
|
|
886
917
|
const newUrl = `${pathname}?${current.toString()}`;
|
|
887
918
|
|
|
@@ -907,7 +938,6 @@ export function EditorClient({
|
|
|
907
938
|
router,
|
|
908
939
|
viewName,
|
|
909
940
|
compareMode,
|
|
910
|
-
previewMode,
|
|
911
941
|
],
|
|
912
942
|
);
|
|
913
943
|
|
|
@@ -1061,6 +1091,9 @@ export function EditorClient({
|
|
|
1061
1091
|
user,
|
|
1062
1092
|
editHistory,
|
|
1063
1093
|
refreshHistory,
|
|
1094
|
+
suggestedEdits,
|
|
1095
|
+
setSuggestedEdits,
|
|
1096
|
+
mode,
|
|
1064
1097
|
}),
|
|
1065
1098
|
[
|
|
1066
1099
|
page,
|
|
@@ -1078,6 +1111,9 @@ export function EditorClient({
|
|
|
1078
1111
|
user,
|
|
1079
1112
|
editHistory,
|
|
1080
1113
|
refreshHistory,
|
|
1114
|
+
suggestedEdits,
|
|
1115
|
+
setSuggestedEdits,
|
|
1116
|
+
mode,
|
|
1081
1117
|
],
|
|
1082
1118
|
);
|
|
1083
1119
|
|
|
@@ -1279,8 +1315,17 @@ export function EditorClient({
|
|
|
1279
1315
|
};
|
|
1280
1316
|
|
|
1281
1317
|
const operationsContext = getOperationsContext(state, ui);
|
|
1318
|
+
|
|
1282
1319
|
const operations = operationsContext.ops;
|
|
1283
1320
|
|
|
1321
|
+
useEffect(() => {
|
|
1322
|
+
if (mode === "suggestions") {
|
|
1323
|
+
setShowSuggestedEdits(true);
|
|
1324
|
+
} else {
|
|
1325
|
+
setShowSuggestedEdits(false);
|
|
1326
|
+
}
|
|
1327
|
+
}, [mode]);
|
|
1328
|
+
|
|
1284
1329
|
//const pageItem = page ? itemsRepository.getItem(page.item) : undefined;
|
|
1285
1330
|
const isReadOnly = false; //pageItem && (!pageItem.hasLock || !pageItem.canWrite);
|
|
1286
1331
|
|
|
@@ -1302,11 +1347,9 @@ export function EditorClient({
|
|
|
1302
1347
|
openSplashScreen: () => {
|
|
1303
1348
|
router.push("/alpaca/editor");
|
|
1304
1349
|
},
|
|
1305
|
-
|
|
1306
1350
|
item: contentEditorItem || page?.item,
|
|
1307
1351
|
itemLanguages,
|
|
1308
1352
|
itemVersions,
|
|
1309
|
-
|
|
1310
1353
|
sessionId: sessionId,
|
|
1311
1354
|
readonly: isReadOnly,
|
|
1312
1355
|
selection,
|
|
@@ -1315,12 +1358,10 @@ export function EditorClient({
|
|
|
1315
1358
|
},
|
|
1316
1359
|
selectedForInsertion,
|
|
1317
1360
|
setSelectedForInsertion,
|
|
1318
|
-
|
|
1319
1361
|
dragObject,
|
|
1320
1362
|
workboxItems,
|
|
1321
1363
|
requestRefresh,
|
|
1322
1364
|
refreshCompletedFlag,
|
|
1323
|
-
|
|
1324
1365
|
openCreatePageDialog: () => {
|
|
1325
1366
|
const current = new URLSearchParams(Array.from(searchParams.entries()));
|
|
1326
1367
|
current.delete("version");
|
|
@@ -1342,8 +1383,6 @@ export function EditorClient({
|
|
|
1342
1383
|
requestLock: boolean,
|
|
1343
1384
|
) => {
|
|
1344
1385
|
if (field) {
|
|
1345
|
-
// if (nonComponentItems.find((x) => x.id == field.item.id))
|
|
1346
|
-
// setSelection([field.item.id]);
|
|
1347
1386
|
setIgnoreBlur(true);
|
|
1348
1387
|
setTimeout(() => {
|
|
1349
1388
|
setIgnoreBlur(false);
|
|
@@ -1360,14 +1399,11 @@ export function EditorClient({
|
|
|
1360
1399
|
}
|
|
1361
1400
|
return true;
|
|
1362
1401
|
},
|
|
1363
|
-
|
|
1364
1402
|
renderedFields,
|
|
1365
1403
|
setRenderedFields,
|
|
1366
|
-
|
|
1367
1404
|
getComponentCommands: (components: Component[]) => {
|
|
1368
1405
|
return getComponentCommands(components, editContext);
|
|
1369
1406
|
},
|
|
1370
|
-
|
|
1371
1407
|
dragStart: (dragObject: DragObject) => {
|
|
1372
1408
|
setDragObject(dragObject);
|
|
1373
1409
|
},
|
|
@@ -1517,13 +1553,11 @@ export function EditorClient({
|
|
|
1517
1553
|
requestRefresh("immediate");
|
|
1518
1554
|
}
|
|
1519
1555
|
},
|
|
1520
|
-
|
|
1521
1556
|
activeFieldActions,
|
|
1522
1557
|
showContextMenu: (event: any, items: MenuItem[]) => {
|
|
1523
1558
|
contextMenuRef.current?.show(event, items);
|
|
1524
1559
|
setCurrentOverlay("context-menu");
|
|
1525
1560
|
},
|
|
1526
|
-
|
|
1527
1561
|
showAiPopup: (
|
|
1528
1562
|
event: MouseEvent<HTMLElement>,
|
|
1529
1563
|
aiTerminalOptions?: AiTerminalOptions,
|
|
@@ -1531,7 +1565,6 @@ export function EditorClient({
|
|
|
1531
1565
|
setCurrentOverlay("ai");
|
|
1532
1566
|
aiPopupRef.current?.show(event, aiTerminalOptions);
|
|
1533
1567
|
},
|
|
1534
|
-
|
|
1535
1568
|
showFieldEditorPopup: (fields: Field[], sections: string[], ev: any) => {
|
|
1536
1569
|
setCurrentOverlay("fields");
|
|
1537
1570
|
fieldEditorPopupRef.current?.show(fields, sections, ev);
|
|
@@ -1540,16 +1573,13 @@ export function EditorClient({
|
|
|
1540
1573
|
validating,
|
|
1541
1574
|
validationResult,
|
|
1542
1575
|
contentEditorItem,
|
|
1543
|
-
|
|
1544
1576
|
loadItem,
|
|
1545
1577
|
editHistory,
|
|
1546
1578
|
isRefreshing,
|
|
1547
1579
|
activeSessions,
|
|
1548
|
-
|
|
1549
1580
|
unlockField: async () => {
|
|
1550
1581
|
await releaseFieldLocks(sessionId);
|
|
1551
1582
|
},
|
|
1552
|
-
|
|
1553
1583
|
isCommandDisabled: <T extends CommandData>({
|
|
1554
1584
|
command,
|
|
1555
1585
|
data,
|
|
@@ -1585,15 +1615,12 @@ export function EditorClient({
|
|
|
1585
1615
|
setCurrentOverlay,
|
|
1586
1616
|
inlineEditingFieldElement,
|
|
1587
1617
|
setInlineEditingFieldElement,
|
|
1588
|
-
|
|
1589
1618
|
lockedField,
|
|
1590
1619
|
timings,
|
|
1591
1620
|
setTimings,
|
|
1592
1621
|
selectedRange,
|
|
1593
1622
|
setSelectedRange,
|
|
1594
|
-
|
|
1595
1623
|
confirmationDialogRef,
|
|
1596
|
-
|
|
1597
1624
|
confirm: (props: ConfirmationProps) => {
|
|
1598
1625
|
confirmationDialogRef.current?.confirm(props);
|
|
1599
1626
|
},
|
|
@@ -1672,19 +1699,24 @@ export function EditorClient({
|
|
|
1672
1699
|
setSelectedComment(newComment);
|
|
1673
1700
|
setShowComments(true);
|
|
1674
1701
|
},
|
|
1675
|
-
|
|
1676
|
-
|
|
1702
|
+
mode,
|
|
1703
|
+
setMode,
|
|
1704
|
+
|
|
1677
1705
|
user,
|
|
1678
1706
|
reviews,
|
|
1679
1707
|
statusMessage,
|
|
1680
1708
|
setStatusMessage,
|
|
1709
|
+
suggestedEdits,
|
|
1710
|
+
showSuggestedEdits,
|
|
1711
|
+
setShowSuggestedEdits,
|
|
1712
|
+
showSuggestedEditsDiff,
|
|
1713
|
+
setShowSuggestedEditsDiff,
|
|
1681
1714
|
};
|
|
1682
1715
|
}, [
|
|
1683
1716
|
operations,
|
|
1684
1717
|
itemsRepository,
|
|
1685
1718
|
configuration,
|
|
1686
1719
|
contentEditorItem,
|
|
1687
|
-
|
|
1688
1720
|
page?.item,
|
|
1689
1721
|
itemLanguages,
|
|
1690
1722
|
itemVersions,
|
|
@@ -1716,7 +1748,6 @@ export function EditorClient({
|
|
|
1716
1748
|
inlineEditingFieldElement,
|
|
1717
1749
|
lockedField,
|
|
1718
1750
|
selectedRange,
|
|
1719
|
-
|
|
1720
1751
|
pageViewContext,
|
|
1721
1752
|
browseHistory,
|
|
1722
1753
|
workboxItems,
|
|
@@ -1741,14 +1772,18 @@ export function EditorClient({
|
|
|
1741
1772
|
selectedComment,
|
|
1742
1773
|
setSelectedComment,
|
|
1743
1774
|
loadComments,
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
showComments,
|
|
1747
|
-
setShowComments,
|
|
1775
|
+
mode,
|
|
1776
|
+
setMode,
|
|
1748
1777
|
user,
|
|
1749
1778
|
reviews,
|
|
1750
1779
|
statusMessage,
|
|
1751
1780
|
setStatusMessage,
|
|
1781
|
+
suggestedEdits,
|
|
1782
|
+
setSuggestedEdits,
|
|
1783
|
+
showSuggestedEdits,
|
|
1784
|
+
setShowSuggestedEdits,
|
|
1785
|
+
showSuggestedEditsDiff,
|
|
1786
|
+
setShowSuggestedEditsDiff,
|
|
1752
1787
|
]);
|
|
1753
1788
|
|
|
1754
1789
|
const modifiedFieldsContext = useMemo(() => {
|
|
@@ -1773,7 +1808,7 @@ export function EditorClient({
|
|
|
1773
1808
|
<>
|
|
1774
1809
|
<div className="fixed inset-0 flex">
|
|
1775
1810
|
<PageViewerFrame
|
|
1776
|
-
|
|
1811
|
+
compareView={compareMode}
|
|
1777
1812
|
pageViewContext={pageViewContext}
|
|
1778
1813
|
/>
|
|
1779
1814
|
</div>
|
|
@@ -1795,11 +1830,8 @@ export function EditorClient({
|
|
|
1795
1830
|
) : (
|
|
1796
1831
|
<>
|
|
1797
1832
|
<Toast ref={toast} />
|
|
1798
|
-
|
|
1799
1833
|
<ConfirmationDialog ref={confirmationDialogRef} />
|
|
1800
|
-
|
|
1801
1834
|
<EditContextMenu ref={contextMenuRef} />
|
|
1802
|
-
|
|
1803
1835
|
<MainLayout
|
|
1804
1836
|
className={className}
|
|
1805
1837
|
view={currentView}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
WorkboxItem,
|
|
29
29
|
Review,
|
|
30
30
|
AddComponentOperation,
|
|
31
|
+
SuggestedEdit,
|
|
31
32
|
} from "../../types";
|
|
32
33
|
|
|
33
34
|
import { ConfirmationProps } from "../ConfirmationDialog";
|
|
@@ -63,6 +64,8 @@ export type SelectionRange = {
|
|
|
63
64
|
text: string;
|
|
64
65
|
};
|
|
65
66
|
|
|
67
|
+
export type EditorMode = "edit" | "preview" | "suggestions";
|
|
68
|
+
|
|
66
69
|
export type EditContextType = {
|
|
67
70
|
operations: {
|
|
68
71
|
moveItems: (
|
|
@@ -106,6 +109,7 @@ export type EditContextType = {
|
|
|
106
109
|
undoing: boolean;
|
|
107
110
|
};
|
|
108
111
|
comments: Comment[];
|
|
112
|
+
suggestedEdits: SuggestedEdit[];
|
|
109
113
|
loadComments: () => void;
|
|
110
114
|
setComments: React.Dispatch<React.SetStateAction<Comment[]>>;
|
|
111
115
|
selectedComment: Comment | undefined;
|
|
@@ -125,6 +129,14 @@ export type EditContextType = {
|
|
|
125
129
|
switchView: (viewName: string) => void;
|
|
126
130
|
compareMode: boolean;
|
|
127
131
|
setCompareMode: (compareMode: boolean) => void;
|
|
132
|
+
|
|
133
|
+
mode: EditorMode;
|
|
134
|
+
setMode: React.Dispatch<React.SetStateAction<EditorMode>>;
|
|
135
|
+
|
|
136
|
+
showSuggestedEdits: boolean;
|
|
137
|
+
setShowSuggestedEdits: React.Dispatch<React.SetStateAction<boolean>>;
|
|
138
|
+
showSuggestedEditsDiff: boolean;
|
|
139
|
+
setShowSuggestedEditsDiff: React.Dispatch<React.SetStateAction<boolean>>;
|
|
128
140
|
setCenterPanelView: (view: ReactNode) => void;
|
|
129
141
|
configuration: EditorConfiguration;
|
|
130
142
|
showToast: (message: ToastMessage | ToastMessage[]) => void;
|
|
@@ -141,9 +153,6 @@ export type EditContextType = {
|
|
|
141
153
|
|
|
142
154
|
updateUrl: (params: Record<string, string>) => void;
|
|
143
155
|
|
|
144
|
-
previewMode: boolean;
|
|
145
|
-
setPreviewMode: React.Dispatch<React.SetStateAction<boolean>>;
|
|
146
|
-
|
|
147
156
|
selection: string[];
|
|
148
157
|
select: (ids: string[]) => void;
|
|
149
158
|
selectedForInsertion: string;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
EditOperation,
|
|
7
7
|
ExecuteEditOptions,
|
|
8
8
|
RenameItemOperation,
|
|
9
|
+
SuggestedEdit,
|
|
9
10
|
User,
|
|
10
11
|
} from "../../types";
|
|
11
12
|
import { FieldDescriptor, FullItem, ItemDescriptor, Page } from "../pageModel";
|
|
@@ -29,6 +30,9 @@ import { ConfirmationDialogHandle } from "../ConfirmationDialog";
|
|
|
29
30
|
import { handleErrorResult } from "./helpers";
|
|
30
31
|
import { ItemsRepository } from "./itemsRepository";
|
|
31
32
|
import { useDebouncedCallback } from "use-debounce";
|
|
33
|
+
import { createOrUpdateSuggestedEdit } from "../services/suggestedEditsService";
|
|
34
|
+
import { decode } from "html-entities";
|
|
35
|
+
import { EditorMode } from "./editContext";
|
|
32
36
|
|
|
33
37
|
export function getOperationsContext(
|
|
34
38
|
state: {
|
|
@@ -45,6 +49,9 @@ export function getOperationsContext(
|
|
|
45
49
|
itemsRepository: ItemsRepository;
|
|
46
50
|
editHistory: EditOperation[];
|
|
47
51
|
refreshHistory: (item: ItemDescriptor) => Promise<void>;
|
|
52
|
+
suggestedEdits: SuggestedEdit[];
|
|
53
|
+
setSuggestedEdits: (suggestedEdits: SuggestedEdit[]) => void;
|
|
54
|
+
mode: EditorMode;
|
|
48
55
|
},
|
|
49
56
|
ui: {
|
|
50
57
|
showErrorToast: (error: { summary?: string; details?: string }) => void;
|
|
@@ -279,6 +286,20 @@ export function getOperationsContext(
|
|
|
279
286
|
rawValue?: string | null;
|
|
280
287
|
refresh?: "none" | "immediate" | "delayed" | "waitForQuietPeriod";
|
|
281
288
|
}): Promise<void> => {
|
|
289
|
+
if (state.mode === "suggestions") {
|
|
290
|
+
const op = await getOrMergeSuggestedEditOp(field, rawValue, value, {
|
|
291
|
+
page: state.page,
|
|
292
|
+
user: state.user,
|
|
293
|
+
itemsRepository: state.itemsRepository,
|
|
294
|
+
suggestedEdits: state.suggestedEdits,
|
|
295
|
+
setSuggestedEdits: state.setSuggestedEdits,
|
|
296
|
+
});
|
|
297
|
+
if (op) {
|
|
298
|
+
await createOrUpdateSuggestedEdit(op);
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
282
303
|
state.itemsRepository.updateFieldValue(
|
|
283
304
|
field,
|
|
284
305
|
state.user ?? { name: "unknown", ai: false },
|
|
@@ -286,6 +307,7 @@ export function getOperationsContext(
|
|
|
286
307
|
value,
|
|
287
308
|
rawValue,
|
|
288
309
|
);
|
|
310
|
+
|
|
289
311
|
if (refresh === "immediate") {
|
|
290
312
|
return editFieldImmediate({ field, value, rawValue, refresh });
|
|
291
313
|
}
|
|
@@ -300,7 +322,14 @@ export function getOperationsContext(
|
|
|
300
322
|
lastEditField.current = field;
|
|
301
323
|
return executeEditFieldDebounced({ field, value, rawValue, refresh });
|
|
302
324
|
},
|
|
303
|
-
[
|
|
325
|
+
[
|
|
326
|
+
state.itemsRepository,
|
|
327
|
+
getOrMergeSuggestedEditOp,
|
|
328
|
+
state.suggestedEdits,
|
|
329
|
+
state.page,
|
|
330
|
+
state.user,
|
|
331
|
+
state.mode,
|
|
332
|
+
],
|
|
304
333
|
);
|
|
305
334
|
|
|
306
335
|
const editFieldImmediate = useCallback(
|
|
@@ -315,13 +344,13 @@ export function getOperationsContext(
|
|
|
315
344
|
rawValue?: string | null;
|
|
316
345
|
refresh?: "none" | "immediate" | "delayed" | "waitForQuietPeriod";
|
|
317
346
|
}): Promise<void> => {
|
|
318
|
-
state.itemsRepository.updateFieldValue(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
347
|
+
// state.itemsRepository.updateFieldValue(
|
|
348
|
+
// field,
|
|
349
|
+
// state.user ?? { name: "unknown", ai: false },
|
|
350
|
+
// true,
|
|
351
|
+
// value,
|
|
352
|
+
// rawValue,
|
|
353
|
+
// );
|
|
325
354
|
|
|
326
355
|
const val = rawValue !== undefined ? rawValue : value;
|
|
327
356
|
if (val === undefined) return;
|
|
@@ -338,7 +367,26 @@ export function getOperationsContext(
|
|
|
338
367
|
);
|
|
339
368
|
|
|
340
369
|
const executeEditFieldDebounced = useDebouncedCallback(
|
|
341
|
-
|
|
370
|
+
async ({
|
|
371
|
+
field,
|
|
372
|
+
value,
|
|
373
|
+
rawValue,
|
|
374
|
+
refresh,
|
|
375
|
+
}: {
|
|
376
|
+
field: FieldDescriptor;
|
|
377
|
+
value?: string;
|
|
378
|
+
rawValue?: string | null;
|
|
379
|
+
refresh?: "none" | "immediate" | "delayed" | "waitForQuietPeriod";
|
|
380
|
+
}) => {
|
|
381
|
+
state.itemsRepository.updateFieldValue(
|
|
382
|
+
field,
|
|
383
|
+
state.user ?? { name: "unknown", ai: false },
|
|
384
|
+
true,
|
|
385
|
+
value,
|
|
386
|
+
rawValue,
|
|
387
|
+
);
|
|
388
|
+
editFieldImmediate({ field, value, rawValue, refresh });
|
|
389
|
+
},
|
|
342
390
|
state.configuration.debounceFieldEditsInterval * 2,
|
|
343
391
|
{ trailing: true },
|
|
344
392
|
);
|
|
@@ -596,6 +644,8 @@ export function getOperationsContext(
|
|
|
596
644
|
duplicateComponents,
|
|
597
645
|
undoing.current,
|
|
598
646
|
moveItems,
|
|
647
|
+
state.mode,
|
|
648
|
+
state.page,
|
|
599
649
|
],
|
|
600
650
|
);
|
|
601
651
|
|
|
@@ -610,3 +660,90 @@ export function getOperationsContext(
|
|
|
610
660
|
[executingEditOperations, editOperationExecuted, ops, undoing],
|
|
611
661
|
);
|
|
612
662
|
}
|
|
663
|
+
|
|
664
|
+
async function getOrMergeSuggestedEditOp(
|
|
665
|
+
field: FieldDescriptor,
|
|
666
|
+
rawValue: string | null | undefined,
|
|
667
|
+
value: string | undefined,
|
|
668
|
+
state: {
|
|
669
|
+
page?: {
|
|
670
|
+
item: { descriptor: { id: string; language: string; version: number } };
|
|
671
|
+
};
|
|
672
|
+
user?: { name: string };
|
|
673
|
+
itemsRepository: { getItem: (descriptor: any) => Promise<any> };
|
|
674
|
+
// Assume you have access to current suggested edits in context or state:
|
|
675
|
+
suggestedEdits: SuggestedEdit[];
|
|
676
|
+
setSuggestedEdits: (suggestedEdits: SuggestedEdit[]) => void;
|
|
677
|
+
},
|
|
678
|
+
): Promise<SuggestedEdit | undefined> {
|
|
679
|
+
const item = await state.itemsRepository.getItem(field.item);
|
|
680
|
+
if (!item) return;
|
|
681
|
+
|
|
682
|
+
const fieldItem = item.fields.find(
|
|
683
|
+
(f: { id: string; rawValue?: string }) => f.id === field.fieldId,
|
|
684
|
+
);
|
|
685
|
+
if (!fieldItem) {
|
|
686
|
+
console.log(
|
|
687
|
+
"Could not locate field in item for suggested edit:",
|
|
688
|
+
field.fieldId,
|
|
689
|
+
);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const newVal = rawValue !== undefined ? rawValue : value;
|
|
694
|
+
if (newVal === undefined) return;
|
|
695
|
+
|
|
696
|
+
if (!state.page) {
|
|
697
|
+
console.log("ERROR: No page");
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const page = state.page;
|
|
701
|
+
|
|
702
|
+
// Attempt to find an existing suggested edit by the same user for this field.
|
|
703
|
+
const existing = state.suggestedEdits.find(
|
|
704
|
+
(edit) =>
|
|
705
|
+
edit.mainItemId === page.item.descriptor.id &&
|
|
706
|
+
edit.mainItemLanguage === page.item.descriptor.language &&
|
|
707
|
+
edit.mainItemVersion === page.item.descriptor.version &&
|
|
708
|
+
edit.itemId === item.id &&
|
|
709
|
+
edit.fieldId === field.fieldId &&
|
|
710
|
+
edit.author === (state.user?.name || "unknown") &&
|
|
711
|
+
edit.status === "Pending", // or any status that indicates it's open for further editing.
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
if (existing) {
|
|
715
|
+
// Update the existing suggestion.
|
|
716
|
+
existing.newValue = newVal || "";
|
|
717
|
+
existing.updated = new Date().toISOString();
|
|
718
|
+
existing.updatedBy = state.user?.name || "unknown";
|
|
719
|
+
// Optionally, you might update the comments or other fields.
|
|
720
|
+
state.setSuggestedEdits([...state.suggestedEdits]);
|
|
721
|
+
return existing;
|
|
722
|
+
} else {
|
|
723
|
+
// Create a new suggested edit if none exists.
|
|
724
|
+
const newValue = newVal || "";
|
|
725
|
+
const oldValue = fieldItem.rawValue || "";
|
|
726
|
+
if (decode(newValue) === decode(oldValue)) return;
|
|
727
|
+
|
|
728
|
+
const newEdit: SuggestedEdit = {
|
|
729
|
+
id: uuid(),
|
|
730
|
+
mainItemId: state.page?.item.descriptor.id,
|
|
731
|
+
mainItemLanguage: state.page?.item.descriptor.language,
|
|
732
|
+
mainItemVersion: state.page?.item.descriptor.version,
|
|
733
|
+
itemId: item.id,
|
|
734
|
+
fieldId: field.fieldId,
|
|
735
|
+
oldValue: fieldItem.rawValue || "",
|
|
736
|
+
newValue: newVal || "",
|
|
737
|
+
author: state.user?.name || "unknown",
|
|
738
|
+
created: new Date().toISOString(),
|
|
739
|
+
status: "Pending",
|
|
740
|
+
updatedBy: state.user?.name || "unknown",
|
|
741
|
+
updated: new Date().toISOString(),
|
|
742
|
+
comments: "",
|
|
743
|
+
type: "FieldValue",
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
state.setSuggestedEdits([...state.suggestedEdits, newEdit]);
|
|
747
|
+
return newEdit;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
@@ -31,7 +31,7 @@ export function LanguageSelector({
|
|
|
31
31
|
const allLanguages = editContext?.itemLanguages || [];
|
|
32
32
|
|
|
33
33
|
const currentLanguage = allLanguages.find(
|
|
34
|
-
(x) => x.languageCode === selectedLanguage
|
|
34
|
+
(x) => x.languageCode === selectedLanguage,
|
|
35
35
|
);
|
|
36
36
|
|
|
37
37
|
const languages = showAllLanguagesInternal
|
|
@@ -49,11 +49,11 @@ export function LanguageSelector({
|
|
|
49
49
|
return (
|
|
50
50
|
<>
|
|
51
51
|
<div
|
|
52
|
-
className={`
|
|
52
|
+
className={`flex cursor-pointer items-center gap-3 p-[7px] py-[5px] text-sm ${
|
|
53
53
|
darkMode
|
|
54
54
|
? "text-gray-500 hover:bg-gray-300"
|
|
55
55
|
: "text-gray-200 hover:bg-gray-500"
|
|
56
|
-
}
|
|
56
|
+
} rounded-md ${disabled ? "opacity-50" : ""}`}
|
|
57
57
|
onClick={(ev) => {
|
|
58
58
|
if (disabled) return;
|
|
59
59
|
overlaypanel.current?.toggle(ev, ev.currentTarget);
|
|
@@ -82,9 +82,9 @@ export function LanguageSelector({
|
|
|
82
82
|
</div>
|
|
83
83
|
</div>
|
|
84
84
|
<OverlayPanel dismissable={true} ref={overlaypanel} closeOnEscape>
|
|
85
|
-
<div className="max-h-[50vh] min-w-64 flex
|
|
85
|
+
<div className="flex max-h-[50vh] min-w-64 flex-col">
|
|
86
86
|
{showAllLanguagesSwitch && (
|
|
87
|
-
<div className="
|
|
87
|
+
<div className="flex items-center justify-center gap-2 p-2 text-xs">
|
|
88
88
|
<span
|
|
89
89
|
className="cursor-pointer"
|
|
90
90
|
onClick={() => setShowAllLanguagesInternal(false)}
|
|
@@ -108,7 +108,7 @@ export function LanguageSelector({
|
|
|
108
108
|
{languages.map((x) => (
|
|
109
109
|
<div
|
|
110
110
|
key={x.languageCode}
|
|
111
|
-
className="cursor-pointer hover:bg-gray-200
|
|
111
|
+
className="flex cursor-pointer gap-2 p-2 hover:bg-gray-200"
|
|
112
112
|
onClick={() => selectLanguage(x)}
|
|
113
113
|
>
|
|
114
114
|
<img src={x.icon} className="h-5" /> {x.name} ({x.versions})
|