@alpaca-editor/core 1.0.3938 → 1.0.3941

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 (357) hide show
  1. package/dist/components/ActionButton.d.ts +1 -0
  2. package/dist/components/ActionButton.js +2 -2
  3. package/dist/components/ActionButton.js.map +1 -1
  4. package/dist/editor/ContentTree.js +12 -8
  5. package/dist/editor/ContentTree.js.map +1 -1
  6. package/dist/editor/ContextMenu.d.ts +1 -1
  7. package/dist/editor/ContextMenu.js +17 -3
  8. package/dist/editor/ContextMenu.js.map +1 -1
  9. package/dist/editor/FieldActionsOverlay.d.ts +18 -0
  10. package/dist/editor/FieldActionsOverlay.js +139 -0
  11. package/dist/editor/FieldActionsOverlay.js.map +1 -0
  12. package/dist/editor/FieldHistory.d.ts +2 -1
  13. package/dist/editor/FieldHistory.js +11 -8
  14. package/dist/editor/FieldHistory.js.map +1 -1
  15. package/dist/editor/FieldListField.js +14 -17
  16. package/dist/editor/FieldListField.js.map +1 -1
  17. package/dist/editor/PictureCropper.js +65 -23
  18. package/dist/editor/PictureCropper.js.map +1 -1
  19. package/dist/editor/PictureEditor.js +43 -3
  20. package/dist/editor/PictureEditor.js.map +1 -1
  21. package/dist/editor/Titlebar.js +19 -10
  22. package/dist/editor/Titlebar.js.map +1 -1
  23. package/dist/editor/ai/AiTerminal.js +27 -41
  24. package/dist/editor/ai/AiTerminal.js.map +1 -1
  25. package/dist/editor/ai/GhostWriter.js +21 -2
  26. package/dist/editor/ai/GhostWriter.js.map +1 -1
  27. package/dist/editor/client/EditorClient.js +48 -18
  28. package/dist/editor/client/EditorClient.js.map +1 -1
  29. package/dist/editor/client/editContext.d.ts +1 -1
  30. package/dist/editor/client/editContext.js.map +1 -1
  31. package/dist/editor/client/itemsRepository.js +126 -90
  32. package/dist/editor/client/itemsRepository.js.map +1 -1
  33. package/dist/editor/commands/componentCommands.js +7 -3
  34. package/dist/editor/commands/componentCommands.js.map +1 -1
  35. package/dist/editor/media-selector/MediaFolderBrowser.d.ts +5 -0
  36. package/dist/editor/media-selector/MediaFolderBrowser.js +77 -0
  37. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -0
  38. package/dist/editor/media-selector/MediaSelector.js +1 -1
  39. package/dist/editor/media-selector/MediaSelector.js.map +1 -1
  40. package/dist/editor/media-selector/Thumbnails.js +2 -2
  41. package/dist/editor/media-selector/index.d.ts +8 -0
  42. package/dist/editor/media-selector/index.js +9 -0
  43. package/dist/editor/media-selector/index.js.map +1 -0
  44. package/dist/editor/menubar/BrowseHistory.js +3 -4
  45. package/dist/editor/menubar/BrowseHistory.js.map +1 -1
  46. package/dist/editor/menubar/PageSelector.js +62 -10
  47. package/dist/editor/menubar/PageSelector.js.map +1 -1
  48. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +1 -1
  49. package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
  50. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +37 -11
  51. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  52. package/dist/editor/page-viewer/PageViewerFrame.js +98 -2
  53. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  54. package/dist/editor/pageModel.d.ts +14 -0
  55. package/dist/editor/reviews/Comment.js +3 -2
  56. package/dist/editor/reviews/Comment.js.map +1 -1
  57. package/dist/editor/services/aiService.js +0 -1
  58. package/dist/editor/services/aiService.js.map +1 -1
  59. package/dist/editor/services/editService.d.ts +1 -1
  60. package/dist/editor/services/editService.js +2 -1
  61. package/dist/editor/services/editService.js.map +1 -1
  62. package/dist/editor/sidebar/ComponentTree.js +3 -4
  63. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  64. package/dist/editor/ui/Icons.js +1 -1
  65. package/dist/editor/ui/Icons.js.map +1 -1
  66. package/dist/editor/ui/ItemList.d.ts +16 -0
  67. package/dist/editor/ui/ItemList.js +19 -0
  68. package/dist/editor/ui/ItemList.js.map +1 -0
  69. package/dist/editor/ui/ItemSearch.js +2 -12
  70. package/dist/editor/ui/ItemSearch.js.map +1 -1
  71. package/dist/editor/ui/SimpleTabs.d.ts +1 -0
  72. package/dist/editor/ui/SimpleTabs.js +3 -3
  73. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  74. package/dist/editor/ui/Splitter.js +61 -6
  75. package/dist/editor/ui/Splitter.js.map +1 -1
  76. package/dist/editor/views/MediaFolderEditView.d.ts +4 -0
  77. package/dist/editor/views/MediaFolderEditView.js +40 -0
  78. package/dist/editor/views/MediaFolderEditView.js.map +1 -0
  79. package/dist/editor/views/SingleEditView.js +9 -1
  80. package/dist/editor/views/SingleEditView.js.map +1 -1
  81. package/dist/revision.d.ts +2 -2
  82. package/dist/revision.js +2 -2
  83. package/dist/styles.css +64 -13
  84. package/package.json +8 -2
  85. package/.prettierrc +0 -3
  86. package/build.css +0 -3
  87. package/components.json +0 -21
  88. package/eslint.config.mjs +0 -4
  89. package/images/bg-shape-black.webp +0 -0
  90. package/images/wizard-bg.png +0 -0
  91. package/images/wizard-tour.png +0 -0
  92. package/images/wizard.png +0 -0
  93. package/src/client-components/api.ts +0 -6
  94. package/src/client-components/index.ts +0 -19
  95. package/src/components/ActionButton.tsx +0 -41
  96. package/src/components/Error.tsx +0 -57
  97. package/src/components/ui/CardConnector.tsx +0 -56
  98. package/src/components/ui/button.tsx +0 -62
  99. package/src/components/ui/card.tsx +0 -372
  100. package/src/components/ui/context-menu.tsx +0 -250
  101. package/src/config/config.tsx +0 -917
  102. package/src/config/types.ts +0 -286
  103. package/src/editor/ComponentInfo.tsx +0 -90
  104. package/src/editor/ConfirmationDialog.tsx +0 -103
  105. package/src/editor/ContentTree.tsx +0 -727
  106. package/src/editor/ContextMenu.tsx +0 -212
  107. package/src/editor/Editor.tsx +0 -90
  108. package/src/editor/EditorWarning.tsx +0 -34
  109. package/src/editor/EditorWarnings.tsx +0 -33
  110. package/src/editor/FieldEditorPopup.tsx +0 -65
  111. package/src/editor/FieldHistory.tsx +0 -74
  112. package/src/editor/FieldList.tsx +0 -190
  113. package/src/editor/FieldListField.tsx +0 -391
  114. package/src/editor/FieldListFieldWithFallbacks.tsx +0 -217
  115. package/src/editor/FloatingToolbar.tsx +0 -163
  116. package/src/editor/ImageEditor.tsx +0 -128
  117. package/src/editor/ItemInfo.tsx +0 -90
  118. package/src/editor/LinkEditorDialog.tsx +0 -196
  119. package/src/editor/MainLayout.tsx +0 -95
  120. package/src/editor/MobileLayout.tsx +0 -68
  121. package/src/editor/NewEditorClient.tsx +0 -11
  122. package/src/editor/PictureCropper.tsx +0 -503
  123. package/src/editor/PictureEditor.tsx +0 -212
  124. package/src/editor/PictureEditorDialog.tsx +0 -381
  125. package/src/editor/PublishDialog.ignore +0 -74
  126. package/src/editor/ScrollingContentTree.tsx +0 -67
  127. package/src/editor/Terminal.tsx +0 -227
  128. package/src/editor/Titlebar.tsx +0 -93
  129. package/src/editor/ai/AiPopup.tsx +0 -59
  130. package/src/editor/ai/AiResponseMessage.tsx +0 -106
  131. package/src/editor/ai/AiTerminal.tsx +0 -514
  132. package/src/editor/ai/AiToolCall.tsx +0 -61
  133. package/src/editor/ai/EditorAiTerminal.tsx +0 -20
  134. package/src/editor/ai/GhostWriter.tsx +0 -432
  135. package/src/editor/ai/aiPageModel.ts +0 -108
  136. package/src/editor/ai/editorAiContext.ts +0 -18
  137. package/src/editor/client/AboutDialog.tsx +0 -44
  138. package/src/editor/client/EditorClient.tsx +0 -2197
  139. package/src/editor/client/GenericDialog.tsx +0 -50
  140. package/src/editor/client/editContext.ts +0 -412
  141. package/src/editor/client/helpers.ts +0 -44
  142. package/src/editor/client/itemsRepository.ts +0 -538
  143. package/src/editor/client/operations.ts +0 -768
  144. package/src/editor/client/pageModelBuilder.ts +0 -219
  145. package/src/editor/commands/commands.ts +0 -22
  146. package/src/editor/commands/componentCommands.tsx +0 -424
  147. package/src/editor/commands/createVersionCommand.ts +0 -33
  148. package/src/editor/commands/deleteVersionCommand.ts +0 -71
  149. package/src/editor/commands/itemCommands.tsx +0 -351
  150. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +0 -201
  151. package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +0 -27
  152. package/src/editor/commands/undo.ts +0 -39
  153. package/src/editor/component-designer/ComponentDesigner.tsx +0 -70
  154. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +0 -11
  155. package/src/editor/component-designer/ComponentDesignerMenu.tsx +0 -91
  156. package/src/editor/component-designer/ComponentEditor.tsx +0 -97
  157. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +0 -31
  158. package/src/editor/component-designer/ComponentRenderingEditor.tsx +0 -104
  159. package/src/editor/component-designer/ComponentsDropdown.tsx +0 -39
  160. package/src/editor/component-designer/PlaceholdersEditor.tsx +0 -179
  161. package/src/editor/component-designer/RenderingsDropdown.tsx +0 -36
  162. package/src/editor/component-designer/TemplateEditor.tsx +0 -236
  163. package/src/editor/component-designer/aiContext.ts +0 -23
  164. package/src/editor/componentTreeHelper.tsx +0 -116
  165. package/src/editor/context-menu/CopyMoveMenu.tsx +0 -103
  166. package/src/editor/context-menu/InsertMenu.tsx +0 -347
  167. package/src/editor/control-center/About.tsx +0 -342
  168. package/src/editor/control-center/ControlCenterMenu.tsx +0 -76
  169. package/src/editor/control-center/IndexOverview.tsx +0 -50
  170. package/src/editor/control-center/IndexSettings.tsx +0 -266
  171. package/src/editor/control-center/Info.tsx +0 -104
  172. package/src/editor/control-center/QuotaInfo.tsx +0 -301
  173. package/src/editor/control-center/Status.tsx +0 -113
  174. package/src/editor/control-center/WebSocketMessages.tsx +0 -155
  175. package/src/editor/editor-warnings/ItemLocked.tsx +0 -63
  176. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +0 -22
  177. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +0 -23
  178. package/src/editor/editor-warnings/NoWriteAccess.tsx +0 -16
  179. package/src/editor/editor-warnings/ValidationErrors.tsx +0 -54
  180. package/src/editor/field-types/AttachmentEditor.tsx +0 -9
  181. package/src/editor/field-types/CheckboxEditor.tsx +0 -47
  182. package/src/editor/field-types/DropLinkEditor.tsx +0 -80
  183. package/src/editor/field-types/DropListEditor.tsx +0 -84
  184. package/src/editor/field-types/ImageFieldEditor.tsx +0 -65
  185. package/src/editor/field-types/InternalLinkFieldEditor.tsx +0 -117
  186. package/src/editor/field-types/LinkFieldEditor.tsx +0 -85
  187. package/src/editor/field-types/MultiLineText.tsx +0 -82
  188. package/src/editor/field-types/PictureFieldEditor.tsx +0 -121
  189. package/src/editor/field-types/RawEditor.tsx +0 -53
  190. package/src/editor/field-types/ReactQuill.tsx +0 -580
  191. package/src/editor/field-types/RichTextEditor.tsx +0 -22
  192. package/src/editor/field-types/RichTextEditorComponent.tsx +0 -127
  193. package/src/editor/field-types/SingleLineText.tsx +0 -174
  194. package/src/editor/field-types/TreeListEditor.tsx +0 -261
  195. package/src/editor/fieldTypes.ts +0 -140
  196. package/src/editor/media-selector/AiImageSearch.tsx +0 -185
  197. package/src/editor/media-selector/AiImageSearchPrompt.tsx +0 -94
  198. package/src/editor/media-selector/MediaSelector.tsx +0 -42
  199. package/src/editor/media-selector/Preview.tsx +0 -14
  200. package/src/editor/media-selector/Thumbnails.tsx +0 -48
  201. package/src/editor/media-selector/TreeSelector.tsx +0 -292
  202. package/src/editor/media-selector/UploadZone.tsx +0 -137
  203. package/src/editor/menubar/ActionsMenu.tsx +0 -94
  204. package/src/editor/menubar/ActiveUsers.tsx +0 -17
  205. package/src/editor/menubar/ApproveAndPublish.tsx +0 -18
  206. package/src/editor/menubar/BrowseHistory.tsx +0 -37
  207. package/src/editor/menubar/ItemLanguageVersion.tsx +0 -76
  208. package/src/editor/menubar/LanguageSelector.tsx +0 -226
  209. package/src/editor/menubar/Menu.tsx +0 -83
  210. package/src/editor/menubar/NavButtons.tsx +0 -74
  211. package/src/editor/menubar/PageSelector.tsx +0 -141
  212. package/src/editor/menubar/PageViewerControls.tsx +0 -120
  213. package/src/editor/menubar/PreviewSecondaryControls.tsx +0 -18
  214. package/src/editor/menubar/SecondaryControls.tsx +0 -45
  215. package/src/editor/menubar/Separator.tsx +0 -12
  216. package/src/editor/menubar/SiteInfo.tsx +0 -53
  217. package/src/editor/menubar/User.tsx +0 -27
  218. package/src/editor/menubar/VersionSelector.tsx +0 -142
  219. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +0 -307
  220. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +0 -35
  221. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +0 -59
  222. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +0 -23
  223. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +0 -64
  224. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +0 -35
  225. package/src/editor/page-editor-chrome/FrameMenu.tsx +0 -338
  226. package/src/editor/page-editor-chrome/FrameMenus.tsx +0 -48
  227. package/src/editor/page-editor-chrome/InlineEditor.tsx +0 -765
  228. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +0 -61
  229. package/src/editor/page-editor-chrome/NoLayout.tsx +0 -36
  230. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +0 -122
  231. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +0 -161
  232. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +0 -169
  233. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +0 -315
  234. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +0 -300
  235. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +0 -40
  236. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +0 -791
  237. package/src/editor/page-viewer/DeviceToolbar.tsx +0 -70
  238. package/src/editor/page-viewer/EditorForm.tsx +0 -258
  239. package/src/editor/page-viewer/MiniMap.tsx +0 -362
  240. package/src/editor/page-viewer/PageViewer.tsx +0 -169
  241. package/src/editor/page-viewer/PageViewerFrame.tsx +0 -879
  242. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +0 -412
  243. package/src/editor/page-viewer/pageViewContext.ts +0 -186
  244. package/src/editor/pageModel.ts +0 -208
  245. package/src/editor/picture-shared.tsx +0 -53
  246. package/src/editor/reviews/Comment.tsx +0 -308
  247. package/src/editor/reviews/Comments.tsx +0 -125
  248. package/src/editor/reviews/DiffView.tsx +0 -109
  249. package/src/editor/reviews/PreviewInfo.tsx +0 -35
  250. package/src/editor/reviews/Reviews.tsx +0 -280
  251. package/src/editor/reviews/SuggestedEdit.tsx +0 -316
  252. package/src/editor/reviews/reviewCommands.tsx +0 -47
  253. package/src/editor/reviews/useReviews.tsx +0 -70
  254. package/src/editor/services/aiService.ts +0 -174
  255. package/src/editor/services/componentDesignerService.ts +0 -151
  256. package/src/editor/services/contentService.ts +0 -180
  257. package/src/editor/services/editService.ts +0 -486
  258. package/src/editor/services/indexService.ts +0 -24
  259. package/src/editor/services/reviewsService.ts +0 -53
  260. package/src/editor/services/serviceHelper.ts +0 -95
  261. package/src/editor/services/suggestedEditsService.ts +0 -39
  262. package/src/editor/services/systemService.ts +0 -5
  263. package/src/editor/services/translationService.ts +0 -21
  264. package/src/editor/services-server/api.ts +0 -150
  265. package/src/editor/services-server/graphQL.ts +0 -106
  266. package/src/editor/sidebar/ComponentPalette.tsx +0 -161
  267. package/src/editor/sidebar/ComponentTree.tsx +0 -548
  268. package/src/editor/sidebar/ComponentTree2.tsxx +0 -490
  269. package/src/editor/sidebar/Debug.tsx +0 -111
  270. package/src/editor/sidebar/DictionaryEditor.tsx +0 -261
  271. package/src/editor/sidebar/EditHistory.tsx +0 -134
  272. package/src/editor/sidebar/GraphQL.tsx +0 -164
  273. package/src/editor/sidebar/Insert.tsx +0 -35
  274. package/src/editor/sidebar/MainContentTree.tsx +0 -102
  275. package/src/editor/sidebar/Performance.tsx +0 -53
  276. package/src/editor/sidebar/Sessions.tsx +0 -35
  277. package/src/editor/sidebar/Sidebar.tsx +0 -20
  278. package/src/editor/sidebar/SidebarView.tsx +0 -152
  279. package/src/editor/sidebar/Translations.tsx +0 -295
  280. package/src/editor/sidebar/Validation.tsx +0 -102
  281. package/src/editor/sidebar/ViewSelector.tsx +0 -60
  282. package/src/editor/sidebar/Workbox.tsx +0 -209
  283. package/src/editor/ui/CenteredMessage.tsx +0 -7
  284. package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +0 -81
  285. package/src/editor/ui/CopyToClipboardButton.tsx +0 -24
  286. package/src/editor/ui/DialogButtons.tsx +0 -11
  287. package/src/editor/ui/Icons.tsx +0 -708
  288. package/src/editor/ui/ItemNameDialogNew.tsx +0 -118
  289. package/src/editor/ui/ItemSearch.tsx +0 -190
  290. package/src/editor/ui/PerfectTree.tsx +0 -571
  291. package/src/editor/ui/Section.tsx +0 -35
  292. package/src/editor/ui/SimpleIconButton.tsx +0 -54
  293. package/src/editor/ui/SimpleMenu.tsx +0 -40
  294. package/src/editor/ui/SimpleTable.tsx +0 -60
  295. package/src/editor/ui/SimpleTabs.tsx +0 -55
  296. package/src/editor/ui/SimpleToolbar.tsx +0 -7
  297. package/src/editor/ui/Spinner.tsx +0 -9
  298. package/src/editor/ui/Splitter.tsx +0 -314
  299. package/src/editor/ui/StackedPanels.tsx +0 -134
  300. package/src/editor/ui/Toolbar.tsx +0 -7
  301. package/src/editor/utils/id-helper.ts +0 -3
  302. package/src/editor/utils/insertOptions.ts +0 -69
  303. package/src/editor/utils/itemutils.ts +0 -29
  304. package/src/editor/utils/useMemoDebug.ts +0 -28
  305. package/src/editor/utils.ts +0 -486
  306. package/src/editor/views/CompareView.tsx +0 -245
  307. package/src/editor/views/EditView.tsx +0 -27
  308. package/src/editor/views/ItemEditor.tsx +0 -58
  309. package/src/editor/views/SingleEditView.tsx +0 -46
  310. package/src/fonts/Geist-Black.woff2 +0 -0
  311. package/src/fonts/Geist-Bold.woff2 +0 -0
  312. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  313. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  314. package/src/fonts/Geist-Light.woff2 +0 -0
  315. package/src/fonts/Geist-Medium.woff2 +0 -0
  316. package/src/fonts/Geist-Regular.woff2 +0 -0
  317. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  318. package/src/fonts/Geist-Thin.woff2 +0 -0
  319. package/src/fonts/Geist[wght].woff2 +0 -0
  320. package/src/fonts/index.ts +0 -10
  321. package/src/index.ts +0 -23
  322. package/src/lib/safelist.tsx +0 -16
  323. package/src/lib/utils.ts +0 -6
  324. package/src/page-wizard/PageWizard.tsx +0 -139
  325. package/src/page-wizard/WizardBox.tsx +0 -4
  326. package/src/page-wizard/WizardBoxConnector.tsx +0 -56
  327. package/src/page-wizard/WizardSteps.tsx +0 -458
  328. package/src/page-wizard/service.ts +0 -35
  329. package/src/page-wizard/startPageWizardCommand.ts +0 -26
  330. package/src/page-wizard/steps/BuildPageStep.tsx +0 -259
  331. package/src/page-wizard/steps/CollectStep.tsx +0 -296
  332. package/src/page-wizard/steps/ComponentTypesSelector.tsx +0 -454
  333. package/src/page-wizard/steps/Components.tsx +0 -193
  334. package/src/page-wizard/steps/ContentStep.tsx +0 -890
  335. package/src/page-wizard/steps/EditButton.tsx +0 -34
  336. package/src/page-wizard/steps/FieldEditor.tsx +0 -102
  337. package/src/page-wizard/steps/Generate.tsx +0 -60
  338. package/src/page-wizard/steps/ImagesStep.tsx +0 -382
  339. package/src/page-wizard/steps/LayoutStep.tsx +0 -227
  340. package/src/page-wizard/steps/MetaDataStep.tsx +0 -173
  341. package/src/page-wizard/steps/SelectStep.tsx +0 -281
  342. package/src/page-wizard/steps/schema.ts +0 -180
  343. package/src/page-wizard/steps/usePageCreator.ts +0 -325
  344. package/src/page-wizard/usePageWizard.ts +0 -79
  345. package/src/revision.ts +0 -2
  346. package/src/splash-screen/NewPage.tsx +0 -294
  347. package/src/splash-screen/OpenPage.tsx +0 -113
  348. package/src/splash-screen/RecentPages.tsx +0 -123
  349. package/src/splash-screen/SectionHeadline.tsx +0 -21
  350. package/src/splash-screen/SplashScreen.tsx +0 -195
  351. package/src/tour/Tour.tsx +0 -566
  352. package/src/tour/default-tour.tsx +0 -301
  353. package/src/tour/preview-tour.tsx +0 -128
  354. package/src/types.ts +0 -335
  355. package/styles.css +0 -765
  356. package/tsconfig.build.json +0 -31
  357. package/tsconfig.json +0 -14
