@alpaca-editor/core 1.0.0

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 (239) hide show
  1. package/.prettierrc +3 -0
  2. package/eslint.config.mjs +4 -0
  3. package/images/bg-shape-black.webp +0 -0
  4. package/package.json +52 -0
  5. package/src/client-components/api.ts +6 -0
  6. package/src/client-components/index.ts +19 -0
  7. package/src/components/ActionButton.tsx +43 -0
  8. package/src/components/Error.tsx +57 -0
  9. package/src/config/config.tsx +737 -0
  10. package/src/config/types.ts +263 -0
  11. package/src/editor/ComponentInfo.tsx +77 -0
  12. package/src/editor/ConfirmationDialog.tsx +103 -0
  13. package/src/editor/ContentTree.tsx +654 -0
  14. package/src/editor/ContextMenu.tsx +155 -0
  15. package/src/editor/Editor.tsx +91 -0
  16. package/src/editor/EditorWarning.tsx +34 -0
  17. package/src/editor/EditorWarnings.tsx +33 -0
  18. package/src/editor/FieldEditorPopup.tsx +65 -0
  19. package/src/editor/FieldHistory.tsx +74 -0
  20. package/src/editor/FieldList.tsx +190 -0
  21. package/src/editor/FieldListField.tsx +387 -0
  22. package/src/editor/FieldListFieldWithFallbacks.tsx +211 -0
  23. package/src/editor/FloatingToolbar.tsx +163 -0
  24. package/src/editor/ImageEditor.tsx +129 -0
  25. package/src/editor/InsertMenu.tsx +332 -0
  26. package/src/editor/ItemInfo.tsx +90 -0
  27. package/src/editor/LinkEditorDialog.tsx +192 -0
  28. package/src/editor/MainLayout.tsx +94 -0
  29. package/src/editor/NewEditorClient.tsx +11 -0
  30. package/src/editor/PictureCropper.tsx +505 -0
  31. package/src/editor/PictureEditor.tsx +206 -0
  32. package/src/editor/PictureEditorDialog.tsx +381 -0
  33. package/src/editor/PublishDialog.ignore +74 -0
  34. package/src/editor/ScrollingContentTree.tsx +47 -0
  35. package/src/editor/Terminal.tsx +215 -0
  36. package/src/editor/Titlebar.tsx +23 -0
  37. package/src/editor/ai/AiPopup.tsx +59 -0
  38. package/src/editor/ai/AiResponseMessage.tsx +82 -0
  39. package/src/editor/ai/AiTerminal.tsx +450 -0
  40. package/src/editor/ai/AiToolCall.tsx +46 -0
  41. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  42. package/src/editor/ai/editorAiContext.ts +18 -0
  43. package/src/editor/client/DialogContext.tsx +49 -0
  44. package/src/editor/client/EditorClient.tsx +1831 -0
  45. package/src/editor/client/GenericDialog.tsx +50 -0
  46. package/src/editor/client/editContext.ts +330 -0
  47. package/src/editor/client/helpers.ts +44 -0
  48. package/src/editor/client/itemsRepository.ts +391 -0
  49. package/src/editor/client/operations.ts +610 -0
  50. package/src/editor/client/pageModelBuilder.ts +182 -0
  51. package/src/editor/commands/commands.ts +23 -0
  52. package/src/editor/commands/componentCommands.tsx +408 -0
  53. package/src/editor/commands/createVersionCommand.ts +33 -0
  54. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  55. package/src/editor/commands/itemCommands.tsx +186 -0
  56. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  57. package/src/editor/commands/undo.ts +39 -0
  58. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  59. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  60. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  61. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  62. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  63. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  64. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  65. package/src/editor/component-designer/PlaceholdersEditor.tsx +183 -0
  66. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  67. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  68. package/src/editor/component-designer/aiContext.ts +23 -0
  69. package/src/editor/componentTreeHelper.tsx +114 -0
  70. package/src/editor/control-center/ControlCenterMenu.tsx +71 -0
  71. package/src/editor/control-center/IndexOverview.tsx +50 -0
  72. package/src/editor/control-center/IndexSettings.tsx +266 -0
  73. package/src/editor/control-center/Status.tsx +7 -0
  74. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  75. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  76. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  77. package/src/editor/editor-warnings/NoWriteAccess.tsx +15 -0
  78. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  79. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  80. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  81. package/src/editor/field-types/DropLinkEditor.tsx +75 -0
  82. package/src/editor/field-types/DropListEditor.tsx +84 -0
  83. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  84. package/src/editor/field-types/InternalLinkFieldEditor.tsx +112 -0
  85. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  86. package/src/editor/field-types/MultiLineText.tsx +63 -0
  87. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  88. package/src/editor/field-types/RawEditor.tsx +53 -0
  89. package/src/editor/field-types/ReactQuill.tsx +580 -0
  90. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  91. package/src/editor/field-types/RichTextEditorComponent.tsx +108 -0
  92. package/src/editor/field-types/SingleLineText.tsx +150 -0
  93. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  94. package/src/editor/fieldTypes.ts +140 -0
  95. package/src/editor/media-selector/AiImageSearch.tsx +186 -0
  96. package/src/editor/media-selector/AiImageSearchPrompt.tsx +95 -0
  97. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  98. package/src/editor/media-selector/Preview.tsx +14 -0
  99. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  100. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  101. package/src/editor/media-selector/UploadZone.tsx +137 -0
  102. package/src/editor/menubar/ActionsMenu.tsx +47 -0
  103. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  104. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  105. package/src/editor/menubar/BrowseHistory.tsx +37 -0
  106. package/src/editor/menubar/ItemLanguageVersion.tsx +52 -0
  107. package/src/editor/menubar/LanguageSelector.tsx +152 -0
  108. package/src/editor/menubar/Menu.tsx +83 -0
  109. package/src/editor/menubar/NavButtons.tsx +74 -0
  110. package/src/editor/menubar/PageSelector.tsx +139 -0
  111. package/src/editor/menubar/PageViewerControls.tsx +99 -0
  112. package/src/editor/menubar/Separator.tsx +12 -0
  113. package/src/editor/menubar/SiteInfo.tsx +53 -0
  114. package/src/editor/menubar/User.tsx +27 -0
  115. package/src/editor/menubar/VersionSelector.tsx +143 -0
  116. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +287 -0
  117. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  118. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +44 -0
  119. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  120. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  121. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  122. package/src/editor/page-editor-chrome/FrameMenu.tsx +263 -0
  123. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  124. package/src/editor/page-editor-chrome/InlineEditor.tsx +147 -0
  125. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  126. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  127. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +119 -0
  128. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +154 -0
  129. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +171 -0
  130. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +233 -0
  131. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  132. package/src/editor/page-viewer/EditorForm.tsx +247 -0
  133. package/src/editor/page-viewer/MiniMap.tsx +351 -0
  134. package/src/editor/page-viewer/PageViewer.tsx +127 -0
  135. package/src/editor/page-viewer/PageViewerFrame.tsx +1030 -0
  136. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  137. package/src/editor/pageModel.ts +191 -0
  138. package/src/editor/picture-shared.tsx +53 -0
  139. package/src/editor/reviews/Comment.tsx +265 -0
  140. package/src/editor/reviews/Comments.tsx +50 -0
  141. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  142. package/src/editor/reviews/Reviews.tsx +280 -0
  143. package/src/editor/reviews/reviewCommands.tsx +47 -0
  144. package/src/editor/reviews/useReviews.tsx +70 -0
  145. package/src/editor/services/aiService.ts +155 -0
  146. package/src/editor/services/componentDesignerService.ts +151 -0
  147. package/src/editor/services/contentService.ts +159 -0
  148. package/src/editor/services/editService.ts +462 -0
  149. package/src/editor/services/indexService.ts +24 -0
  150. package/src/editor/services/reviewsService.ts +45 -0
  151. package/src/editor/services/serviceHelper.ts +95 -0
  152. package/src/editor/services/systemService.ts +5 -0
  153. package/src/editor/services/translationService.ts +21 -0
  154. package/src/editor/services-server/api.ts +150 -0
  155. package/src/editor/services-server/graphQL.ts +106 -0
  156. package/src/editor/sidebar/ComponentPalette.tsx +146 -0
  157. package/src/editor/sidebar/ComponentTree.tsx +512 -0
  158. package/src/editor/sidebar/ComponentTree2.tsxx +490 -0
  159. package/src/editor/sidebar/Debug.tsx +105 -0
  160. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  161. package/src/editor/sidebar/EditHistory.tsx +134 -0
  162. package/src/editor/sidebar/GraphQL.tsx +164 -0
  163. package/src/editor/sidebar/Insert.tsx +35 -0
  164. package/src/editor/sidebar/MainContentTree.tsx +95 -0
  165. package/src/editor/sidebar/Performance.tsx +53 -0
  166. package/src/editor/sidebar/Sessions.tsx +35 -0
  167. package/src/editor/sidebar/Sidebar.tsx +20 -0
  168. package/src/editor/sidebar/SidebarView.tsx +150 -0
  169. package/src/editor/sidebar/Translations.tsx +276 -0
  170. package/src/editor/sidebar/Validation.tsx +102 -0
  171. package/src/editor/sidebar/ViewSelector.tsx +49 -0
  172. package/src/editor/sidebar/Workbox.tsx +209 -0
  173. package/src/editor/ui/CenteredMessage.tsx +7 -0
  174. package/src/editor/ui/CopyToClipboardButton.tsx +23 -0
  175. package/src/editor/ui/DialogButtons.tsx +11 -0
  176. package/src/editor/ui/Icons.tsx +585 -0
  177. package/src/editor/ui/ItemNameDialog.tsx +94 -0
  178. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  179. package/src/editor/ui/ItemSearch.tsx +173 -0
  180. package/src/editor/ui/PerfectTree.tsx +550 -0
  181. package/src/editor/ui/Section.tsx +35 -0
  182. package/src/editor/ui/SimpleIconButton.tsx +43 -0
  183. package/src/editor/ui/SimpleMenu.tsx +48 -0
  184. package/src/editor/ui/SimpleTable.tsx +63 -0
  185. package/src/editor/ui/SimpleTabs.tsx +55 -0
  186. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  187. package/src/editor/ui/Spinner.tsx +7 -0
  188. package/src/editor/ui/Splitter.tsx +247 -0
  189. package/src/editor/ui/StackedPanels.tsx +134 -0
  190. package/src/editor/ui/Toolbar.tsx +7 -0
  191. package/src/editor/utils/id-helper.ts +3 -0
  192. package/src/editor/utils/insertOptions.ts +69 -0
  193. package/src/editor/utils/itemutils.ts +29 -0
  194. package/src/editor/utils/useMemoDebug.ts +28 -0
  195. package/src/editor/utils.ts +435 -0
  196. package/src/editor/views/CompareView.tsx +256 -0
  197. package/src/editor/views/EditView.tsx +27 -0
  198. package/src/editor/views/ItemEditor.tsx +58 -0
  199. package/src/editor/views/SingleEditView.tsx +44 -0
  200. package/src/fonts/Geist-Black.woff2 +0 -0
  201. package/src/fonts/Geist-Bold.woff2 +0 -0
  202. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  203. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  204. package/src/fonts/Geist-Light.woff2 +0 -0
  205. package/src/fonts/Geist-Medium.woff2 +0 -0
  206. package/src/fonts/Geist-Regular.woff2 +0 -0
  207. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  208. package/src/fonts/Geist-Thin.woff2 +0 -0
  209. package/src/fonts/Geist[wght].woff2 +0 -0
  210. package/src/index.ts +7 -0
  211. package/src/page-wizard/PageWizard.tsx +163 -0
  212. package/src/page-wizard/SelectWizard.tsx +109 -0
  213. package/src/page-wizard/WizardSteps.tsx +207 -0
  214. package/src/page-wizard/service.ts +35 -0
  215. package/src/page-wizard/startPageWizardCommand.ts +27 -0
  216. package/src/page-wizard/steps/BuildPageStep.tsx +266 -0
  217. package/src/page-wizard/steps/CollectStep.tsx +233 -0
  218. package/src/page-wizard/steps/ComponentTypesSelector.tsx +443 -0
  219. package/src/page-wizard/steps/Components.tsx +193 -0
  220. package/src/page-wizard/steps/CreatePage.tsx +285 -0
  221. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +384 -0
  222. package/src/page-wizard/steps/EditButton.tsx +34 -0
  223. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  224. package/src/page-wizard/steps/Generate.tsx +32 -0
  225. package/src/page-wizard/steps/ImagesStep.tsx +318 -0
  226. package/src/page-wizard/steps/LayoutStep.tsx +228 -0
  227. package/src/page-wizard/steps/SelectStep.tsx +256 -0
  228. package/src/page-wizard/steps/schema.ts +180 -0
  229. package/src/page-wizard/steps/usePageCreator.ts +279 -0
  230. package/src/splash-screen/NewPage.tsx +232 -0
  231. package/src/splash-screen/SectionHeadline.tsx +21 -0
  232. package/src/splash-screen/SplashScreen.tsx +156 -0
  233. package/src/tour/Tour.tsx +558 -0
  234. package/src/tour/default-tour.tsx +300 -0
  235. package/src/tour/preview-tour.tsx +127 -0
  236. package/src/types.ts +302 -0
  237. package/styles.css +476 -0
  238. package/tsconfig.build.json +21 -0
  239. package/tsconfig.json +11 -0
