@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,137 @@
1
+ import { classNames } from "primereact/utils";
2
+ import { useCallback, useRef } from "react";
3
+
4
+ import { useState } from "react";
5
+ import { Spinner } from "../ui/Spinner";
6
+
7
+ export function UploadZone({
8
+ selectedFolderId,
9
+ uploadCompleted,
10
+ }: {
11
+ selectedFolderId: string | undefined;
12
+ uploadCompleted: () => void;
13
+ }) {
14
+ const [isDragging, setIsDragging] = useState(false);
15
+ const [isUploading, setIsUploading] = useState(false);
16
+ const dragZoneRef = useRef<HTMLDivElement>(null);
17
+ const handleDragOver = useCallback((e: React.DragEvent) => {
18
+ e.preventDefault();
19
+ e.stopPropagation();
20
+ }, []);
21
+
22
+ const handleDragEnter = useCallback((e: React.DragEvent) => {
23
+ e.preventDefault();
24
+ e.stopPropagation();
25
+
26
+ if (dragZoneRef.current === e.target) {
27
+ setIsDragging(true);
28
+ }
29
+ }, []);
30
+
31
+ const handleDragLeave = useCallback((e: React.DragEvent) => {
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+
35
+ if (dragZoneRef.current?.contains(e.target as Node)) {
36
+ return;
37
+ }
38
+
39
+ setIsDragging(false);
40
+ }, []);
41
+
42
+ const handleDrop = useCallback(
43
+ (e: React.DragEvent) => {
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ setIsDragging(false);
47
+
48
+ const files = Array.from(e.dataTransfer.files);
49
+ if (files.length > 0) {
50
+ onFilesDropped(files);
51
+ }
52
+ },
53
+ [selectedFolderId]
54
+ );
55
+
56
+ const onFilesDropped = useCallback(
57
+ async (files: File[]) => {
58
+ const formData = new FormData();
59
+ files.forEach((file, index) => {
60
+ formData.append("file" + index, file);
61
+ });
62
+
63
+ try {
64
+ setIsUploading(true);
65
+ const response = await fetch(
66
+ "/alpaca/editor/upload?folder=" + selectedFolderId,
67
+ {
68
+ method: "POST",
69
+ body: formData,
70
+ }
71
+ );
72
+
73
+ setIsUploading(false);
74
+ if (!response.ok) {
75
+ throw new Error("Upload failed");
76
+ }
77
+
78
+ uploadCompleted();
79
+ } catch (error) {
80
+ console.error("Error uploading files:", error);
81
+ }
82
+ },
83
+ [selectedFolderId]
84
+ );
85
+
86
+ if (isUploading) {
87
+ return (
88
+ <div className="p-4 border-2 border-dashed rounded-lg items-center justify-center gap-2 text-gray-500 text-sm flex ">
89
+ <Spinner size="2xl" /> Uploading...
90
+ </div>
91
+ );
92
+ }
93
+
94
+ return (
95
+ <>
96
+ <div
97
+ ref={dragZoneRef}
98
+ className={classNames(
99
+ "p-4 border-2 border-dashed rounded-lg flex flex-col items-center justify-center gap-2",
100
+ isDragging
101
+ ? "border-blue-400 bg-blue-50"
102
+ : "border-gray-300 hover:border-blue-300 hover:bg-gray-50"
103
+ )}
104
+ onDragOverCapture={handleDragOver}
105
+ onDragEnterCapture={handleDragEnter}
106
+ onDragLeaveCapture={handleDragLeave}
107
+ onDropCapture={handleDrop}
108
+ onClick={() => {
109
+ const fileInput = document.createElement("input");
110
+ fileInput.type = "file";
111
+ fileInput.multiple = true;
112
+ fileInput.accept = "image/*,video/*";
113
+ fileInput.onchange = (e) => {
114
+ const files = Array.from(
115
+ (e.target as HTMLInputElement).files || []
116
+ );
117
+ if (files.length > 0) {
118
+ onFilesDropped(files);
119
+ }
120
+ };
121
+ fileInput.click();
122
+ }}
123
+ >
124
+ <div className="flex items-center gap-2">
125
+ <i className="pi pi-upload text-gray-400" />
126
+ <span className="text-gray-600">
127
+ {isDragging ? "Drop files here" : "Drag and drop files here"}
128
+ </span>
129
+ </div>
130
+ </div>
131
+
132
+ {isDragging && (
133
+ <div className="absolute inset-0 border-2 border-dashed border-blue-400 pointer-events-none" />
134
+ )}
135
+ </>
136
+ );
137
+ }
@@ -0,0 +1,47 @@
1
+ import { useEditContext } from "../client/editContext";
2
+ import { useRef } from "react";
3
+ import { OverlayPanel } from "primereact/overlaypanel";
4
+
5
+ import { Menu } from "primereact/menu";
6
+
7
+ export function ActionsMenu() {
8
+ const overlaypanel = useRef<OverlayPanel>(null);
9
+ const editContext = useEditContext();
10
+ if (!editContext) return null;
11
+
12
+ const items =
13
+ editContext.configuration.editor.actionsMenu?.itemsFactory(editContext);
14
+
15
+ // Enhance each item to call overlaypanel.hide() after executing its original command
16
+ const enhancedItems = items?.map((item) => {
17
+ // Preserve the original command if present
18
+ const originalCommand = item.command;
19
+ return {
20
+ ...item,
21
+ command: (e: any) => {
22
+ if (originalCommand) {
23
+ originalCommand(e);
24
+ }
25
+ // Close the overlay panel
26
+ overlaypanel.current?.hide();
27
+ },
28
+ };
29
+ });
30
+
31
+ return (
32
+ <>
33
+ <div
34
+ id="actions-menu"
35
+ className="flex gap-2 items-center bg-blue-500 text-white px-4 h-12 hover:bg-blue-600 cursor-pointer ml-2"
36
+ onClick={async (ev) => {
37
+ overlaypanel.current!.toggle(ev);
38
+ }}
39
+ >
40
+ <i className="pi pi-sort-down-fill"></i>
41
+ </div>
42
+ <OverlayPanel dismissable={true} ref={overlaypanel} closeOnEscape>
43
+ <Menu model={enhancedItems} />
44
+ </OverlayPanel>
45
+ </>
46
+ );
47
+ }
@@ -0,0 +1,17 @@
1
+ import { useEditContext } from "../client/editContext";
2
+ import { User } from "./User";
3
+
4
+ export function ActiveUsers() {
5
+ const editContext = useEditContext();
6
+ const mySession = editContext?.activeSessions.find(
7
+ (x) => x.sessionId === editContext?.sessionId
8
+ );
9
+
10
+ return (
11
+ <div className="border-gray-200 flex gap-2 items-center">
12
+ {editContext?.activeSessions
13
+ .filter((x) => x.item?.id === mySession?.item?.id)
14
+ .map((s) => <User key={s.sessionId} session={s} />)}
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,18 @@
1
+ import { useEditContext } from "../client/editContext";
2
+
3
+ export function ApproveAndPublishButton() {
4
+ const editContext = useEditContext();
5
+ return (
6
+ <div
7
+ className="bg-alpaca-blue hover:bg-alpaca-blue-light text-white p-4 cursor-pointer text-sm rounded-l-full"
8
+ onClick={() => {
9
+ const workboxView = editContext?.configuration.editor.views.find(
10
+ (x) => x.name == "workbox"
11
+ );
12
+ if (workboxView) editContext?.switchView(workboxView.name);
13
+ }}
14
+ >
15
+ Approve & Publish
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,37 @@
1
+ import { useEditContext } from "../client/editContext";
2
+
3
+ import { ItemDescriptor } from "../pageModel";
4
+ import { ResultItem } from "../ui/ItemSearch";
5
+
6
+ export function BrowseHistory({
7
+ setHoveredItem,
8
+ itemSelected,
9
+ }: {
10
+ setHoveredItem: (item: ResultItem | undefined) => void;
11
+ itemSelected: (item: ItemDescriptor) => void;
12
+ }) {
13
+ const editContext = useEditContext();
14
+
15
+ if (!editContext) return;
16
+
17
+ const history = editContext.browseHistory;
18
+
19
+ return (
20
+ <div className="overflow-y-auto flex flex-col gap-1 flex-1">
21
+ {history
22
+ .filter((x, i) => x.language && i < 7)
23
+ .map((x) => (
24
+ <div
25
+ key={x.id + x.language}
26
+ className="text-xs cursor-pointer hover:text-gray-400 flex gap-1"
27
+ onMouseEnter={() => setHoveredItem(x)}
28
+ onMouseLeave={() => setHoveredItem(undefined)}
29
+ onClick={() => itemSelected(x)}
30
+ >
31
+ {x.icon && <img src={x.icon} width="16" height="16" />} {x.path}{" "}
32
+ <span className="text-500">({x.language})</span>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1,52 @@
1
+ import { useEditContext } from "../client/editContext";
2
+ //import { BrowseHistory } from "./BrowseHistory";
3
+ import { LanguageSelector } from "./LanguageSelector";
4
+ import { PageSelector } from "./PageSelector";
5
+ import { Separator } from "./Separator";
6
+ import { VersionSelector } from "./VersionSelector";
7
+ import { NavButtons } from "./NavButtons";
8
+ import { getItemDescriptor } from "../utils";
9
+ import { confirmCreateVersion } from "../utils/itemutils";
10
+
11
+ export function ItemLanguageVersion() {
12
+ const editContext = useEditContext();
13
+
14
+ if (!editContext) return;
15
+
16
+ const item = editContext.currentItemDescriptor;
17
+ if (!item) return;
18
+
19
+ return (
20
+ <div className="flex gap-1 items-center">
21
+ <PageSelector itemDescriptor={item} />
22
+ <Separator />
23
+ <LanguageSelector
24
+ showAllLanguagesSwitch={true}
25
+ selectedLanguage={item.language}
26
+ onLanguageSelected={(language) => {
27
+ if (!language.versions) {
28
+ if (editContext) {
29
+ confirmCreateVersion(editContext, language);
30
+ }
31
+ } else
32
+ editContext?.loadItem({
33
+ ...getItemDescriptor(item),
34
+ language: language.languageCode,
35
+ version: 0,
36
+ });
37
+ }}
38
+ />
39
+ <Separator />
40
+ <VersionSelector
41
+ itemDescriptor={item}
42
+ versions={editContext.itemVersions}
43
+ actualVersion={editContext.contentEditorItem?.version}
44
+ onVersionSelected={(version) => {
45
+ editContext.loadItem({ ...item, version });
46
+ }}
47
+ />
48
+ <Separator />
49
+ <NavButtons />
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,152 @@
1
+ "use client";
2
+
3
+ import { ArrowDownIcon } from "../ui/Icons";
4
+ import { OverlayPanel } from "primereact/overlaypanel";
5
+ import { useRef, useState } from "react";
6
+ import { InputSwitch } from "primereact/inputswitch";
7
+ import { useEditContext } from "../client/editContext";
8
+
9
+ import { Language } from "../pageModel";
10
+
11
+ export function LanguageSelector({
12
+ selectedLanguage,
13
+ darkMode,
14
+ onLanguageSelected,
15
+ showAllLanguagesSwitch,
16
+ showAllLanguages = false,
17
+ disabled = false,
18
+ }: {
19
+ selectedLanguage: string | undefined;
20
+ darkMode?: boolean;
21
+ onLanguageSelected: (language: Language) => void;
22
+ showAllLanguagesSwitch?: boolean;
23
+ showAllLanguages?: boolean;
24
+ disabled?: boolean;
25
+ }) {
26
+ const overlaypanel = useRef<OverlayPanel>(null);
27
+ const [showAllLanguagesInternal, setShowAllLanguagesInternal] =
28
+ useState(showAllLanguages);
29
+ const editContext = useEditContext();
30
+
31
+ const allLanguages = editContext?.itemLanguages || [];
32
+
33
+ const currentLanguage = allLanguages.find(
34
+ (x) => x.languageCode === selectedLanguage
35
+ );
36
+
37
+ const languages = showAllLanguagesInternal
38
+ ? allLanguages
39
+ : allLanguages.filter((x: Language) => x.versions > 0);
40
+
41
+ languages.sort((a, b) => a.name.localeCompare(b.name));
42
+
43
+ const selectLanguage = async (language: Language) => {
44
+ onLanguageSelected(language);
45
+ overlaypanel.current?.hide();
46
+ return;
47
+ };
48
+
49
+ return (
50
+ <>
51
+ <div
52
+ className={`p-[7px] text-sm cursor-pointer flex items-center gap-3 ${
53
+ darkMode
54
+ ? "text-gray-500 hover:bg-gray-300"
55
+ : "text-gray-200 hover:bg-gray-500"
56
+ } rounded-md ${disabled ? "opacity-50" : ""}`}
57
+ onClick={(ev) => {
58
+ if (disabled) return;
59
+ overlaypanel.current?.toggle(ev, ev.currentTarget);
60
+ }}
61
+ >
62
+ <div className="flex gap-2">
63
+ {currentLanguage && (
64
+ <>
65
+ <img src={currentLanguage.icon} className="h-5" />{" "}
66
+ {currentLanguage.name}
67
+ </>
68
+ )}
69
+ {!currentLanguage && <div>Select language</div>}
70
+ </div>
71
+ <div
72
+ className={`p-dropdown-trigger ${
73
+ darkMode ? "text-gray-500" : "text-gray-200"
74
+ }`}
75
+ role="button"
76
+ aria-haspopup="listbox"
77
+ aria-expanded="false"
78
+ aria-label="Select a language"
79
+ data-pc-section="trigger"
80
+ >
81
+ <ArrowDownIcon />
82
+ </div>
83
+ </div>
84
+ <OverlayPanel dismissable={true} ref={overlaypanel} closeOnEscape>
85
+ <div className="max-h-[50vh] min-w-64 flex flex-col">
86
+ {showAllLanguagesSwitch && (
87
+ <div className="text-xs p-2 flex items-center gap-2 justify-center">
88
+ <span
89
+ className="cursor-pointer"
90
+ onClick={() => setShowAllLanguagesInternal(false)}
91
+ >
92
+ Item languages
93
+ </span>
94
+ <InputSwitch
95
+ checked={showAllLanguagesInternal}
96
+ onChange={(e) => setShowAllLanguagesInternal(e.value)}
97
+ />
98
+ <span
99
+ className="cursor-pointer"
100
+ onClick={() => setShowAllLanguagesInternal(true)}
101
+ >
102
+ All languages
103
+ </span>
104
+ </div>
105
+ )}
106
+ <div className="flex-1 overflow-y-auto">
107
+ <div className="flex flex-col text-sm">
108
+ {languages.map((x) => (
109
+ <div
110
+ key={x.languageCode}
111
+ className="cursor-pointer hover:bg-gray-200 flex gap-2 p-2"
112
+ onClick={() => selectLanguage(x)}
113
+ >
114
+ <img src={x.icon} className="h-5" /> {x.name} ({x.versions})
115
+ </div>
116
+ ))}
117
+ </div>
118
+ {languages.length === 0 && (
119
+ <div className="p-2 text-gray-500">
120
+ This item has no languages
121
+ </div>
122
+ )}
123
+ </div>
124
+ </div>
125
+ {/* {!readOnly && (
126
+ <div className="border-t p-2 flex flex-col">
127
+ <div className="text-xs text-gray-500 mb-2 flex items-center gap-1">
128
+ <ArrowDownIcon /> Actions
129
+ </div>
130
+ <div className="flex gap-2">
131
+ <Button
132
+ size="small"
133
+ icon="pi pi-globe"
134
+ label="Localize"
135
+ onClick={(event) => {
136
+ if (!editContext?.contentEditorItem) return;
137
+ editContext?.executeCommand({
138
+ command: editContext.configuration.commands.localizeItem,
139
+ data: {
140
+ items: [editContext.contentEditorItem],
141
+ },
142
+ event,
143
+ });
144
+ }}
145
+ ></Button>
146
+ </div>
147
+ </div>
148
+ )} */}
149
+ </OverlayPanel>
150
+ </>
151
+ );
152
+ }
@@ -0,0 +1,83 @@
1
+ import { Menubar } from "primereact/menubar";
2
+ import { EditContextType, useEditContext } from "../client/editContext";
3
+ import { ItemLanguageVersion } from "./ItemLanguageVersion";
4
+ import { getConfiguration } from "../../config/config";
5
+ import { MenuItemCommandEvent } from "primereact/menuitem";
6
+
7
+ import { Command } from "../commands/commands";
8
+ import { PageViewerControls } from "./PageViewerControls";
9
+ import { MenuItem } from "../../config/types";
10
+ import { ApproveAndPublishButton } from "./ApproveAndPublish";
11
+
12
+ export function Menu() {
13
+ const editContext = useEditContext();
14
+ if (!editContext) return;
15
+
16
+ const configuration = getConfiguration();
17
+ if (!configuration.editor.mainNavigation) return null;
18
+ const getSubItems = (
19
+ items:
20
+ | (MenuItem | Command<any>)[]
21
+ | ((editContext: EditContextType) => (MenuItem | Command<any>)[])
22
+ ) => {
23
+ if (typeof items === "function") items = items(editContext);
24
+ return items.map((y) => {
25
+ if ("execute" in y)
26
+ return {
27
+ id: y.id,
28
+ label: y.label,
29
+ icon: y.icon,
30
+ disabled: editContext.isCommandDisabled({ command: y }),
31
+ command: async (event: MenuItemCommandEvent) => {
32
+ await editContext.executeCommand({
33
+ command: y,
34
+ event: event.originalEvent,
35
+ });
36
+ },
37
+ };
38
+ return {
39
+ id: y.id,
40
+ label: y.label,
41
+ icon: y.icon,
42
+ };
43
+ });
44
+ };
45
+
46
+ const menuItems = configuration.editor.mainNavigation.map((x) => {
47
+ if ("execute" in x)
48
+ return {
49
+ id: x.id,
50
+ label: x.label,
51
+ icon: x.icon,
52
+ disabled: editContext.isCommandDisabled({ command: x }),
53
+ command: async (event: MenuItemCommandEvent) => {
54
+ await editContext.executeCommand({
55
+ command: x,
56
+ event: event.originalEvent,
57
+ });
58
+ },
59
+ };
60
+ return {
61
+ id: x.id,
62
+ label: x.label,
63
+ icon: x.icon,
64
+ items: x.items ? getSubItems(x.items) : undefined,
65
+ };
66
+ });
67
+
68
+ const endContent = (
69
+ <div className="flex gap-3 items-center">
70
+ <PageViewerControls />
71
+ <ItemLanguageVersion />
72
+ <ApproveAndPublishButton />
73
+ </div>
74
+ );
75
+
76
+ return (
77
+ <Menubar
78
+ model={menuItems}
79
+ end={endContent}
80
+ className="p-0 text-sm rounded-none border-0 border-b bg-gray-50 shadow-bottom-soft z-10 min-h-12"
81
+ ></Menubar>
82
+ );
83
+ }
@@ -0,0 +1,74 @@
1
+ import { useEditContext } from "../client/editContext";
2
+ import { classNames } from "primereact/utils";
3
+
4
+ export function NavButtons() {
5
+ const editContext = useEditContext();
6
+ if (!editContext) return;
7
+
8
+ const item = editContext.contentEditorItem;
9
+ if (!item) return;
10
+
11
+ const history = editContext.browseHistory;
12
+
13
+ const currentIndex = history.findIndex(
14
+ (x) => x.id === item.id && x.language === item.language
15
+ );
16
+ return (
17
+ <>
18
+ <div
19
+ key="nav-fwd"
20
+ className={classNames(
21
+ currentIndex < history.length - 1
22
+ ? " text-gray-200"
23
+ : "text-gray-400",
24
+ "cursor-pointer hover:bg-gray-500 flex gap-2 p-2 rounded-md "
25
+ )}
26
+ onClick={() => {
27
+ const currentIndex = history.findIndex(
28
+ (x) => x.id === item.id && x.language === item.language
29
+ );
30
+ if (currentIndex < history.length - 1) {
31
+ const historyItem = history[currentIndex + 1];
32
+ if (!historyItem) return;
33
+ editContext.loadItem(
34
+ {
35
+ id: historyItem.id,
36
+ language: historyItem.language,
37
+ version: historyItem.version || 0,
38
+ },
39
+ { addToBrowseHistory: false }
40
+ );
41
+ }
42
+ }}
43
+ >
44
+ <i className="pi pi-chevron-left text-sm" />
45
+ </div>
46
+ <div
47
+ key="nav-back"
48
+ className={classNames(
49
+ currentIndex > 0 ? " text-gray-200" : "text-gray-400",
50
+ "cursor-pointer hover:bg-gray-500 flex gap-2 p-2 rounded-md "
51
+ )}
52
+ onClick={() => {
53
+ const currentIndex = history.findIndex(
54
+ (x) => x.id === item.id && x.language === item.language
55
+ );
56
+ if (currentIndex > 0) {
57
+ const historyItem = history[currentIndex - 1];
58
+ if (!historyItem) return;
59
+ editContext.loadItem(
60
+ {
61
+ id: historyItem.id,
62
+ language: historyItem.language,
63
+ version: historyItem.version || 0,
64
+ },
65
+ { addToBrowseHistory: false }
66
+ );
67
+ }
68
+ }}
69
+ >
70
+ <i className="pi pi-chevron-right text-sm" />
71
+ </div>
72
+ </>
73
+ );
74
+ }