@@ -1,2197 +0,0 @@
1
- "use client";
2
-
3
- import React, {
4
- useState,
5
- useEffect,
6
- useRef,
7
- useCallback,
8
- MouseEvent,
9
- useMemo,
10
- ReactNode,
11
- } from "react";
12
-
13
- import { Toast, ToastMessage } from "primereact/toast";
14
-
15
- import {
16
- EditContextProvider,
17
- DragObject,
18
- ModifiedFieldsContextProvider,
19
- OperationsContextProvider,
20
- ModifiedField,
21
- EditContextType,
22
- EditedField,
23
- SelectionRange,
24
- EditorMode,
25
- } from "./editContext";
26
-
27
- import type { OpenDialog } from "./editContext";
28
-
29
- import { EditorConfiguration, MenuItem } from "../../config/types";
30
- import { useRouter, useSearchParams, usePathname } from "next/navigation";
31
- import { findComponent, getComponentById } from "../componentTreeHelper";
32
-
33
- import { getOperationsContext } from "./operations";
34
- import { handleErrorResult } from "./helpers";
35
-
36
- import {
37
- executeFieldAction as executeFieldServerAction,
38
- connectSocket,
39
- getEditHistory,
40
- releaseFieldLocks,
41
- validateItems,
42
- } from "../services/editService";
43
-
44
- import "primeicons/primeicons.css";
45
- import "primereact/resources/themes/md-light-indigo/theme.css";
46
- import "react-json-view-lite/dist/index.css";
47
- import {
48
- MediaSelector,
49
- MediaSelectorMode,
50
- } from "../media-selector/MediaSelector";
51
- import { getComponentCommands } from "../commands/componentCommands";
52
-
53
- import {
54
- getLanguagesAndVersions,
55
- getWorkbox,
56
- } from "../services/contentService";
57
- import ConfirmationDialog, {
58
- ConfirmationDialogHandle,
59
- ConfirmationProps,
60
- } from "../ConfirmationDialog";
61
-
62
- import MainLayout from "../MainLayout";
63
- import { getItemDescriptor, useEventListenerExt } from "../utils";
64
-
65
- import { EditContextMenu, EditContextMenuRef } from "../ContextMenu";
66
-
67
- import { FieldEditorPopup, FieldEditorPopupRef } from "../FieldEditorPopup";
68
-
69
- import { Command, CommandData } from "../commands/commands";
70
- import { AiPopup, AiPopupRef } from "../ai/AiPopup";
71
-
72
- import { ComponentDetails } from "../services/componentDesignerService";
73
- import {
74
- EditFieldOperation,
75
- EditOperation,
76
- EditSession,
77
- FieldDescriptor,
78
- HistoryEntry,
79
- InsertOption,
80
- LanguageVersions,
81
- LinkComponentOperation,
82
- MoveComponentOperation,
83
- ValidationResult,
84
- WorkboxItem,
85
- Comment,
86
- SuggestedEdit,
87
- UserInfo,
88
- } from "../../types";
89
-
90
- import { post } from "../services/serviceHelper";
91
- import { SidebarView } from "../sidebar/SidebarView";
92
- import { PageViewerFrame } from "../page-viewer/PageViewerFrame";
93
- import { ClientFieldButton } from "../../config/types";
94
-
95
- import {
96
- Component,
97
- FieldButton,
98
- ItemDescriptor,
99
- FullItem,
100
- Version,
101
- Timings,
102
- Field,
103
- } from "../pageModel";
104
-
105
- import { useItemsRepository } from "./itemsRepository";
106
-
107
- import { Spinner } from "../ui/Spinner";
108
- import { cleanId } from "../utils/id-helper";
109
-
110
- import { useDebouncedCallback } from "use-debounce";
111
-
112
- import { Tour } from "../../tour/Tour";
113
- import { usePageViewContext } from "../page-viewer/pageViewContext";
114
-
115
- import { getComments } from "../services/reviewsService";
116
- import { AiTerminalOptions } from "../ai/AiTerminal";
117
- import { useReviews } from "../reviews/useReviews";
118
- import uuid from "react-uuid";
119
- import { flushSync } from "react-dom";
120
- import { getSuggestedEdits } from "../services/suggestedEditsService";
121
- import { usePageWizard } from "../../page-wizard/usePageWizard";
122
- import { requestQuota } from "../services/aiService";
123
-
124
- export type FieldAction = {
125
- field: FieldDescriptor;
126
- message?: string;
127
- state: "running" | "success" | "error";
128
- label?: string;
129
- };
130
-
131
- export type WindowSize = { width: number; height: number };
132
- export type InsertingState = {
133
- positionElement: Element;
134
- positionAnchor: "left" | "right" | "top" | "bottom";
135
- };
136
- export type QuotaUsage = {
137
- totalTokens: number;
138
- totalImages: number;
139
- dailyTokens: number;
140
- dailyImages: number;
141
- };
142
- export type QuotaLimits = {
143
- totalTokens: number;
144
- dailyTokens: number;
145
- monthlyTokens: number;
146
- totalImages: number;
147
- dailyImages: number;
148
- monthlyImages: number;
149
- };
150
- export type QuotaInfo = { usage: QuotaUsage; limits: QuotaLimits };
151
-
152
- export type WebSocketMessage = {
153
- id: string;
154
- timestamp: string;
155
- type: string;
156
- payload: any;
157
- rawMessage: string;
158
- };
159
-
160
- export function EditorClient({
161
- configuration,
162
- className,
163
- item: loadItemDescriptor,
164
- sessionId,
165
- userInfo,
166
- }: {
167
- configuration: EditorConfiguration;
168
- className?: string;
169
- item?: ItemDescriptor;
170
- sessionId: string;
171
- userInfo: UserInfo;
172
- }) {
173
- const router = useRouter();
174
-
175
- const pathname = usePathname();
176
- const searchParams = useSearchParams();
177
- const [selection, setSelection] = useState<string[]>([]);
178
- const [selectedForInsertion, setSelectedForInsertion] = useState<string>("");
179
-
180
- const [refreshCompletedFlag, setRefreshCompletedFlag] = useState(false);
181
-
182
- const [isRefreshing, setIsRefreshing] = useState(false);
183
- const [dragObject, setDragObject] = useState<DragObject>();
184
- const [mediaResolver, setMediaResolver] = useState<(value: string) => void>();
185
- const [mediaSelectorVisible, setMediaSelectorVisible] = useState(false);
186
- const [mediaSelectorMode, setMediaSelectorMode] = useState<
187
- "images" | "video"
188
- >("images");
189
- const [selectedMediaIdPath, setSelectedMediaIdPath] = useState<string>("");
190
- const [scrollIntoView, setScrollIntoView] = useState<string>();
191
-
192
- const confirmationDialogRef = useRef<ConfirmationDialogHandle>(null);
193
- const contextMenuRef = useRef<EditContextMenuRef>(null);
194
- const editContextRef = useRef<EditContextType>(undefined);
195
-
196
- const [currentOverlay, setCurrentOverlay] = useState<string>();
197
- const [contentEditorItem, setContentEditorItem] = useState<FullItem>();
198
-
199
- const [focusedField, setFocusedField] = useState<FieldDescriptor>();
200
- const [selectedRange, setSelectedRange] = useState<SelectionRange>();
201
-
202
- const [validating, setValidating] = useState(false);
203
-
204
- const [inserting, setInserting] = useState<InsertingState>();
205
-
206
- const [showFullscreenHint, setShowFullscreenHint] = useState(false);
207
- // const [showPublishDialog, setShowPublishDialog] = useState(false);
208
- const [activeFieldActions, setActiveFieldActions] = useState<FieldAction[]>(
209
- [],
210
- );
211
- const [renderedFields, setRenderedFields] = useState<FieldDescriptor[]>([]);
212
-
213
- const aiPopupRef = React.useRef<AiPopupRef>(null);
214
- const fieldEditorPopupRef = React.useRef<FieldEditorPopupRef>(null);
215
-
216
- const [validationResult, setValidationResult] = useState<
217
- ValidationResult[] | undefined
218
- >();
219
-
220
- const [editHistory, setEditHistory] = useState<EditOperation[]>([]);
221
-
222
- const [lastEditedFields, setLastEditedFields] = useState<EditedField[]>([]);
223
-
224
- const [activeSessions, setActiveSessions] = useState<EditSession[]>([]);
225
-
226
- if (typeof window !== "undefined")
227
- sessionStorage?.setItem("sessionId", sessionId);
228
-
229
- const [viewName, setViewName] = useState<string>(
230
- // default from query string
231
- searchParams.get("view") ??
232
- configuration.editor.views[0]?.name ??
233
- "splash-screen",
234
- );
235
-
236
- const [compareMode, setCompareMode] = useState(false);
237
- const [compareTo, setCompareTo] = useState<ItemDescriptor>();
238
-
239
- const [componentDesignerComponent, setComponentDesignerComponent] =
240
- useState<ComponentDetails>();
241
- const [componentDesignerRendering, setComponentDesignerRendering] =
242
- useState<FullItem>();
243
-
244
- const [insertMode, setInsertMode] = useState(false);
245
-
246
- const [ignoreBlur, setIgnoreBlur] = useState(false);
247
-
248
- const [currentItemDescriptor, setCurrentItemDescriptor] =
249
- useState<ItemDescriptor>();
250
- const currentItemDescriptorRef = useRef<ItemDescriptor>(undefined);
251
- useEffect(() => {
252
- currentItemDescriptorRef.current = currentItemDescriptor;
253
- }, [currentItemDescriptor]);
254
-
255
- const currentItemRef = useRef<FullItem>(undefined);
256
- useEffect(() => {
257
- currentItemRef.current = contentEditorItem;
258
- }, [contentEditorItem]);
259
-
260
- const [inlineEditingFieldElement, setInlineEditingFieldElement] =
261
- useState<HTMLElement>();
262
-
263
- const [lockedField, setLockedField] = useState<FieldDescriptor>();
264
-
265
- const [itemLanguages, setItemLanguages] = useState<LanguageVersions[]>([]);
266
- const [itemVersions, setItemVersions] = useState<Version[]>([]);
267
- const [modifiedFields, setModifiedFields] = useState<ModifiedField[]>([]);
268
- const [comments, setComments] = useState<Comment[]>([]);
269
- const [suggestedEdits, setSuggestedEdits] = useState<SuggestedEdit[]>([]);
270
- const [showSuggestedEdits, setShowSuggestedEdits] = useState(false);
271
- const [showSuggestedEditsDiff, setShowSuggestedEditsDiff] = useState(false);
272
- const [showComments, setShowComments] = useState<boolean>(() => {
273
- const savedShowComments =
274
- typeof window !== "undefined"
275
- ? localStorage.getItem("editor.showComments")
276
- : null;
277
- return savedShowComments ? JSON.parse(savedShowComments) : true;
278
- });
279
-
280
- useEffect(() => {
281
- if (typeof window !== "undefined") {
282
- localStorage.setItem("editor.showComments", JSON.stringify(showComments));
283
- }
284
- }, [showComments]);
285
-
286
- const [selectedComment, setSelectedComment] = useState<Comment>();
287
-
288
- const [browseHistory, setBrowseHistory] = useState<HistoryEntry[]>(() => {
289
- const savedHistory =
290
- typeof window !== "undefined"
291
- ? localStorage.getItem("editor.browseHistory")
292
- : null;
293
- return savedHistory ? JSON.parse(savedHistory) : [];
294
- });
295
-
296
- const [centerPanelView, setCenterPanelView] = useState<ReactNode>();
297
- const [timings, setTimings] = useState<Timings>({});
298
-
299
- const [revision, setRevision] = useState<string>();
300
-
301
- const [workboxItems, setWorkboxItems] = useState<WorkboxItem[]>([]);
302
- const [isTourActive, setIsTourActive] = useState(false);
303
-
304
- const [mode, setMode] = useState<EditorMode>("edit");
305
-
306
- const [statusMessage, setStatusMessage] = useState<React.ReactNode>("");
307
- const [focusFieldComponentId, setFocusFieldComponentId] = useState<string>();
308
-
309
- const [enableCompletions, setEnableCompletions] = useState(false);
310
- const [quotaInfo, setQuotaInfo] = useState<QuotaInfo | null>(null);
311
- const pageWizard = usePageWizard();
312
-
313
- const [webSocketMessages, setWebSocketMessages] = useState<
314
- WebSocketMessage[]
315
- >([]);
316
-
317
- useEffect(() => {
318
- const queryMode = searchParams.get("mode");
319
- if (queryMode) setMode(queryMode as EditorMode);
320
- }, [searchParams]);
321
-
322
- useEffect(() => {
323
- if (mode === "suggestions") {
324
- setViewName("reviews");
325
- }
326
- }, [mode]);
327
-
328
- useEffect(() => {
329
- if (
330
- focusedField &&
331
- selection.length > 0 &&
332
- selection[0] !== focusedField.item.id
333
- ) {
334
- setFocusedField(undefined);
335
- }
336
- }, [selection]);
337
-
338
- const itemsRepository = useItemsRepository(
339
- setModifiedFields,
340
- setLastEditedFields,
341
- );
342
-
343
- const pageViewContext = usePageViewContext({
344
- pageItemDescriptor: currentItemDescriptor,
345
- itemsRepository,
346
- configuration,
347
- });
348
-
349
- const socketMessageListeners = useRef<Set<(data: any) => void>>(new Set());
350
-
351
- const addSocketMessageListener = (
352
- callback: (message: { type: string; payload: any }) => void,
353
- ) => {
354
- socketMessageListeners.current.add(callback);
355
- return () => socketMessageListeners.current.delete(callback);
356
- };
357
-
358
- const reviews = useReviews({
359
- currentItemDescriptor: contentEditorItem?.descriptor,
360
- addSocketMessageListener: addSocketMessageListener,
361
- });
362
-
363
- const validate = useDebouncedCallback(async (items: ItemDescriptor[]) => {
364
- setValidating(true);
365
- const result = await validateItems(items, sessionId);
366
- if (result.type === "success") setValidationResult(await result.data);
367
- setValidating(false);
368
- }, 1000);
369
-
370
- useEffect(() => {
371
- setSelectedForInsertion("");
372
- }, [selection]);
373
-
374
- useEffect(() => {
375
- if (focusedField?.fieldId !== selectedRange?.fieldId) {
376
- setSelectedRange(undefined);
377
- }
378
- }, [focusedField]);
379
-
380
- const currentView =
381
- configuration.editor.views.find((x) => x.name === viewName) ??
382
- configuration.editor.views[0];
383
-
384
- useEffect(() => {
385
- if (currentView?.defaultCenterPanelView)
386
- setCenterPanelView(currentView.defaultCenterPanelView);
387
- }, [currentView]);
388
-
389
- const sendClientInfo = async () => {
390
- const socket = (globalThis as any).editorSocket;
391
-
392
- const clientInfoMessage = {
393
- type: "client-info",
394
- sessionId: sessionId,
395
- url: window.location.href,
396
- userAgent: navigator.userAgent,
397
- item: currentItemRef.current
398
- ? {
399
- ...currentItemRef.current.descriptor,
400
- name: currentItemRef.current.name,
401
- }
402
- : null,
403
- };
404
-
405
- if (socket.readyState !== WebSocket.OPEN) {
406
- const url = "/alpaca/editor/client";
407
- await post(url, clientInfoMessage);
408
- } else {
409
- socket.send(JSON.stringify(clientInfoMessage));
410
- }
411
- };
412
-
413
- const startTour = () => {
414
- setIsTourActive(true);
415
- };
416
-
417
- const messageHandler = useCallback(
418
- async (event: any) => {
419
- if (!event.data.startsWith("{")) return;
420
- const message = JSON.parse(event.data);
421
-
422
- // Track all WebSocket messages for debugging/monitoring
423
- try {
424
- const webSocketMessage: WebSocketMessage = {
425
- id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
426
- timestamp: new Date().toISOString(),
427
- type: message.type || "unknown",
428
- payload: message.payload,
429
- rawMessage: JSON.stringify(message, null, 2),
430
- };
431
-
432
- setWebSocketMessages((prev) => {
433
- const updated = [webSocketMessage, ...prev];
434
- // Keep only the latest 1000 messages
435
- return updated.slice(0, 1000);
436
- });
437
- } catch (error) {
438
- console.error("Error tracking WebSocket message:", error);
439
- }
440
-
441
- if (message.type === "active-sessions") {
442
- setActiveSessions(() => {
443
- return message.payload;
444
- });
445
- }
446
-
447
- if (message.type === "item-deleted") {
448
- itemsRepository.onItemsDeleted([
449
- { item: message.payload.item, parentId: message.payload.parentId },
450
- ]);
451
- if (message.payload.item.id === currentItemDescriptor?.id) {
452
- console.log("Load", message.payload.parentId);
453
- loadItem({
454
- id: message.payload.parentId,
455
- language: currentItemDescriptor?.language ?? "en",
456
- version: 0,
457
- });
458
- }
459
- }
460
-
461
- if (message.type === "item-changed") {
462
- await itemsRepository.refreshItems([message.payload.item]);
463
- if (message.payload.item.id === currentItemDescriptor?.id)
464
- loadItemVersions();
465
- }
466
-
467
- if (message.type === "item-version-added") {
468
- if (currentItemDescriptorRef.current) {
469
- if (currentItemDescriptorRef.current.id === message.payload.item.id)
470
- await loadItemVersions();
471
- setCurrentItemDescriptor({ ...currentItemDescriptorRef.current });
472
- }
473
- }
474
-
475
- if (message.type === "comment-updated") {
476
- setComments((x) => {
477
- const newComments = [...x];
478
- const index = newComments.findIndex(
479
- (c) => c.id === message.payload.comment.id,
480
- );
481
- if (index !== -1) newComments[index] = message.payload.comment;
482
- else newComments.push(message.payload.comment);
483
- return newComments;
484
- });
485
- }
486
-
487
- if (message.type === "comment-deleted") {
488
- setComments((x) => {
489
- return x.filter((c) => c.id !== message.payload.commentId);
490
- });
491
- }
492
-
493
- if (message.type === "suggested-edit-updated") {
494
- setSuggestedEdits((x) => {
495
- const index = x.findIndex(
496
- (s) => s.id === message.payload.suggestedEdit.id,
497
- );
498
- if (index !== -1) x[index] = message.payload.suggestedEdit;
499
- else x.push(message.payload.suggestedEdit);
500
- return x;
501
- });
502
- }
503
-
504
- if (message.type === "suggested-edit-deleted") {
505
- setSuggestedEdits((x) => {
506
- return x.filter((s) => s.id !== message.payload.id);
507
- });
508
- }
509
-
510
- if (message.type === "executing-field-action") {
511
- setActiveFieldActions((x) => {
512
- const payload = message.payload;
513
- const fieldId = payload.fieldId;
514
- const item = payload.item;
515
- const status = payload.status;
516
- const msg = payload.message;
517
- const label = payload.label;
518
-
519
- // Map backend status to FieldAction state
520
- let state: "running" | "success" | "error";
521
- switch (status?.toLowerCase()) {
522
- case "completed":
523
- case "success":
524
- state = "success";
525
- break;
526
- case "failed":
527
- case "error":
528
- state = "error";
529
- break;
530
- default:
531
- state = "running";
532
- break;
533
- }
534
-
535
- // Check if action already exists
536
- const existingActionIndex = x.findIndex(
537
- (action) =>
538
- action.field.fieldId === fieldId &&
539
- action.field.item.id === item.id &&
540
- action.field.item.language === item.language &&
541
- action.field.item.version === item.version,
542
- );
543
-
544
- if (existingActionIndex !== -1) {
545
- // Update existing action
546
- const newActions = [...x];
547
- newActions[existingActionIndex]!.state = state;
548
- newActions[existingActionIndex]!.message = msg;
549
- return newActions;
550
- } else {
551
- // Insert new action
552
- const fieldDescriptor: FieldDescriptor = {
553
- fieldId: fieldId,
554
- item: item,
555
- };
556
-
557
- const newAction: FieldAction = {
558
- field: fieldDescriptor,
559
- state,
560
- message: msg,
561
- label: label,
562
- };
563
- // console.log(newAction);
564
- return [...x, newAction];
565
- }
566
- });
567
- }
568
-
569
- if (message.type === "update-quota") {
570
- setQuotaInfo(message.payload);
571
- }
572
-
573
- if (message.type === "edit-operation") {
574
- const op = message.payload as EditOperation;
575
-
576
- if (op.type === "edit-field") {
577
- const editFieldOperation = op as EditFieldOperation;
578
-
579
- const field = await itemsRepository.getField({
580
- item: {
581
- ...editFieldOperation.mainItem,
582
- id: editFieldOperation.itemId,
583
- },
584
- fieldId: editFieldOperation.fieldId,
585
- });
586
-
587
- if (
588
- !field ||
589
- (field.type !== "single-line text" &&
590
- field.type !== "multi-line text" &&
591
- field.type !== "rich text")
592
- ) {
593
- itemsRepository.refreshItems([
594
- {
595
- ...editFieldOperation.mainItem,
596
- id: editFieldOperation.itemId,
597
- },
598
- ]);
599
- requestRefresh("immediate");
600
- }
601
- //TODO: field value changes that require rerender
602
- else
603
- itemsRepository.updateFieldValue(
604
- {
605
- fieldId: editFieldOperation.fieldId,
606
- item: {
607
- ...editFieldOperation.mainItem,
608
- id: editFieldOperation.itemId,
609
- },
610
- },
611
- editFieldOperation.user ?? { name: "unknown", ai: false },
612
- false,
613
- editFieldOperation.undone
614
- ? editFieldOperation.oldValue
615
- : editFieldOperation.value,
616
- );
617
- } else {
618
- requestRefresh("immediate");
619
- }
620
-
621
- if (
622
- op.mainItem &&
623
- op.mainItem.id === currentItemRef.current?.descriptor.id &&
624
- op.mainItem.language ===
625
- currentItemRef.current?.descriptor.language &&
626
- op.mainItem.version === currentItemRef.current?.descriptor.version
627
- ) {
628
- loadHistory(op.mainItem);
629
- }
630
- }
631
-
632
- socketMessageListeners.current.forEach((listener) => listener(message));
633
- },
634
- [currentItemDescriptorRef, setLastEditedFields],
635
- );
636
-
637
- const user = activeSessions.find((x) => x.sessionId === sessionId)?.user;
638
-
639
- useEffect(() => {
640
- if (typeof window === "undefined") return;
641
- const keepAliveUrl = "/alpaca/editor/keepalive";
642
- const interval = setInterval(
643
- () => {
644
- fetch(keepAliveUrl + "?ts=" + Date.now()).catch((error) =>
645
- console.error("Keep Alive error:", error),
646
- );
647
- },
648
- 5 * 60 * 1000,
649
- );
650
-
651
- const handleMessage = (event: MessageEvent) => {
652
- if (event.data.type === "componentsSelected") {
653
- setSelection(event.data.componentIds);
654
- }
655
- };
656
-
657
- window.addEventListener("message", handleMessage);
658
-
659
- return () => {
660
- window.removeEventListener("message", handleMessage);
661
- clearInterval(interval);
662
- };
663
- }, []);
664
-
665
- // Custom hook for responsive design
666
- const useMediaQuery = (query: string) => {
667
- const [matches, setMatches] = useState(false);
668
-
669
- useEffect(() => {
670
- const media = window.matchMedia(query);
671
- const updateMatch = () => setMatches(media.matches);
672
-
673
- // Set initial value
674
- updateMatch();
675
-
676
- // Listen for changes
677
- media.addEventListener("change", updateMatch);
678
-
679
- // Cleanup
680
- return () => {
681
- media.removeEventListener("change", updateMatch);
682
- };
683
- }, [query]);
684
-
685
- return matches;
686
- };
687
-
688
- // Detect mobile screens (max-width: 768px)
689
- const isMobile = useMediaQuery("(max-width: 768px)");
690
-
691
- useEffect(() => {
692
- const tour = configuration.activeTour;
693
- const key =
694
- tour === "default" ? "editor.tourShown" : "editor.tourShown." + tour;
695
- const tourShown = localStorage.getItem(key);
696
-
697
- if (user?.isLimitedPreviewUser && !tourShown) {
698
- startTour();
699
- localStorage.setItem(key, "true");
700
- }
701
- }, [user]);
702
-
703
- useEffect(() => {
704
- let socket: WebSocket = (globalThis as any).editorSocket;
705
-
706
- if (
707
- socket &&
708
- (socket.readyState === WebSocket.OPEN ||
709
- socket.readyState === WebSocket.CONNECTING)
710
- )
711
- return;
712
-
713
- socket = connectSocket(sessionId);
714
-
715
- // Connection opened
716
- socket.addEventListener("open", () => {
717
- console.log("Connected!");
718
- sendClientInfo();
719
- requestQuota();
720
- //TODO: Load clients
721
- });
722
-
723
- // Listen for messages
724
- socket.addEventListener("message", messageHandler);
725
-
726
- (globalThis as any).editorSocket = socket;
727
- }, []);
728
-
729
- useEffect(() => {
730
- const itemid = searchParams.get("itemid");
731
-
732
- if (searchParams.has("view")) {
733
- setViewName(searchParams.get("view")!);
734
- } else if (!itemid) {
735
- setViewName("splash-screen");
736
- }
737
-
738
- if (searchParams.has("compare"))
739
- setCompareMode(searchParams.get("compare") === "true");
740
- const itemId = cleanId(loadItemDescriptor?.id ?? itemid ?? undefined);
741
- const language = loadItemDescriptor?.language ?? searchParams.get("lang");
742
- const version =
743
- loadItemDescriptor?.version ??
744
- (searchParams.has("version")
745
- ? parseInt(searchParams.get("version")!)
746
- : 0);
747
-
748
- // if (itemid && viewName === "splash-screen") {
749
- // setViewName("page-editor");
750
- // }
751
-
752
- if (!itemId || !language) return;
753
-
754
- if (
755
- currentItemDescriptor?.id === itemId &&
756
- currentItemDescriptor?.language === language &&
757
- (!version || currentItemDescriptor?.version === version)
758
- ) {
759
- return;
760
- }
761
-
762
- loadItem({ id: itemId, language, version });
763
- }, [searchParams, loadItemDescriptor]);
764
-
765
- useEffect(() => {
766
- if (selection.length) setSelectedForInsertion("");
767
-
768
- // Does the current focused field belong to the current item?
769
- const currentItem =
770
- selection.length > 0 ? selection[0] : pageViewContext.page?.item.id;
771
- if (currentItem && focusedField?.item.id !== currentItem) {
772
- setFocusedField(undefined);
773
- }
774
- }, [selection]);
775
-
776
- const addToEditHistory = useCallback(
777
- (currentEditOperation: EditOperation) => {
778
- setEditHistory((history) => {
779
- // Check if the operation was updated or needs to be added
780
- const exists = history.some(
781
- (item) => item.id === currentEditOperation.id,
782
- );
783
- return exists ? history : [currentEditOperation, ...history];
784
- });
785
- },
786
- [],
787
- );
788
-
789
- useEffect(() => {
790
- setRefreshCompletedFlag(!refreshCompletedFlag);
791
- setInserting(undefined);
792
- }, [currentItemDescriptor, pageViewContext.page]);
793
-
794
- useEffect(() => {
795
- sendClientInfo();
796
- }, [currentItemDescriptor]);
797
-
798
- const loadComments = useCallback(async () => {
799
- if (!currentItemDescriptor) return;
800
- const result = await getComments(
801
- currentItemDescriptor.id,
802
- currentItemDescriptor.language,
803
- currentItemDescriptor.version,
804
- );
805
- if (handleErrorResult(result, ui, state)) return;
806
- setComments((x) => {
807
- const loadedComments = result.data as Comment[];
808
- const newComments = x.filter(
809
- (c) =>
810
- c.isNew &&
811
- c.mainItemId === currentItemDescriptor.id &&
812
- c.language === currentItemDescriptor.language &&
813
- c.version === currentItemDescriptor.version,
814
- );
815
- const allComments = [...loadedComments, ...newComments];
816
- allComments.sort((a, b) => a.position - b.position);
817
- return allComments;
818
- });
819
- }, [currentItemDescriptor]);
820
-
821
- // Assuming currentItemDescriptor, ui, state, handleErrorResult, and setSuggestedEdits
822
- // are available in your component context.
823
- const loadSuggestedEdits = useCallback(async () => {
824
- if (!contentEditorItem) return;
825
-
826
- const result = await getSuggestedEdits(
827
- contentEditorItem.descriptor.id,
828
- contentEditorItem.descriptor.language,
829
- contentEditorItem.descriptor.version,
830
- );
831
-
832
- if (handleErrorResult(result, ui, state)) return;
833
-
834
- setSuggestedEdits(result.data || []);
835
- }, [contentEditorItem]);
836
-
837
- const page = pageViewContext.page;
838
-
839
- useEffect(() => {
840
- const isLoading =
841
- !!contentEditorItem &&
842
- contentEditorItem.hasLayout &&
843
- (!page || (page?.editRevision ?? "") != (revision ?? ""));
844
- setIsRefreshing(isLoading && viewName === "page-editor");
845
- if (!isLoading) setInserting(undefined);
846
- }, [page, viewName, revision]);
847
-
848
- useEffect(() => {
849
- if (searchParams.get("fullscreen")) {
850
- pageViewContext.setFullscreen(true);
851
- }
852
- const handleMessage = (event: MessageEvent) => {
853
- if (event.data.action === "refresh") {
854
- requestRefresh("immediate");
855
- }
856
- };
857
-
858
- window.addEventListener("message", handleMessage);
859
-
860
- return () => {
861
- window.removeEventListener("message", handleMessage);
862
- };
863
- }, []);
864
-
865
- const loadHistory = useDebouncedCallback(async (item: ItemDescriptor) => {
866
- const result = await getEditHistory(item);
867
- if (handleErrorResult(result, ui, state)) return;
868
- setEditHistory(result.data || []);
869
- }, 200);
870
-
871
- const refreshHistory = useCallback(async (item: ItemDescriptor) => {
872
- const result = await getEditHistory(item);
873
- if (handleErrorResult(result, ui, state)) return;
874
- setEditHistory(result.data || []);
875
- }, []);
876
-
877
- const requestRefresh = useCallback(
878
- (mode?: "immediate" | "delayed" | "waitForQuietPeriod") => {
879
- const refreshTimer = (globalThis as any).editorRefreshTimer;
880
- const doRefresh = () => {
881
- //console.log("Refreshing");
882
- //const url = new URL(window.location.href);
883
- //url.searchParams.set("edit_rev", uuid());
884
- const newRevision = uuid();
885
- setRevision(newRevision);
886
- console.log("doRefresh", newRevision);
887
- //router.replace(url.toString(), { scroll: false });
888
- (globalThis as any).editorRefreshTimer = undefined;
889
- };
890
- if (mode === "immediate") {
891
- // console.error(
892
- // "Immediate refresh requested. Stack trace:",
893
- // new Error().stack
894
- // );
895
- console.log("Immediate refresh requested");
896
- doRefresh();
897
- return;
898
- }
899
- //console.log("request refresh with mode: " + mode, "timer: ", refreshTimer);
900
- if (mode === "waitForQuietPeriod" && refreshTimer) {
901
- clearTimeout(refreshTimer);
902
- }
903
- if (!refreshTimer || mode === "waitForQuietPeriod") {
904
- (globalThis as any).editorRefreshTimer = setTimeout(
905
- () => {
906
- doRefresh();
907
- },
908
- mode === "waitForQuietPeriod" ? 1200 : 700,
909
- );
910
- }
911
- },
912
- [contentEditorItem, router],
913
- );
914
-
915
- useEffect(() => {
916
- if (!currentItemDescriptor) return;
917
- loadComments();
918
- loadSuggestedEdits();
919
- }, [currentItemDescriptor]);
920
-
921
- // Automatically focus the first rendered field when a new component is added
922
- useEffect(() => {
923
- async function setFocus() {
924
- if (!focusFieldComponentId) return;
925
-
926
- const component = findComponent(
927
- focusFieldComponentId,
928
- page?.rootComponent?.placeholders ?? [],
929
- );
930
-
931
- if (component?.datasourceItem) {
932
- const item = await itemsRepository.getItem(component.datasourceItem);
933
- if (!item) return;
934
- const field = item.fields.find(
935
- (f) =>
936
- component.datasourceItem?.renderedFieldIds?.includes(f.id) &&
937
- (f.type === "single-line text" ||
938
- f.type === "multi-line text" ||
939
- f.type === "rich text"),
940
- );
941
- if (field) {
942
- setFocusedField(field.descriptor);
943
- setFocusFieldComponentId(undefined);
944
- }
945
- }
946
- }
947
- if (mode === "edit") setFocus();
948
- }, [page]);
949
-
950
- useEffect(() => {
951
- if (!currentItemDescriptor) return;
952
-
953
- const current = new URLSearchParams(Array.from(searchParams.entries()));
954
- if (current.get("itemid") !== currentItemDescriptor?.id) {
955
- current.set("itemid", currentItemDescriptor.id);
956
- }
957
- if (current.get("lang") !== currentItemDescriptor?.language) {
958
- current.set("lang", currentItemDescriptor.language);
959
- }
960
- if (current.get("version") !== currentItemDescriptor?.version.toString()) {
961
- current.set("version", currentItemDescriptor.version.toString());
962
- }
963
- if (current.get("view") !== viewName) {
964
- current.set("view", viewName);
965
- }
966
- if (!compareMode) {
967
- current.delete("compare");
968
- current.delete("compareLanguage");
969
- current.delete("compareVersion");
970
- current.delete("compareDevice");
971
- } else {
972
- current.set("compare", "true");
973
- }
974
-
975
- current.set("mode", mode);
976
-
977
- const newUrl = `${pathname}?${current.toString()}`;
978
- router.push(newUrl, { scroll: false });
979
- }, [currentItemDescriptor, viewName, compareMode, mode]);
980
-
981
- useEffect(() => {
982
- async function load() {
983
- if (!currentItemDescriptor) return;
984
- const item = await itemsRepository.getItem(currentItemDescriptor);
985
-
986
- setContentEditorItem(item);
987
- if (!item) return;
988
- if (
989
- contentEditorItem?.descriptor.id === currentItemDescriptor.id &&
990
- contentEditorItem?.descriptor.language ===
991
- currentItemDescriptor.language &&
992
- contentEditorItem?.descriptor.version !== item.version
993
- ) {
994
- showInfoToast({
995
- summary: "New version!",
996
- details: "New version of item loaded",
997
- });
998
- }
999
- }
1000
- load();
1001
- }, [itemsRepository.revision]);
1002
-
1003
- const addToBrowseHistory = useCallback(
1004
- (item: FullItem) => {
1005
- const historyEntry = {
1006
- path: item.path || "unknown",
1007
- name: item.name || "unknown",
1008
- language: item.language,
1009
- templateName: item.templateName,
1010
- id: item.id,
1011
- hasLayout: item.hasLayout,
1012
- icon: item.icon,
1013
- version: item.version,
1014
- };
1015
-
1016
- setBrowseHistory((history: HistoryEntry[]) => {
1017
- const newItem = item;
1018
-
1019
- if (!newItem) return history;
1020
-
1021
- const newHistory = [
1022
- historyEntry,
1023
- ...history
1024
- .filter(
1025
- (x) => x.id !== newItem.id || x.language !== newItem.language,
1026
- )
1027
- .slice(0, 25),
1028
- ];
1029
-
1030
- localStorage.setItem(
1031
- "editor.browseHistory",
1032
- JSON.stringify(newHistory),
1033
- );
1034
-
1035
- return newHistory;
1036
- });
1037
- },
1038
- [browseHistory, setBrowseHistory],
1039
- );
1040
-
1041
- const loadItem = useCallback(
1042
- async (
1043
- itemToLoad: ItemDescriptor | string,
1044
- options?: { addToBrowseHistory?: boolean },
1045
- ): Promise<FullItem | undefined> => {
1046
- if (typeof itemToLoad === "string")
1047
- itemToLoad = {
1048
- id: itemToLoad,
1049
- language: contentEditorItem?.language || "en",
1050
- version: 0,
1051
- };
1052
-
1053
- console.log("load item: " + itemToLoad.id, itemToLoad.language);
1054
- const item = await itemsRepository.getItem(itemToLoad);
1055
-
1056
- if (!item) {
1057
- //TODO: Show error
1058
- console.log("item not found: ", itemToLoad.id, itemToLoad.language);
1059
- return undefined;
1060
- }
1061
-
1062
- if (
1063
- (!item.hasLayout && viewName === "page-editor") ||
1064
- viewName == "splash-screen"
1065
- ) {
1066
- setViewName("content-editor");
1067
- }
1068
-
1069
- // ensure this is object has no additional properties
1070
- itemToLoad = getItemDescriptor(itemToLoad);
1071
-
1072
- // Set state for the item
1073
- setCurrentItemDescriptor(itemToLoad);
1074
- setContentEditorItem(item);
1075
- setSelection([]);
1076
-
1077
- // Directly update URL here
1078
- const current = new URLSearchParams(Array.from(searchParams.entries()));
1079
-
1080
- current.set("itemid", itemToLoad.id);
1081
- current.set("lang", itemToLoad.language);
1082
- current.set("version", itemToLoad.version.toString());
1083
- current.set("view", viewName);
1084
-
1085
- if (!compareMode) {
1086
- current.delete("compare");
1087
- current.delete("compareLanguage");
1088
- current.delete("compareVersion");
1089
- current.delete("compareDevice");
1090
- } else {
1091
- current.set("compare", "true");
1092
- }
1093
-
1094
- current.set("mode", mode);
1095
-
1096
- const newUrl = `${pathname}?${current.toString()}`;
1097
-
1098
- // Wait for the URL update to complete
1099
- await router.push(newUrl, { scroll: false });
1100
-
1101
- // Now that URL is updated, load history and add to browse history
1102
- loadHistory(itemToLoad);
1103
- if (
1104
- options?.addToBrowseHistory ||
1105
- options?.addToBrowseHistory === undefined
1106
- ) {
1107
- addToBrowseHistory(item);
1108
- }
1109
-
1110
- return item;
1111
- },
1112
- [
1113
- itemsRepository,
1114
- setContentEditorItem,
1115
- searchParams,
1116
- pathname,
1117
- router,
1118
- viewName,
1119
- compareMode,
1120
- ],
1121
- );
1122
-
1123
- useEffect(() => {
1124
- if (pageViewContext.fullscreen && !searchParams.get("fullscreen"))
1125
- setShowFullscreenHint(true);
1126
- }, [pageViewContext.fullscreen]);
1127
-
1128
- const executeCommand = useCallback(
1129
- async <T extends CommandData>({
1130
- command,
1131
- event,
1132
- data,
1133
- }: {
1134
- command: Command<T>;
1135
- data?: any;
1136
- event?: React.SyntheticEvent;
1137
- }): Promise<any> => {
1138
- if (!editContextRef.current) return;
1139
- const context = {
1140
- editContext: editContextRef.current,
1141
- pathname,
1142
- router,
1143
- searchParams,
1144
- openDialog: openDialog,
1145
- event,
1146
- data,
1147
- };
1148
- const result = await command.execute(context);
1149
-
1150
- return result;
1151
- },
1152
- [editContextRef, pathname, router, searchParams],
1153
- );
1154
-
1155
- const handleKeyDownDebounced = useDebouncedCallback(
1156
- async (event: KeyboardEvent) => {
1157
- if (!event.key) return;
1158
-
1159
- if (event.ctrlKey && event.key === "z") {
1160
- await operations.undo();
1161
- }
1162
- if (event.ctrlKey && event.key === "y") {
1163
- await operations.redo();
1164
- }
1165
- if (event.ctrlKey && event.key === "F11") {
1166
- event.preventDefault();
1167
- pageViewContext.setFullscreen(false);
1168
- }
1169
-
1170
- const command = configuration.commands.allItemCommands.find(
1171
- (x) => x.keyBinding === event.key,
1172
- );
1173
-
1174
- if (command) {
1175
- event.preventDefault();
1176
- const contentEditorItem = editContextRef.current?.contentEditorItem;
1177
- if (!contentEditorItem) return;
1178
-
1179
- const items =
1180
- editContextRef.current?.selection?.map((x) => ({
1181
- id: x,
1182
- language: contentEditorItem.language,
1183
- version: 0,
1184
- })) || [];
1185
-
1186
- if (!items.length) items.push(contentEditorItem.descriptor);
1187
-
1188
- if (items.length > 0) {
1189
- const fullItems =
1190
- await editContextRef.current?.itemsRepository.getItems(items);
1191
- executeCommand({
1192
- command,
1193
- data: {
1194
- items: fullItems,
1195
- },
1196
- });
1197
- }
1198
- }
1199
- },
1200
- 50,
1201
- );
1202
-
1203
- const handleKeyDown = useCallback(
1204
- async (event: KeyboardEvent) => {
1205
- if (event.key === "Insert") {
1206
- event.preventDefault();
1207
- event.stopPropagation();
1208
- editContext.setInsertMode((x) => !x);
1209
- }
1210
-
1211
- if (event.ctrlKey && event.key === "s") {
1212
- event.preventDefault();
1213
- event.stopPropagation();
1214
- return;
1215
- }
1216
-
1217
- const target = event.target as HTMLElement;
1218
- const isTyping =
1219
- target instanceof HTMLInputElement ||
1220
- target instanceof HTMLTextAreaElement ||
1221
- target.isContentEditable;
1222
-
1223
- if (
1224
- (event.ctrlKey && event.key === "z") ||
1225
- (event.ctrlKey && event.key === "y")
1226
- ) {
1227
- if (!isTyping) {
1228
- event.preventDefault();
1229
- event.stopPropagation();
1230
- handleKeyDownDebounced(event);
1231
- }
1232
- return;
1233
- }
1234
- handleKeyDownDebounced(event);
1235
- },
1236
- [
1237
- configuration.commands.allItemCommands,
1238
- executeCommand,
1239
- editContextRef.current,
1240
- ],
1241
- );
1242
-
1243
- if (typeof window !== "undefined")
1244
- if (typeof window !== "undefined")
1245
- useEventListenerExt("keydown", handleKeyDown, window, true);
1246
-
1247
- if (typeof window !== "undefined")
1248
- useEventListenerExt(
1249
- "click",
1250
- () => {
1251
- contextMenuRef.current?.close({});
1252
- },
1253
- window,
1254
- true,
1255
- );
1256
- const state = useMemo(
1257
- () => ({
1258
- page,
1259
- configuration,
1260
- selection,
1261
- setSelection,
1262
- loadItem,
1263
- addToEditHistory,
1264
- setLockedField,
1265
- contentEditorItem,
1266
- sessionId,
1267
- requestRefresh,
1268
- lockedField,
1269
- itemsRepository,
1270
- user,
1271
- editHistory,
1272
- refreshHistory,
1273
- suggestedEdits,
1274
- setSuggestedEdits,
1275
- mode,
1276
- setFocusFieldComponentId,
1277
- }),
1278
- [
1279
- page,
1280
- configuration,
1281
- selection,
1282
- setSelection,
1283
- loadItem,
1284
- addToEditHistory,
1285
- setLockedField,
1286
- contentEditorItem,
1287
- sessionId,
1288
- requestRefresh,
1289
- lockedField,
1290
- itemsRepository,
1291
- user,
1292
- editHistory,
1293
- refreshHistory,
1294
- suggestedEdits,
1295
- setSuggestedEdits,
1296
- mode,
1297
- setFocusFieldComponentId,
1298
- ],
1299
- );
1300
-
1301
- useEffect(() => {
1302
- if (currentOverlay !== "ai") aiPopupRef.current?.close();
1303
- if (currentOverlay !== "fields") fieldEditorPopupRef.current?.close();
1304
- if (currentOverlay !== "context-menu") contextMenuRef.current?.close({});
1305
- }, [currentOverlay]);
1306
-
1307
- const toast = useRef<Toast | null>(null);
1308
-
1309
- useEffect(() => {
1310
- loadItemVersions();
1311
- }, [currentItemDescriptor]);
1312
-
1313
- const loadItemVersions = useCallback(async () => {
1314
- if (!currentItemDescriptorRef.current) {
1315
- setItemVersions([]);
1316
- setItemLanguages([]);
1317
- return;
1318
- }
1319
-
1320
- const result = await getLanguagesAndVersions(
1321
- currentItemDescriptorRef.current,
1322
- );
1323
- if (!result?.data) {
1324
- setItemVersions([]);
1325
- setItemLanguages([]);
1326
- showErrorToast({
1327
- summary: "Error",
1328
- details: "Failed to load item versions",
1329
- });
1330
- return;
1331
- }
1332
- const v = [...result.data.versions].reverse() || [];
1333
-
1334
- setItemVersions(v);
1335
- setItemLanguages(result.data.languages);
1336
- }, [currentItemDescriptorRef.current, setItemVersions, setItemLanguages]);
1337
-
1338
- const showErrorToast = useCallback(
1339
- ({ summary, details }: { summary?: string; details?: string }) => {
1340
- toast.current?.show({
1341
- severity: "error",
1342
- summary: summary || "Error",
1343
- detail: details || "An error occurred",
1344
- life: 3000,
1345
- });
1346
- },
1347
- [],
1348
- );
1349
-
1350
- const showInfoToast = useCallback(
1351
- ({ summary, details }: { summary?: string; details?: string }) => {
1352
- toast.current?.show({
1353
- severity: "info",
1354
- summary: summary || "Info",
1355
- detail: details || "Information",
1356
- life: 3000,
1357
- });
1358
- },
1359
- [],
1360
- );
1361
-
1362
- const ui = useMemo(
1363
- () => ({
1364
- showErrorToast,
1365
- confirmationDialogRef,
1366
- onOperationExecuted: (op: EditOperation) => {
1367
- // Replace the operation in edit history with the executed operation
1368
-
1369
- setEditHistory((prev) => {
1370
- const existingOpIndex = prev.findIndex((x) => x.id === op.id);
1371
- if (existingOpIndex >= 0) {
1372
- prev[existingOpIndex] = op;
1373
- }
1374
- return prev;
1375
- });
1376
-
1377
- if (
1378
- contentEditorItem?.id === op.mainItem?.id &&
1379
- contentEditorItem?.language === op.mainItem?.language &&
1380
- contentEditorItem?.version === op.mainItem?.version
1381
- ) {
1382
- setInsertMode(false);
1383
- }
1384
- },
1385
- }),
1386
- [showErrorToast, confirmationDialogRef, currentItemDescriptor],
1387
- );
1388
-
1389
- const selectMedia = useCallback(
1390
- ({
1391
- selectedIdPath,
1392
- mode,
1393
- }: {
1394
- selectedIdPath: string;
1395
- mode?: MediaSelectorMode;
1396
- }) => {
1397
- setSelectedMediaIdPath(selectedIdPath);
1398
- setMediaSelectorVisible(true);
1399
- if (mode) setMediaSelectorMode(mode);
1400
- return new Promise<string>((resolve) => {
1401
- setMediaResolver(() => resolve);
1402
- });
1403
- },
1404
- [],
1405
- );
1406
-
1407
- const onMediaSelect = useCallback(
1408
- (mediaUrl: string) => {
1409
- mediaResolver?.(mediaUrl);
1410
- setMediaSelectorVisible(false);
1411
- setMediaResolver(undefined);
1412
- },
1413
- [mediaResolver],
1414
- );
1415
-
1416
- useEffect(() => {
1417
- if (!workboxItems || workboxItems.length === 0) return;
1418
- const itemsToValidate = workboxItems.map((x) => x.item);
1419
- validate(itemsToValidate);
1420
- }, [workboxItems]);
1421
-
1422
- async function loadWorkbox(items: ItemDescriptor[]) {
1423
- if (!items.length) {
1424
- setWorkboxItems([]);
1425
- return;
1426
- }
1427
-
1428
- const workbox = await getWorkbox(items);
1429
- const workboxItems: WorkboxItem[] = workbox.data || [];
1430
-
1431
- const sortedWorkboxItems = workboxItems.sort((a, b) => {
1432
- if (a.isPublished === b.isPublished)
1433
- return (
1434
- (b.workflowCommands?.length || 0) - (a.workflowCommands?.length || 0)
1435
- );
1436
- return !a.isPublished || !a.isPublishable ? -1 : 1;
1437
- });
1438
-
1439
- setWorkboxItems(sortedWorkboxItems);
1440
- }
1441
-
1442
- const loadWorkboxDebounced = useDebouncedCallback(
1443
- (items: ItemDescriptor[]) => loadWorkbox(items),
1444
- 5000,
1445
- );
1446
-
1447
- useEffect(() => {
1448
- const items: ItemDescriptor[] = [];
1449
-
1450
- if (editContext.contentEditorItem) {
1451
- items.push(editContext.contentEditorItem.descriptor);
1452
- }
1453
-
1454
- if (editContext.page) {
1455
- collectAllItems(editContext.page.rootComponent, items);
1456
- }
1457
-
1458
- loadWorkboxDebounced(items.filter((x) => x));
1459
- }, [page, contentEditorItem]);
1460
-
1461
- function collectAllItems(component: Component, items: ItemDescriptor[]) {
1462
- component.placeholders.forEach((x) => {
1463
- x.components.forEach((y) => {
1464
- if (y.isShared && y.datasourceItem) {
1465
- items.push(y.datasourceItem);
1466
- }
1467
-
1468
- //TODO: Add picture fields
1469
- // y.datasourceItem?.fields.forEach((z) => {
1470
- // if (z.type === "picture") {
1471
- // const picture = z.value as PictureValue;
1472
- // if (picture.variants) {
1473
- // picture.variants.forEach((v) => {
1474
- // if (v.mediaId) {
1475
- // items.push({
1476
- // id: v.mediaId,
1477
- // language: y.datasourceItem!.descriptor.language,
1478
- // version: 0,
1479
- // });
1480
- // }
1481
- // });
1482
- // }
1483
- // }
1484
- // });
1485
-
1486
- collectAllItems(y, items);
1487
- });
1488
- });
1489
- }
1490
-
1491
- const switchView = (
1492
- viewName: string,
1493
- options?: { skipConfirmation?: boolean },
1494
- ) => {
1495
- async function switchView() {
1496
- if (currentView?.beforeClose && !options?.skipConfirmation) {
1497
- const result = await currentView.beforeClose(editContext);
1498
- if (!result) return;
1499
- }
1500
- if (typeof document.startViewTransition === "function") {
1501
- document.startViewTransition(() => {
1502
- flushSync(() => {
1503
- setViewName(viewName);
1504
- });
1505
- });
1506
- } else {
1507
- setViewName(viewName);
1508
- }
1509
- }
1510
- switchView();
1511
- };
1512
-
1513
- const [dialog, setDialog] = useState<ReactNode>(null);
1514
-
1515
- const openDialog: OpenDialog = (Component, props) => {
1516
- return new Promise((resolve) => {
1517
- const handleClose = (result: any) => {
1518
- setDialog(null);
1519
- resolve(result);
1520
- };
1521
-
1522
- setDialog(<Component {...(props as any)} onClose={handleClose} />);
1523
- });
1524
- };
1525
-
1526
- const operationsContext = getOperationsContext(state, ui);
1527
-
1528
- const operations = operationsContext.ops;
1529
-
1530
- useEffect(() => {
1531
- if (mode === "suggestions") {
1532
- setShowSuggestedEdits(true);
1533
- } else {
1534
- setShowSuggestedEdits(false);
1535
- }
1536
- }, [mode]);
1537
-
1538
- const isReadOnly =
1539
- !contentEditorItem ||
1540
- !contentEditorItem.canWriteItem ||
1541
- !contentEditorItem.canWriteLanguage ||
1542
- !contentEditorItem.canWriteWorkflow;
1543
-
1544
- const updateUrl = useCallback((params: Record<string, string>) => {
1545
- const url = new URL(window.location.href);
1546
- Object.entries(params).forEach(([key, value]) => {
1547
- if (value) url.searchParams.set(key, value);
1548
- else url.searchParams.delete(key);
1549
- });
1550
- router.push(url.toString(), { scroll: false });
1551
- }, []);
1552
-
1553
- // Quota checking functions
1554
- const isQuotaExceeded = useCallback(() => {
1555
- if (!quotaInfo) return false;
1556
-
1557
- const { usage, limits } = quotaInfo;
1558
-
1559
- // Check absolute limits
1560
- if (limits.totalTokens > 0 && usage.totalTokens >= limits.totalTokens)
1561
- return true;
1562
- if (limits.totalImages > 0 && usage.totalImages >= limits.totalImages)
1563
- return true;
1564
-
1565
- // For now, we're only checking absolute limits as daily/monthly would require server-side logic
1566
- // You can extend this to check daily/monthly limits if the server provides that information
1567
-
1568
- return false;
1569
- }, [quotaInfo]);
1570
-
1571
- const getQuotaWarningMessage = useCallback(() => {
1572
- if (!quotaInfo) return null;
1573
-
1574
- const { usage, limits } = quotaInfo;
1575
- const warnings: string[] = [];
1576
-
1577
- // Check tokens
1578
- if (limits.totalTokens > 0) {
1579
- const tokenPercentage = (usage.totalTokens / limits.totalTokens) * 100;
1580
- if (tokenPercentage >= 100) {
1581
- warnings.push(
1582
- `Token limit exceeded (${usage.totalTokens}/${limits.totalTokens})`,
1583
- );
1584
- } else if (tokenPercentage >= 90) {
1585
- warnings.push(
1586
- `Token usage high: ${Math.round(tokenPercentage)}% (${usage.totalTokens}/${limits.totalTokens})`,
1587
- );
1588
- }
1589
- }
1590
-
1591
- // Check images
1592
- if (limits.totalImages > 0) {
1593
- const imagePercentage = (usage.totalImages / limits.totalImages) * 100;
1594
- if (imagePercentage >= 100) {
1595
- warnings.push(
1596
- `Image limit exceeded (${usage.totalImages}/${limits.totalImages})`,
1597
- );
1598
- } else if (imagePercentage >= 90) {
1599
- warnings.push(
1600
- `Image usage high: ${Math.round(imagePercentage)}% (${usage.totalImages}/${limits.totalImages})`,
1601
- );
1602
- }
1603
- }
1604
-
1605
- return warnings.length > 0 ? warnings.join(", ") : null;
1606
- }, [quotaInfo]);
1607
-
1608
- // Show warning when quota is exceeded
1609
- useEffect(() => {
1610
- const warningMessage = getQuotaWarningMessage();
1611
- if (warningMessage) {
1612
- const isExceeded = isQuotaExceeded();
1613
- showErrorToast({
1614
- summary: isExceeded ? "AI Quota Exceeded" : "AI Quota Warning",
1615
- details: warningMessage,
1616
- });
1617
- }
1618
- }, [quotaInfo, getQuotaWarningMessage, isQuotaExceeded, showErrorToast]);
1619
-
1620
- const editContext = useMemo(() => {
1621
- return {
1622
- operations: operationsContext.ops,
1623
- itemsRepository,
1624
- configuration,
1625
- updateUrl,
1626
- openSplashScreen: () => {
1627
- router.push("/alpaca/editor");
1628
- },
1629
- item: contentEditorItem || page?.item,
1630
- itemLanguages,
1631
- itemVersions,
1632
- sessionId: sessionId,
1633
- readonly: isReadOnly,
1634
- selection,
1635
- select: (ids: string[]) => {
1636
- setSelection(ids);
1637
- },
1638
- selectedForInsertion,
1639
- setSelectedForInsertion,
1640
- dragObject,
1641
- workboxItems,
1642
- requestRefresh,
1643
- refreshCompletedFlag,
1644
- openCreatePageDialog: () => {
1645
- const current = new URLSearchParams(Array.from(searchParams.entries()));
1646
- current.delete("version");
1647
- current.delete("itemid");
1648
- current.delete("view");
1649
- current.set("create", "1");
1650
- const newUrl = `${pathname}?${current.toString()}`;
1651
- router.push(newUrl, { scroll: false });
1652
- },
1653
- selectMedia,
1654
- showToast: (message: ToastMessage | ToastMessage[]) => {
1655
- toast.current?.show(message);
1656
- },
1657
- scrollIntoView,
1658
- setScrollIntoView,
1659
- focusedField,
1660
- setFocusedField: async (
1661
- field: FieldDescriptor | undefined,
1662
- requestLock: boolean,
1663
- ) => {
1664
- if (field) {
1665
- setIgnoreBlur(true);
1666
- setTimeout(() => {
1667
- setIgnoreBlur(false);
1668
- }, 20);
1669
- setFocusedField({ ...field });
1670
- if (requestLock) {
1671
- return (await operations.ensureLock(field)) || false;
1672
- }
1673
- } else {
1674
- if (!ignoreBlur) {
1675
- setFocusedField(undefined);
1676
- releaseFieldLocks(sessionId);
1677
- }
1678
- }
1679
- return true;
1680
- },
1681
- renderedFields,
1682
- setRenderedFields,
1683
- getComponentCommands: async (components: Component[]) => {
1684
- return await getComponentCommands(components, editContext);
1685
- },
1686
- dragStart: (dragObject: DragObject) => {
1687
- setDragObject(dragObject);
1688
- },
1689
- dragEnd: () => {
1690
- setDragObject(undefined);
1691
- },
1692
- droppedInPlaceholder: async (
1693
- placeholderKey: string,
1694
- index: number,
1695
- spotPositionElement?: Element,
1696
- spotPositionAnchor?: "left" | "right" | "top" | "bottom",
1697
- insertOption?: InsertOption,
1698
- ) => {
1699
- if ((!dragObject && !insertOption) || !page) return;
1700
- setDragObject(undefined);
1701
-
1702
- if (spotPositionElement && spotPositionAnchor) {
1703
- setInserting({
1704
- positionElement: spotPositionElement,
1705
- positionAnchor: spotPositionAnchor,
1706
- });
1707
- }
1708
-
1709
- const placeholderKeyComponents = placeholderKey.split("_");
1710
- const parentId =
1711
- placeholderKeyComponents.length === 1
1712
- ? undefined
1713
- : placeholderKeyComponents[1];
1714
-
1715
- if (parentId) setSelection([parentId]);
1716
-
1717
- let op: EditOperation | undefined;
1718
-
1719
- if (!currentItemDescriptorRef.current) return;
1720
-
1721
- if (insertOption) {
1722
- operations.addComponent(
1723
- insertOption.typeId,
1724
- placeholderKey,
1725
- index,
1726
- currentItemDescriptorRef.current,
1727
- );
1728
- return;
1729
- }
1730
-
1731
- if (!dragObject) return;
1732
-
1733
- if (dragObject && dragObject.type == "template") {
1734
- operations.addComponent(
1735
- dragObject.typeId,
1736
- placeholderKey,
1737
- index,
1738
- currentItemDescriptorRef.current,
1739
- );
1740
- return;
1741
- }
1742
-
1743
- if (
1744
- dragObject.type == "component" ||
1745
- (dragObject.type == "link-component" && dragObject.component)
1746
- ) {
1747
- const parentComponent =
1748
- parentId && page ? getComponentById(parentId, page) : null;
1749
-
1750
- if (dragObject.type == "link-component") {
1751
- op = {
1752
- type: "link-component",
1753
- mainItem: page!.item.descriptor,
1754
- parent: parentId && {
1755
- id: parentId,
1756
- language: page!.item.descriptor.language,
1757
- version: page!.item.descriptor.version,
1758
- name: parentComponent?.name,
1759
- },
1760
- placeholderKey,
1761
- placeholderIndex: index,
1762
- date: new Date().toISOString(),
1763
- id: uuid(),
1764
- linkedComponentItem: dragObject.component,
1765
- description: "Link component",
1766
- } as LinkComponentOperation;
1767
- } else {
1768
- if (!dragObject.component) return;
1769
- op = {
1770
- type: "move-component",
1771
- mainItem: page!.item.descriptor,
1772
- parent: parentId && {
1773
- id: parentId,
1774
- language: page!.item.descriptor.language,
1775
- version: page!.item.descriptor.version,
1776
- },
1777
- placeholderKey,
1778
- placeholderIndex: index,
1779
- componentIds: [dragObject.component.id],
1780
- date: new Date().toISOString(),
1781
- id: uuid(),
1782
- description: "Move component",
1783
- } as MoveComponentOperation;
1784
- }
1785
- }
1786
-
1787
- if (op) operations.executeEditOperation(op);
1788
- },
1789
- page,
1790
- triggerFieldAction: async (
1791
- fieldDescriptor: FieldDescriptor,
1792
- actionButton: FieldButton,
1793
- ) => {
1794
- const field = await itemsRepository.getField(fieldDescriptor);
1795
-
1796
- if (!field) return;
1797
-
1798
- const op: FieldAction = {
1799
- field: fieldDescriptor,
1800
- // actionButton,
1801
- label: actionButton.label,
1802
- state: "running",
1803
- };
1804
- const fieldItem = fieldDescriptor.item;
1805
- setActiveFieldActions((prevFieldActions) => [
1806
- ...prevFieldActions.filter(
1807
- (x) =>
1808
- !(
1809
- x.field.fieldId == fieldDescriptor.fieldId &&
1810
- x.field.item.id == fieldItem.id &&
1811
- x.field.item.language == fieldItem.language &&
1812
- x.field.item.version == fieldItem.version
1813
- ),
1814
- ),
1815
- op,
1816
- ]);
1817
-
1818
- if ("clientAction" in actionButton) {
1819
- await (actionButton as ClientFieldButton).clientAction!({
1820
- field,
1821
- editContext,
1822
- //dialogContext,
1823
- });
1824
- }
1825
-
1826
- if (actionButton.action) {
1827
- await executeFieldServerAction(
1828
- fieldDescriptor.item,
1829
- fieldDescriptor.fieldId,
1830
- contentEditorItem!.descriptor,
1831
- actionButton.action,
1832
- editContext!.sessionId,
1833
- selectedRange?.text || "",
1834
- (data: any) => {
1835
- op.message = data.responseText;
1836
- setActiveFieldActions((prevFieldActions) => [
1837
- ...prevFieldActions,
1838
- ]);
1839
- },
1840
- );
1841
-
1842
- itemsRepository.refreshItems([fieldDescriptor.item]);
1843
-
1844
- op.state = "success";
1845
- requestRefresh("immediate");
1846
- }
1847
- },
1848
- activeFieldActions,
1849
- showContextMenu: (event: any, items: MenuItem[]) => {
1850
- contextMenuRef.current?.show(event, items);
1851
- setCurrentOverlay("context-menu");
1852
- },
1853
- showAiPopup: (
1854
- event: MouseEvent<HTMLElement>,
1855
- aiTerminalOptions?: AiTerminalOptions,
1856
- ) => {
1857
- setCurrentOverlay("ai");
1858
- aiPopupRef.current?.show(event, aiTerminalOptions);
1859
- },
1860
- showFieldEditorPopup: (fields: Field[], sections: string[], ev: any) => {
1861
- setCurrentOverlay("fields");
1862
- fieldEditorPopupRef.current?.show(fields, sections, ev);
1863
- },
1864
- inserting,
1865
- validating,
1866
- validationResult,
1867
- contentEditorItem,
1868
- loadItem,
1869
- editHistory,
1870
- isRefreshing,
1871
- activeSessions,
1872
- unlockField: async () => {
1873
- await releaseFieldLocks(sessionId);
1874
- },
1875
- isCommandDisabled: <T extends CommandData>({
1876
- command,
1877
- data,
1878
- }: {
1879
- command: Command<T>;
1880
- data?: T;
1881
- }): boolean => {
1882
- const props = {
1883
- editContext,
1884
- pathname,
1885
- router,
1886
- searchParams,
1887
- data,
1888
- openDialog,
1889
- };
1890
-
1891
- return command.disabled(props);
1892
- },
1893
- executeCommand,
1894
- viewName,
1895
- switchView,
1896
- compareMode,
1897
- setCompareMode,
1898
- view: currentView,
1899
- pageView: pageViewContext,
1900
- componentDesignerComponent,
1901
- setComponentDesignerComponent,
1902
- componentDesignerRendering,
1903
- setComponentDesignerRendering,
1904
- insertMode,
1905
- setInsertMode,
1906
- currentOverlay,
1907
- setCurrentOverlay,
1908
- inlineEditingFieldElement,
1909
- setInlineEditingFieldElement,
1910
- lockedField,
1911
- timings,
1912
- setTimings,
1913
- selectedRange,
1914
- setSelectedRange,
1915
- confirmationDialogRef,
1916
- confirm: (props: ConfirmationProps) => {
1917
- confirmationDialogRef.current?.confirm(props);
1918
- },
1919
- showMessageDialog: (props: { header: string; message: string }) => {
1920
- confirmationDialogRef.current?.confirm({
1921
- header: props.header,
1922
- message: props.message,
1923
- accept: () => {},
1924
- acceptLabel: "Ok",
1925
- });
1926
- },
1927
- browseHistory,
1928
- setCenterPanelView,
1929
- handleKeyDown,
1930
- startTour,
1931
- addSocketMessageListener,
1932
- currentItemDescriptor,
1933
- compareTo,
1934
- setCompareTo,
1935
- lastEditedFields,
1936
- revision,
1937
- selectedComment,
1938
- setSelectedComment,
1939
- comments,
1940
- loadComments,
1941
- setComments,
1942
- showComments,
1943
- setShowComments,
1944
- addComment: async () => {
1945
- const descriptor = contentEditorItem?.descriptor;
1946
- if (!descriptor) return;
1947
-
1948
- const itemId =
1949
- focusedField?.item.id ||
1950
- (selection.length > 0 ? selection[0] : undefined) ||
1951
- descriptor.id;
1952
-
1953
- if (!itemId) return;
1954
-
1955
- const language = descriptor.language;
1956
- const version = descriptor.version;
1957
-
1958
- const getFieldName = async () => {
1959
- if (!focusedField) return "";
1960
- const field = await itemsRepository.getField(focusedField);
1961
- return field?.name;
1962
- };
1963
-
1964
- const getItemName = async () => {
1965
- const item = await itemsRepository.getItem({
1966
- id: itemId,
1967
- language,
1968
- version,
1969
- });
1970
- return item?.name;
1971
- };
1972
-
1973
- const newComment = {
1974
- id: uuid(),
1975
- isNew: true,
1976
- itemId,
1977
- itemName: await getItemName(),
1978
- fieldId: focusedField?.fieldId,
1979
- fieldName: await getFieldName(),
1980
- mainItemId: descriptor.id,
1981
- language,
1982
- version,
1983
- position: 0,
1984
- rangeStart: selectedRange?.startOffset || 0,
1985
- rangeEnd: selectedRange?.endOffset || 0,
1986
- author: user?.name,
1987
- authorDisplayName: user?.displayName,
1988
- date: new Date().toISOString(),
1989
- };
1990
-
1991
- setComments([newComment, ...comments]);
1992
- setSelectedComment(newComment);
1993
- setShowComments(true);
1994
- },
1995
- mode,
1996
- setMode,
1997
-
1998
- user,
1999
- reviews,
2000
- statusMessage,
2001
- setStatusMessage,
2002
- suggestedEdits,
2003
- showSuggestedEdits,
2004
- setShowSuggestedEdits,
2005
- showSuggestedEditsDiff,
2006
- setShowSuggestedEditsDiff,
2007
- enableCompletions,
2008
- setEnableCompletions,
2009
- quotaInfo,
2010
- isQuotaExceeded: isQuotaExceeded(),
2011
- getQuotaWarningMessage,
2012
- isMobile,
2013
- openDialog,
2014
- pageWizard,
2015
- webSocketMessages,
2016
- clearWebSocketMessages: () => setWebSocketMessages([]),
2017
- userInfo,
2018
- };
2019
- }, [
2020
- operations,
2021
- itemsRepository,
2022
- configuration,
2023
- contentEditorItem,
2024
- page?.item,
2025
- itemLanguages,
2026
- itemVersions,
2027
- sessionId,
2028
- isReadOnly,
2029
- selection,
2030
- selectedForInsertion,
2031
- dragObject,
2032
- requestRefresh,
2033
- refreshCompletedFlag,
2034
- searchParams,
2035
- pathname,
2036
- router,
2037
- selectMedia,
2038
- scrollIntoView,
2039
- focusedField,
2040
- renderedFields,
2041
- inserting,
2042
- page,
2043
- activeFieldActions,
2044
- editHistory,
2045
- isRefreshing,
2046
- activeSessions,
2047
- currentView,
2048
- componentDesignerComponent,
2049
- componentDesignerRendering,
2050
- insertMode,
2051
- currentOverlay,
2052
- inlineEditingFieldElement,
2053
- lockedField,
2054
- selectedRange,
2055
- pageViewContext,
2056
- browseHistory,
2057
- workboxItems,
2058
- validating,
2059
- setCenterPanelView,
2060
- handleKeyDown,
2061
- setTimings,
2062
- timings,
2063
- startTour,
2064
- viewName,
2065
- compareMode,
2066
- setCompareMode,
2067
- addSocketMessageListener,
2068
- currentItemDescriptor,
2069
- compareTo,
2070
- setCompareTo,
2071
- lastEditedFields,
2072
- revision,
2073
- comments,
2074
- setComments,
2075
- selectedComment,
2076
- setSelectedComment,
2077
- loadComments,
2078
- mode,
2079
- setMode,
2080
- user,
2081
- reviews,
2082
- statusMessage,
2083
- setStatusMessage,
2084
- suggestedEdits,
2085
- setSuggestedEdits,
2086
- showSuggestedEdits,
2087
- setShowSuggestedEdits,
2088
- showSuggestedEditsDiff,
2089
- setShowSuggestedEditsDiff,
2090
- quotaInfo,
2091
- isQuotaExceeded,
2092
- getQuotaWarningMessage,
2093
- isMobile,
2094
- openDialog,
2095
- pageWizard,
2096
- webSocketMessages,
2097
- userInfo,
2098
- ]);
2099
-
2100
- const modifiedFieldsContext = useMemo(() => {
2101
- return {
2102
- modifiedFields,
2103
- clear: () => {
2104
- setModifiedFields([]);
2105
- },
2106
- };
2107
- }, [modifiedFields, setModifiedFields]);
2108
-
2109
- useEffect(() => {
2110
- editContextRef.current = editContext;
2111
- }, [editContext]);
2112
-
2113
- useEffect(() => {
2114
- modifiedFieldsContext.clear();
2115
- }, [currentItemDescriptor]);
2116
-
2117
- if (!currentView) return null;
2118
-
2119
- return (
2120
- <div className={`editor h-full`}>
2121
- <OperationsContextProvider value={operationsContext.context}>
2122
- <ModifiedFieldsContextProvider value={modifiedFieldsContext}>
2123
- <EditContextProvider value={editContext}>
2124
- {pageViewContext.fullscreen ? (
2125
- <>
2126
- <div className="fixed inset-0 flex">
2127
- <PageViewerFrame
2128
- compareView={compareMode}
2129
- pageViewContext={pageViewContext}
2130
- />
2131
- </div>
2132
- {showFullscreenHint && (
2133
- <div
2134
- className="fixed inset-0"
2135
- onMouseMoveCapture={() => {
2136
- setTimeout(() => {
2137
- setShowFullscreenHint(false);
2138
- }, 600);
2139
- }}
2140
- >
2141
- <div className="fixed top-3 left-1/2 -translate-x-1/2 transform rounded-sm bg-gray-200 p-12">
2142
- Press Ctrl + F11 to exit fullscreen mode
2143
- </div>
2144
- </div>
2145
- )}
2146
- </>
2147
- ) : (
2148
- <>
2149
- <Toast ref={toast} />
2150
- <ConfirmationDialog ref={confirmationDialogRef} />
2151
- <EditContextMenu ref={contextMenuRef} />
2152
- <MainLayout
2153
- className={className}
2154
- view={currentView}
2155
- centerPanelView={centerPanelView}
2156
- rightSidebar={
2157
- currentView.rightSidebar && (
2158
- <SidebarView
2159
- sidebar={currentView.rightSidebar}
2160
- editContext={editContext}
2161
- active={true}
2162
- />
2163
- )
2164
- }
2165
- rightSidebarTitle={currentView.rightSidebar?.title}
2166
- />
2167
-
2168
- {mediaSelectorVisible && (
2169
- <MediaSelector
2170
- language={editContext.currentItemDescriptor!.language}
2171
- visible={mediaSelectorVisible}
2172
- onHide={() => setMediaSelectorVisible(false)}
2173
- onMediaSelected={onMediaSelect}
2174
- selectedIdPath={selectedMediaIdPath}
2175
- mode={mediaSelectorMode}
2176
- />
2177
- )}
2178
-
2179
- <AiPopup ref={aiPopupRef} />
2180
- <FieldEditorPopup ref={fieldEditorPopupRef} />
2181
- {isTourActive && (
2182
- <Tour tourStopCallback={() => setIsTourActive(false)} />
2183
- )}
2184
- </>
2185
- )}
2186
- {editContext.isRefreshing && (
2187
- <div className="pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center text-gray-600 opacity-50 select-none">
2188
- <Spinner />
2189
- </div>
2190
- )}
2191
- {dialog}
2192
- </EditContextProvider>
2193
- </ModifiedFieldsContextProvider>
2194
- </OperationsContextProvider>
2195
- </div>
2196
- );
2197
- }