@@ -0,0 +1,71 @@
1
+ import { deleteVersion } from "../services/editService";
2
+ import { getItemDescriptor } from "../utils";
3
+ import { Command, CommandContext } from "./commands";
4
+
5
+ export function getDeleteVersionCommand({}: {
6
+ language?: string;
7
+ }): Command<CommandContext<any>> {
8
+ return {
9
+ id: "deleteVersion",
10
+ execute: (context) => execute(context),
11
+ label: "Delete Version",
12
+ icon: "pi pi-fw pi-times",
13
+ disabled: (context: CommandContext<any>) =>
14
+ !context.editContext.item?.canLock ||
15
+ context.editContext.item.version === 0 ||
16
+ context.editContext.itemVersions.length === 0,
17
+ };
18
+ }
19
+
20
+ async function execute(context: CommandContext<any>): Promise<void> {
21
+ if (!context.editContext.item) return;
22
+ const descriptor = getItemDescriptor(context.editContext.item);
23
+
24
+ let message =
25
+ "Are you sure you want to delete version " +
26
+ context.editContext.item.version +
27
+ "?";
28
+
29
+ const sessions = context.editContext.activeSessions.filter(
30
+ (x) =>
31
+ ((x.item?.id === descriptor.id &&
32
+ x.item?.language === descriptor.language &&
33
+ x.item?.version === descriptor.version) ||
34
+ (x.page?.id === descriptor.id &&
35
+ x.page?.language === descriptor.language &&
36
+ x.page?.version === descriptor.version)) &&
37
+ x.sessionId !== context.editContext.sessionId
38
+ );
39
+
40
+ if (sessions.length > 0) {
41
+ context.editContext.showMessageDialog({
42
+ header: "Delete Version",
43
+ message:
44
+ "This version is currently locked by " +
45
+ sessions.map((x) => x.user.name).join(", ") +
46
+ ". You cannot delete it.",
47
+ });
48
+ return;
49
+ }
50
+
51
+ if (
52
+ context.editContext.itemVersions[
53
+ context.editContext.itemVersions.length - 1
54
+ ]?.version === context.editContext.item.version
55
+ ) {
56
+ message += " This is the latest version!";
57
+ }
58
+
59
+ context.editContext.confirm?.({
60
+ header: "Delete Version",
61
+ message,
62
+ showCancel: true,
63
+ accept: async () => {
64
+ await deleteVersion(descriptor, context.editContext.sessionId);
65
+ context.editContext.loadItem({
66
+ ...descriptor,
67
+ version: 0,
68
+ });
69
+ },
70
+ });
71
+ }
@@ -0,0 +1,186 @@
1
+ import { Command, CommandContext, CommandData } from "./commands";
2
+ import { FullItem } from "../pageModel";
3
+
4
+ import { ItemNameDialog, ItemNameDialogProps } from "../ui/ItemNameDialogNew";
5
+
6
+ export type ItemCommandData = CommandData & {
7
+ items: FullItem[];
8
+ };
9
+
10
+ export type ItemCommandContext = CommandContext<ItemCommandData>;
11
+
12
+ export type ItemCommand = Command<ItemCommandData>;
13
+
14
+ export const deleteItemCommand: ItemCommand = {
15
+ id: "deleteItem",
16
+ label: "Delete",
17
+ icon: "pi pi-times",
18
+ disabled: (context: ItemCommandContext) =>
19
+ !context.data?.items || !context.data?.items[0]?.canLock || false,
20
+ execute: async (context: ItemCommandContext) => {
21
+ const items = context.data?.items;
22
+
23
+ if (!items || items.length === 0) return;
24
+ const confirmed = await new Promise<boolean>((resolve) => {
25
+ context.editContext.confirm({
26
+ message: (
27
+ <div>
28
+ Are you sure you want to delete{" "}
29
+ <em>{items.map((x) => x.name).join(", ")}</em>? This cannot be
30
+ undone.
31
+ </div>
32
+ ),
33
+ header: "Confirmation",
34
+ icon: "pi pi-exclamation-triangle",
35
+ accept: () => resolve(true),
36
+ reject: () => resolve(false),
37
+ showCancel: true,
38
+ });
39
+ });
40
+
41
+ if (confirmed) {
42
+ if (
43
+ items.some(
44
+ (x) => context.editContext.contentEditorItem?.descriptor.id === x.id,
45
+ )
46
+ ) {
47
+ const firstItem = items[0];
48
+ if (firstItem) {
49
+ context.editContext.loadItem({
50
+ id: firstItem.parentId,
51
+ language: context.editContext.contentEditorItem!.language,
52
+ version: 0,
53
+ });
54
+ }
55
+ }
56
+
57
+ await context.editContext.operations.deleteItems(
58
+ items.map((x) => x.descriptor),
59
+ );
60
+
61
+ return true;
62
+ }
63
+ return false;
64
+ },
65
+ };
66
+ export const renameItemCommand: ItemCommand = {
67
+ id: "renameItem",
68
+ label: "Rename",
69
+ icon: "pi pi-pencil",
70
+ keyBinding: "F2",
71
+ disabled: (context: ItemCommandContext) =>
72
+ !context.data?.items ||
73
+ context.data.items.length !== 1 ||
74
+ !context.data.items[0]?.canLock ||
75
+ false,
76
+ execute: async (context: ItemCommandContext) => {
77
+ const item = context.data?.items[0];
78
+
79
+ if (!item) return;
80
+ const newName = await context.openDialog<string, ItemNameDialogProps>(
81
+ ItemNameDialog,
82
+ {
83
+ name: item.name,
84
+ title: "Rename Item",
85
+ message: "Enter a new name for the item:",
86
+ parentItem: { ...item.descriptor, id: item.parentId },
87
+ itemId: item.id,
88
+ },
89
+ );
90
+ if (newName) {
91
+ await context.editContext.operations.renameItem(item.descriptor, newName);
92
+ }
93
+ },
94
+ };
95
+
96
+ type InsertItemCommandData = ItemCommandData & {
97
+ templateId: string;
98
+ };
99
+
100
+ export type InsertItemCommandContext = CommandContext<InsertItemCommandData>;
101
+
102
+ export type InsertItemCommand = Command<InsertItemCommandData>;
103
+
104
+ export const insertItemCommand: ItemCommand = {
105
+ id: "insertItem",
106
+ label: "Insert Item",
107
+ icon: "pi pi-plus",
108
+ disabled: (context: ItemCommandContext) =>
109
+ !context.data?.items || context.data.items.length !== 1 || false,
110
+ execute: async (context: InsertItemCommandContext) => {
111
+ const parentItem = context.data?.items[0];
112
+ const templateId = context.data?.templateId;
113
+
114
+ if (!parentItem || !templateId) return;
115
+
116
+ const templateItem = await context.editContext.itemsRepository.getItem({
117
+ id: templateId,
118
+ language: "en",
119
+ version: 0,
120
+ });
121
+ const templateName = templateItem?.name;
122
+
123
+ const name = await context.openDialog<string, ItemNameDialogProps>(
124
+ ItemNameDialog,
125
+ {
126
+ title: "Create Item",
127
+ message: "Enter a name for the new item:",
128
+ name: templateName,
129
+ parentItem: parentItem.descriptor,
130
+ },
131
+ );
132
+ if (name) {
133
+ const result = await context.editContext.operations.createItem(
134
+ parentItem.descriptor,
135
+ templateId,
136
+ name,
137
+ );
138
+
139
+ if (result) {
140
+ context.editContext.loadItem(result);
141
+ }
142
+
143
+ return result;
144
+ }
145
+ },
146
+ };
147
+
148
+ // export const localizeItemCommand: ItemCommand = {
149
+ // id: "localizeItem",
150
+ // label: "Localize",
151
+ // icon: "pi pi-globe",
152
+ // disabled: (context: ItemCommandContext) =>
153
+ // !context.data?.items || context.data.items.length === 0 || false,
154
+ // execute: async (context: ItemCommandContext) => {
155
+ // const items = context.data?.items;
156
+ // if (!items?.length) return;
157
+
158
+ // await context.openDialog<string, LocalizeItemDialogProps>(
159
+ // LocalizeItemDialog,
160
+ // {
161
+ // items,
162
+ // editContext: context.editContext,
163
+ // }
164
+ // );
165
+ // if (context.editContext.contentEditorItem?.descriptor)
166
+ // context.editContext.loadItem(
167
+ // context.editContext.contentEditorItem.descriptor
168
+ // );
169
+ // },
170
+ // };
171
+
172
+ export const publishItemCommand: ItemCommand = {
173
+ id: "publishItem",
174
+ label: "Publish",
175
+ icon: "pi pi-cloud-upload",
176
+ disabled: (context: ItemCommandContext) =>
177
+ !context.data?.items ||
178
+ context.data.items.length !== 1 ||
179
+ !context.data.items[0]?.canLock ||
180
+ false,
181
+ execute: async (context: ItemCommandContext) => {
182
+ const item = context.data?.items[0];
183
+ if (!item) return;
184
+ await context.editContext.switchView("publish");
185
+ },
186
+ };
@@ -0,0 +1,201 @@
1
+ import { Button } from "primereact/button";
2
+ import { Dialog } from "primereact/dialog";
3
+ import { useEffect, useState } from "react";
4
+ import DialogButtons from "../../ui/DialogButtons";
5
+ import { DialogProps } from "../../client/DialogContext";
6
+ import { getLanguagesAndVersions } from "../../services/contentService";
7
+ import { FullItem, ItemDescriptor } from "../../pageModel";
8
+ import { LanguageVersions } from "../../../types";
9
+ import { Checkbox } from "primereact/checkbox";
10
+
11
+ import { EditContextType } from "../../client/editContext";
12
+ import { Spinner } from "../../ui/Spinner";
13
+
14
+ export type LocalizeItemDialogProps = {
15
+ items: FullItem[];
16
+ editContext: EditContextType;
17
+ };
18
+
19
+ type LanguageData = {
20
+ name: string;
21
+ items: ItemDescriptor[];
22
+ };
23
+
24
+ export function LocalizeItemDialog(
25
+ props: LocalizeItemDialogProps & DialogProps<string>
26
+ ) {
27
+ const [languageSelection, setLanguageSelection] = useState<
28
+ Record<string, boolean>
29
+ >({});
30
+ const [languageData, setLanguageData] = useState<Map<string, LanguageData>>();
31
+
32
+ const [isExecuting, setIsExecuting] = useState(false);
33
+ const [currentTask, setCurrentTask] = useState<string>();
34
+ const [completedTasks, setCompletedTasks] = useState<string[]>([]);
35
+ const [finished, setFinished] = useState(false);
36
+
37
+ useEffect(() => {
38
+ const itemDescriptors = props.items.map((x) => x.descriptor);
39
+
40
+ const loadLanguages = async () => {
41
+ const itemLanguages = await Promise.all(
42
+ itemDescriptors.map(async (item: ItemDescriptor) => {
43
+ const result = await getLanguagesAndVersions(item);
44
+ return result.data?.languages || [];
45
+ })
46
+ );
47
+ const languageMap = new Map<string, LanguageData>();
48
+ itemLanguages.forEach((languages, index) => {
49
+ languages.forEach((language: LanguageVersions) => {
50
+ const languageData: LanguageData = languageMap.get(language.name) || {
51
+ name: language.name,
52
+ items: [],
53
+ };
54
+ if (language.versions > 0)
55
+ languageData.items.push(itemDescriptors[index]);
56
+ languageMap.set(language.languageCode, languageData);
57
+ });
58
+ });
59
+ setLanguageData(languageMap);
60
+ };
61
+ loadLanguages();
62
+ }, [props]);
63
+
64
+ async function startLocalization() {
65
+ setIsExecuting(true);
66
+ const completedTasks: string[] = [];
67
+ for (const item of props.items) {
68
+ for (const language of Object.keys(languageSelection)) {
69
+ if (!languageSelection[language]) continue;
70
+ if (
71
+ languageData
72
+ ?.get(language)
73
+ ?.items.find((x) => x.id === item.descriptor.id)
74
+ )
75
+ continue;
76
+ setCurrentTask(`${item.name} - ${language}`);
77
+ await props.editContext.operations.createVersion({
78
+ id: item.descriptor.id,
79
+ language: language,
80
+ version: 0,
81
+ });
82
+ completedTasks.push(`${item.name} - ${language}`);
83
+ }
84
+ }
85
+ setFinished(true);
86
+ setCompletedTasks(completedTasks);
87
+ }
88
+
89
+ const allLanguages = Array.from(languageData?.keys() || []).map((x) => ({
90
+ code: x,
91
+ name: languageData?.get(x)?.name || "",
92
+ }));
93
+
94
+ allLanguages.sort((a, b) => a.name.localeCompare(b.name));
95
+
96
+ const dialogContent = getContent();
97
+
98
+ function getContent() {
99
+ if (finished)
100
+ return (
101
+ <div className="flex gap-1 flex-col justify h-full">
102
+ {completedTasks.length > 0 && (
103
+ <>
104
+ <div className="text-lg font-bold pt-3 px-3">
105
+ Finished. Versions created:
106
+ </div>
107
+ <div className="flex-1 relative">
108
+ <div className="absolute inset-3 flex flex-col gap-3 overflow-y-auto">
109
+ {completedTasks.map((x, index) => (
110
+ <div key={index}>{x}</div>
111
+ ))}
112
+ </div>
113
+ </div>
114
+ </>
115
+ )}
116
+ {completedTasks.length === 0 && (
117
+ <div className="flex items-center justify-center h-full">
118
+ All items are already localized.
119
+ </div>
120
+ )}
121
+ <DialogButtons>
122
+ <Button onClick={() => props.onClose?.(null)} label="Close" />
123
+ </DialogButtons>
124
+ </div>
125
+ );
126
+
127
+ if (isExecuting)
128
+ return (
129
+ <div className="flex items-center justify-center gap-3 h-full">
130
+ <Spinner /> Creating version {currentTask}
131
+ </div>
132
+ );
133
+
134
+ return (
135
+ <div className="flex gap-1 flex-col justify h-full text-sm ">
136
+ <div className="p-3 flex flex-col gap-2 h-full">
137
+ <div className="my-2">
138
+ Select languages to localize{" "}
139
+ {props.items.map((x) => x.name).join(", ")}:
140
+ </div>
141
+ <div className="flex items-center gap-2 my-1">
142
+ <Checkbox
143
+ checked={allLanguages.every(
144
+ (language) => languageSelection[language.code]
145
+ )}
146
+ onChange={(e) => {
147
+ setLanguageSelection(
148
+ Object.fromEntries(
149
+ Array.from(languageData?.keys() || []).map((language) => [
150
+ language,
151
+ e.checked || false,
152
+ ])
153
+ )
154
+ );
155
+ }}
156
+ />{" "}
157
+ All
158
+ </div>
159
+ <div className="relative flex-1">
160
+ <div className="absolute top-0 left-0 w-full h-full overflow-y-auto">
161
+ {allLanguages.map((language) => (
162
+ <div
163
+ key={language.code}
164
+ className="flex items-center gap-2 my-2"
165
+ >
166
+ <Checkbox
167
+ checked={languageSelection[language.code] || false}
168
+ onChange={(e) => {
169
+ setLanguageSelection((prev) => ({
170
+ ...prev,
171
+ [language.code]: e.checked || false,
172
+ }));
173
+ }}
174
+ />
175
+ <div>{language.name}</div>
176
+ </div>
177
+ ))}
178
+ </div>
179
+ </div>
180
+ </div>
181
+ <DialogButtons>
182
+ <Button onClick={startLocalization} label="Start" />
183
+ <Button onClick={() => props.onClose?.(null)} label="Cancel" />
184
+ </DialogButtons>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ return (
190
+ <Dialog
191
+ visible={true}
192
+ onHide={() => {
193
+ props.onClose?.(null);
194
+ }}
195
+ style={{ width: "75vw", height: "75vh" }}
196
+ header="Localize"
197
+ >
198
+ {dialogContent}
199
+ </Dialog>
200
+ );
201
+ }
@@ -0,0 +1,39 @@
1
+ import { CommandContext } from "./commands";
2
+
3
+ export function getUndoCommand() {
4
+ return {
5
+ id: "undo",
6
+ label: "Undo",
7
+ icon: "pi pi-undo",
8
+ execute: async (context: CommandContext<any>) => {
9
+ context.editContext.operations.undo();
10
+ },
11
+ disabled: (context: CommandContext<any>) => {
12
+ return (
13
+ context.editContext.editHistory.length === 0 ||
14
+ !context.editContext.editHistory.find(
15
+ (x) => x.sessionId === context.editContext.sessionId && x.canUndo
16
+ )
17
+ );
18
+ },
19
+ };
20
+ }
21
+
22
+ export function getRedoCommand() {
23
+ return {
24
+ id: "redo",
25
+ label: "Redo",
26
+ icon: "pi pi-refresh",
27
+ execute: async (context: CommandContext<any>) => {
28
+ context.editContext.operations.redo();
29
+ },
30
+ disabled: (context: CommandContext<any>) => {
31
+ return (
32
+ context.editContext.editHistory.length === 0 ||
33
+ !context.editContext.editHistory.find(
34
+ (x) => x.sessionId === context.editContext.sessionId && x.canRedo
35
+ )
36
+ );
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,70 @@
1
+ import { ComponentRenderingEditor } from "./ComponentRenderingEditor";
2
+ import { Splitter, SplitterPanel } from "primereact/splitter";
3
+ import { StackedPanels } from "../ui/StackedPanels";
4
+ import { Panel } from "../../config/types";
5
+
6
+ import { PlaceholdersEditor } from "./PlaceholdersEditor";
7
+ import { PuzzleIcon } from "../ui/Icons";
8
+ import { ComponentEditor } from "./ComponentEditor";
9
+ import { useEffect, useState } from "react";
10
+ import { useEditContext } from "../client/editContext";
11
+ import {
12
+ Component,
13
+ getAllComponents,
14
+ } from "../services/componentDesignerService";
15
+ import { PageViewerFrame } from "../page-viewer/PageViewerFrame";
16
+
17
+ export function ComponentDesigner() {
18
+ const [allComponents, setAllComponents] = useState<Component[]>([]);
19
+ const context = useEditContext();
20
+ if (!context?.page) return null;
21
+
22
+ useEffect(() => {
23
+ async function loadComponents() {
24
+ if (context?.page) {
25
+ const components = await getAllComponents(context.page.item.id);
26
+ setAllComponents(components);
27
+ } else setAllComponents([]);
28
+ }
29
+
30
+ loadComponents();
31
+ }, [context.page]);
32
+
33
+ const mainPanels: Panel[] = [
34
+ {
35
+ name: "template",
36
+ title: "Component",
37
+ initialSize: 30,
38
+ icon: <PuzzleIcon />,
39
+ noOverflow: true,
40
+ content: <ComponentEditor allComponents={allComponents} />,
41
+ },
42
+ {
43
+ name: "placeholders",
44
+ title: "Placeholders",
45
+ icon: "pi pi-th-large",
46
+ initialSize: 30,
47
+ noOverflow: true,
48
+ content: <PlaceholdersEditor allComponents={allComponents} />,
49
+ },
50
+ {
51
+ name: "rendering",
52
+ title: "Rendering",
53
+ icon: "pi pi-palette",
54
+ initialSize: 40,
55
+ content: <ComponentRenderingEditor />,
56
+ noOverflow: true,
57
+ },
58
+ ];
59
+
60
+ return (
61
+ <Splitter className="h-full">
62
+ <SplitterPanel>
63
+ <StackedPanels panels={mainPanels} />
64
+ </SplitterPanel>
65
+ <SplitterPanel>
66
+ <PageViewerFrame mode="view" />
67
+ </SplitterPanel>
68
+ </Splitter>
69
+ );
70
+ }
@@ -0,0 +1,11 @@
1
+ import { AiTerminal } from "../ai/AiTerminal";
2
+ import { createDesignerAiContext } from "./aiContext";
3
+
4
+ export function ComponentDesignerAiTerminal() {
5
+ return (
6
+ <AiTerminal
7
+ createAiContext={createDesignerAiContext}
8
+ defaultProfile="Component Designer"
9
+ />
10
+ );
11
+ }
@@ -0,0 +1,91 @@
1
+ import { Menubar } from "primereact/menubar";
2
+
3
+ import {
4
+ createNewComponent,
5
+ createRendering,
6
+ loadComponentDetails,
7
+ } from "../services/componentDesignerService";
8
+ import { ItemNameDialogHandle, ItemNameDialog } from "../ui/ItemNameDialog";
9
+ import { useRef } from "react";
10
+ import { useEditContext } from "../client/editContext";
11
+ import { ItemLanguageVersion } from "../menubar/ItemLanguageVersion";
12
+
13
+ export function ComponentDesignerMenu() {
14
+ const context = useEditContext();
15
+ if (!context) return;
16
+
17
+ const menuItems = [
18
+ {
19
+ id: "newComponent",
20
+ label: "New Component",
21
+ icon: "pi pi-fw pi-plus",
22
+ command: () => createNew(),
23
+ // disabled: !context.componentRenderingFolder,
24
+ },
25
+ {
26
+ id: "newRendering",
27
+ label: "New Rendering",
28
+ icon: "pi pi-fw pi-palette",
29
+ command: () => createNewRendering(),
30
+ disabled: !context.componentDesignerComponent,
31
+ },
32
+ {
33
+ id: "delete",
34
+ label: "Delete rendering",
35
+ icon: "pi pi-fw pi-trash",
36
+ disabled: !context.componentDesignerRendering,
37
+ },
38
+ ];
39
+ const itemNameDialogRef = useRef<ItemNameDialogHandle>(null);
40
+
41
+ const createNew = async () => {
42
+ itemNameDialogRef.current?.show({
43
+ title: "Choose Component Name",
44
+ accept: async (name) => {
45
+ await createNewComponent(name, context.page!.item);
46
+ },
47
+ });
48
+ };
49
+
50
+ const createNewRendering = async () => {
51
+ itemNameDialogRef.current?.show({
52
+ title: "Choose Rendering Name",
53
+ accept: async (name) => {
54
+ if (!context.componentDesignerComponent) return;
55
+ const details = await createRendering(
56
+ name,
57
+ context.page!.item,
58
+ context.componentDesignerComponent.templateId
59
+ );
60
+ if (!("id" in details)) return;
61
+
62
+ const component = await loadComponentDetails(
63
+ context.componentDesignerComponent.templateId,
64
+ context!.page!.item.id
65
+ );
66
+ // const rendering = await getItem({
67
+ // id: details.id,
68
+ // language: "en",
69
+ // version: 0,
70
+ // });
71
+ context.setComponentDesignerComponent(component);
72
+ // context.setComponentDesignerRendering(rendering);
73
+ },
74
+ });
75
+ };
76
+
77
+ return (
78
+ <>
79
+ <Menubar
80
+ model={menuItems}
81
+ end={
82
+ <div className="flex items-center space-x-2">
83
+ <ItemLanguageVersion />
84
+ </div>
85
+ }
86
+ className="p-1 text-sm rounded-none border-0 border-b bg-gray-50"
87
+ ></Menubar>
88
+ <ItemNameDialog ref={itemNameDialogRef} />
89
+ </>
90
+ );
91
+ }