@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.
Files changed (119) hide show
  1. package/dist/config/config.js +1 -1
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/EditorWarnings.js +1 -1
  4. package/dist/editor/EditorWarnings.js.map +1 -1
  5. package/dist/editor/FieldList.js +1 -1
  6. package/dist/editor/FieldList.js.map +1 -1
  7. package/dist/editor/FieldListFieldWithFallbacks.js +3 -3
  8. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  9. package/dist/editor/Titlebar.js +1 -1
  10. package/dist/editor/Titlebar.js.map +1 -1
  11. package/dist/editor/client/EditorClient.js +71 -31
  12. package/dist/editor/client/EditorClient.js.map +1 -1
  13. package/dist/editor/client/editContext.d.ts +9 -3
  14. package/dist/editor/client/editContext.js.map +1 -1
  15. package/dist/editor/client/operations.d.ts +5 -1
  16. package/dist/editor/client/operations.js +97 -3
  17. package/dist/editor/client/operations.js.map +1 -1
  18. package/dist/editor/component-designer/ComponentDesigner.js +1 -1
  19. package/dist/editor/component-designer/ComponentDesigner.js.map +1 -1
  20. package/dist/editor/menubar/LanguageSelector.js +3 -3
  21. package/dist/editor/menubar/LanguageSelector.js.map +1 -1
  22. package/dist/editor/menubar/PageSelector.js +1 -1
  23. package/dist/editor/menubar/PageSelector.js.map +1 -1
  24. package/dist/editor/menubar/PageViewerControls.js +12 -7
  25. package/dist/editor/menubar/PageViewerControls.js.map +1 -1
  26. package/dist/editor/menubar/Separator.js +1 -1
  27. package/dist/editor/menubar/VersionSelector.js +1 -1
  28. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  29. package/dist/editor/page-editor-chrome/FrameMenu.d.ts +2 -2
  30. package/dist/editor/page-editor-chrome/FrameMenu.js +21 -15
  31. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  32. package/dist/editor/page-editor-chrome/FrameMenus.d.ts +2 -2
  33. package/dist/editor/page-editor-chrome/FrameMenus.js +2 -2
  34. package/dist/editor/page-editor-chrome/FrameMenus.js.map +1 -1
  35. package/dist/editor/page-editor-chrome/InlineEditor.d.ts +2 -2
  36. package/dist/editor/page-editor-chrome/InlineEditor.js +175 -17
  37. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  38. package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +2 -2
  39. package/dist/editor/page-editor-chrome/PageEditorChrome.js +2 -2
  40. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  41. package/dist/editor/page-viewer/EditorForm.d.ts +2 -1
  42. package/dist/editor/page-viewer/EditorForm.js +9 -8
  43. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  44. package/dist/editor/page-viewer/MiniMap.d.ts +2 -2
  45. package/dist/editor/page-viewer/MiniMap.js +2 -2
  46. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  47. package/dist/editor/page-viewer/PageViewer.d.ts +2 -2
  48. package/dist/editor/page-viewer/PageViewer.js +3 -3
  49. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  50. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -2
  51. package/dist/editor/page-viewer/PageViewerFrame.js +12 -12
  52. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  53. package/dist/editor/reviews/Comments.d.ts +2 -0
  54. package/dist/editor/reviews/Comments.js +26 -9
  55. package/dist/editor/reviews/Comments.js.map +1 -1
  56. package/dist/editor/reviews/DiffView.d.ts +17 -0
  57. package/dist/editor/reviews/DiffView.js +57 -0
  58. package/dist/editor/reviews/DiffView.js.map +1 -0
  59. package/dist/editor/reviews/SuggestedEdit.d.ts +4 -0
  60. package/dist/editor/reviews/SuggestedEdit.js +180 -0
  61. package/dist/editor/reviews/SuggestedEdit.js.map +1 -0
  62. package/dist/editor/services/suggestedEditsService.d.ts +17 -0
  63. package/dist/editor/services/suggestedEditsService.js +26 -0
  64. package/dist/editor/services/suggestedEditsService.js.map +1 -0
  65. package/dist/editor/ui/PerfectTree.js +3 -3
  66. package/dist/editor/ui/PerfectTree.js.map +1 -1
  67. package/dist/editor/ui/SimpleIconButton.js +3 -1
  68. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  69. package/dist/editor/views/CompareView.js +4 -13
  70. package/dist/editor/views/CompareView.js.map +1 -1
  71. package/dist/editor/views/EditView.js +2 -2
  72. package/dist/editor/views/EditView.js.map +1 -1
  73. package/dist/editor/views/SingleEditView.d.ts +2 -2
  74. package/dist/editor/views/SingleEditView.js +2 -2
  75. package/dist/editor/views/SingleEditView.js.map +1 -1
  76. package/dist/lib/safelist.js +1 -1
  77. package/dist/lib/safelist.js.map +1 -1
  78. package/dist/page-wizard/steps/BuildPageStep.js +2 -2
  79. package/dist/page-wizard/steps/BuildPageStep.js.map +1 -1
  80. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +2 -2
  81. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
  82. package/dist/styles.css +36 -2
  83. package/dist/types.d.ts +18 -0
  84. package/package.json +4 -1
  85. package/src/config/config.tsx +2 -2
  86. package/src/editor/EditorWarnings.tsx +2 -2
  87. package/src/editor/FieldList.tsx +6 -6
  88. package/src/editor/FieldListFieldWithFallbacks.tsx +9 -9
  89. package/src/editor/Titlebar.tsx +4 -4
  90. package/src/editor/client/EditorClient.tsx +83 -51
  91. package/src/editor/client/editContext.ts +12 -3
  92. package/src/editor/client/operations.ts +146 -9
  93. package/src/editor/component-designer/ComponentDesigner.tsx +1 -1
  94. package/src/editor/menubar/LanguageSelector.tsx +6 -6
  95. package/src/editor/menubar/PageSelector.tsx +11 -11
  96. package/src/editor/menubar/PageViewerControls.tsx +49 -23
  97. package/src/editor/menubar/Separator.tsx +2 -2
  98. package/src/editor/menubar/VersionSelector.tsx +1 -1
  99. package/src/editor/page-editor-chrome/FrameMenu.tsx +18 -17
  100. package/src/editor/page-editor-chrome/FrameMenus.tsx +6 -6
  101. package/src/editor/page-editor-chrome/InlineEditor.tsx +233 -22
  102. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +11 -14
  103. package/src/editor/page-viewer/EditorForm.tsx +15 -9
  104. package/src/editor/page-viewer/MiniMap.tsx +4 -4
  105. package/src/editor/page-viewer/PageViewer.tsx +6 -6
  106. package/src/editor/page-viewer/PageViewerFrame.tsx +19 -13
  107. package/src/editor/reviews/Comments.tsx +56 -15
  108. package/src/editor/reviews/DiffView.tsx +109 -0
  109. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  110. package/src/editor/services/suggestedEditsService.ts +39 -0
  111. package/src/editor/ui/PerfectTree.tsx +5 -5
  112. package/src/editor/ui/SimpleIconButton.tsx +5 -3
  113. package/src/editor/views/CompareView.tsx +13 -24
  114. package/src/editor/views/EditView.tsx +2 -2
  115. package/src/editor/views/SingleEditView.tsx +3 -3
  116. package/src/lib/safelist.tsx +2 -0
  117. package/src/page-wizard/steps/BuildPageStep.tsx +18 -25
  118. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +16 -18
  119. 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 [previewMode, setPreviewMode] = useState(false);
273
- const [statusMessage, setStatusMessage] = useState<React.ReactNode>("");
278
+ const [mode, setMode] = useState<EditorMode>("edit");
274
279
 
275
- const mode = searchParams.get("mode");
280
+ const [statusMessage, setStatusMessage] = useState<React.ReactNode>("");
276
281
 
277
282
  useEffect(() => {
278
- if (mode === "preview") {
279
- setPreviewMode(true);
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 (previewMode) {
288
+ if (mode === "suggestions") {
289
+ setViewName("reviews");
285
290
  }
286
- }, [previewMode]);
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
- if (previewMode) {
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, previewMode]);
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
- if (previewMode) {
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
- previewMode,
1676
- setPreviewMode,
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
- previewMode,
1745
- setPreviewMode,
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
- mode={editContext.previewMode ? "view" : "edit"}
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
- [state.itemsRepository],
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
- field,
320
- state.user ?? { name: "unknown", ai: false },
321
- true,
322
- value,
323
- rawValue,
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
- editFieldImmediate,
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
+ }
@@ -63,7 +63,7 @@ export function ComponentDesigner() {
63
63
  <StackedPanels panels={mainPanels} />
64
64
  </SplitterPanel>
65
65
  <SplitterPanel>
66
- <PageViewerFrame mode="view" />
66
+ <PageViewerFrame compareView={false} />
67
67
  </SplitterPanel>
68
68
  </Splitter>
69
69
  );
@@ -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={`p-[7px] text-sm cursor-pointer flex items-center gap-3 ${
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
- } rounded-md ${disabled ? "opacity-50" : ""}`}
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 flex-col">
85
+ <div className="flex max-h-[50vh] min-w-64 flex-col">
86
86
  {showAllLanguagesSwitch && (
87
- <div className="text-xs p-2 flex items-center gap-2 justify-center">
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 flex gap-2 p-2"
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})