@alpaca-editor/core 1.0.3956 → 1.0.3960

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 (247) hide show
  1. package/build.css +1 -1
  2. package/dist/components/ui/badge.d.ts +9 -0
  3. package/dist/components/ui/badge.js +23 -0
  4. package/dist/components/ui/badge.js.map +1 -0
  5. package/dist/components/ui/button.js +3 -3
  6. package/dist/components/ui/button.js.map +1 -1
  7. package/dist/components/ui/command.d.ts +18 -0
  8. package/dist/components/ui/command.js +35 -0
  9. package/dist/components/ui/command.js.map +1 -0
  10. package/dist/components/ui/dialog.d.ts +15 -0
  11. package/dist/components/ui/dialog.js +37 -0
  12. package/dist/components/ui/dialog.js.map +1 -0
  13. package/dist/components/ui/dropdown-menu.d.ts +25 -0
  14. package/dist/components/ui/dropdown-menu.js +52 -0
  15. package/dist/components/ui/dropdown-menu.js.map +1 -0
  16. package/dist/components/ui/input.d.ts +3 -0
  17. package/dist/components/ui/input.js +7 -0
  18. package/dist/components/ui/input.js.map +1 -0
  19. package/dist/components/ui/menubar.d.ts +26 -0
  20. package/dist/components/ui/menubar.js +55 -0
  21. package/dist/components/ui/menubar.js.map +1 -0
  22. package/dist/components/ui/popover.d.ts +9 -0
  23. package/dist/components/ui/popover.js +63 -0
  24. package/dist/components/ui/popover.js.map +1 -0
  25. package/dist/components/ui/switch.d.ts +4 -0
  26. package/dist/components/ui/switch.js +9 -0
  27. package/dist/components/ui/switch.js.map +1 -0
  28. package/dist/components/ui/tooltip.d.ts +7 -0
  29. package/dist/components/ui/tooltip.js +18 -0
  30. package/dist/components/ui/tooltip.js.map +1 -0
  31. package/dist/config/config.js +79 -63
  32. package/dist/config/config.js.map +1 -1
  33. package/dist/config/types.d.ts +3 -3
  34. package/dist/editor/ContentTree.js +1 -1
  35. package/dist/editor/ContentTree.js.map +1 -1
  36. package/dist/editor/Editor.js +6 -2
  37. package/dist/editor/Editor.js.map +1 -1
  38. package/dist/editor/FieldList.js +1 -1
  39. package/dist/editor/FieldList.js.map +1 -1
  40. package/dist/editor/FieldListField.js +1 -1
  41. package/dist/editor/FieldListField.js.map +1 -1
  42. package/dist/editor/ImageEditor.js +16 -6
  43. package/dist/editor/ImageEditor.js.map +1 -1
  44. package/dist/editor/MainLayout.js +4 -4
  45. package/dist/editor/MainLayout.js.map +1 -1
  46. package/dist/editor/MobileLayout.js +3 -3
  47. package/dist/editor/MobileLayout.js.map +1 -1
  48. package/dist/editor/PictureEditor.js +29 -15
  49. package/dist/editor/PictureEditor.js.map +1 -1
  50. package/dist/editor/Titlebar.js +6 -11
  51. package/dist/editor/Titlebar.js.map +1 -1
  52. package/dist/editor/ai/GhostWriter.js +1 -1
  53. package/dist/editor/ai/GhostWriter.js.map +1 -1
  54. package/dist/editor/client/EditorClient.d.ts +4 -2
  55. package/dist/editor/client/EditorClient.js +32 -11
  56. package/dist/editor/client/EditorClient.js.map +1 -1
  57. package/dist/editor/client/editContext.d.ts +4 -1
  58. package/dist/editor/client/editContext.js.map +1 -1
  59. package/dist/editor/client/operations.js +2 -2
  60. package/dist/editor/client/pageModelBuilder.js +3 -6
  61. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  62. package/dist/editor/commands/itemCommands.d.ts +2 -0
  63. package/dist/editor/commands/itemCommands.js +180 -0
  64. package/dist/editor/commands/itemCommands.js.map +1 -1
  65. package/dist/editor/field-types/MultiLineText.js +1 -1
  66. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  67. package/dist/editor/field-types/SingleLineText.js +1 -1
  68. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  69. package/dist/editor/menubar/ActiveUsers.js +98 -4
  70. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  71. package/dist/editor/menubar/{ActionsMenu.d.ts → ItemActionsMenu.d.ts} +1 -1
  72. package/dist/editor/menubar/ItemActionsMenu.js +23 -0
  73. package/dist/editor/menubar/ItemActionsMenu.js.map +1 -0
  74. package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
  75. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  76. package/dist/editor/menubar/LanguageSelector.d.ts +1 -2
  77. package/dist/editor/menubar/LanguageSelector.js +23 -23
  78. package/dist/editor/menubar/LanguageSelector.js.map +1 -1
  79. package/dist/editor/menubar/PageSelector.js +7 -8
  80. package/dist/editor/menubar/PageSelector.js.map +1 -1
  81. package/dist/editor/menubar/PageViewerControls.js +22 -19
  82. package/dist/editor/menubar/PageViewerControls.js.map +1 -1
  83. package/dist/editor/menubar/PreviewSecondaryControls.js +2 -3
  84. package/dist/editor/menubar/PreviewSecondaryControls.js.map +1 -1
  85. package/dist/editor/menubar/User.js +1 -1
  86. package/dist/editor/menubar/User.js.map +1 -1
  87. package/dist/editor/menubar/VersionSelector.js +36 -31
  88. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  89. package/dist/editor/menubar/WorkflowButton.d.ts +1 -0
  90. package/dist/editor/menubar/WorkflowButton.js +41 -0
  91. package/dist/editor/menubar/WorkflowButton.js.map +1 -0
  92. package/dist/editor/page-editor-chrome/FrameMenu.js +5 -5
  93. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  94. package/dist/editor/page-editor-chrome/SuggestionHighlightings.js +2 -2
  95. package/dist/editor/page-editor-chrome/SuggestionHighlightings.js.map +1 -1
  96. package/dist/editor/page-viewer/EditorForm.d.ts +2 -1
  97. package/dist/editor/page-viewer/EditorForm.js +61 -49
  98. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  99. package/dist/editor/page-viewer/PageViewer.d.ts +2 -1
  100. package/dist/editor/page-viewer/PageViewer.js +28 -44
  101. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  102. package/dist/editor/page-viewer/PageViewerFrame.js +1 -1
  103. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  104. package/dist/editor/reviews/Comments.js +9 -9
  105. package/dist/editor/reviews/Comments.js.map +1 -1
  106. package/dist/editor/reviews/SuggestedEdit.js +3 -3
  107. package/dist/editor/services/contentService.d.ts +18 -0
  108. package/dist/editor/services/contentService.js +6 -0
  109. package/dist/editor/services/contentService.js.map +1 -1
  110. package/dist/editor/services/editService.d.ts +5 -0
  111. package/dist/editor/services/editService.js +4 -0
  112. package/dist/editor/services/editService.js.map +1 -1
  113. package/dist/editor/services/systemService.d.ts +2 -1
  114. package/dist/editor/services/systemService.js +4 -1
  115. package/dist/editor/services/systemService.js.map +1 -1
  116. package/dist/editor/sidebar/ComponentTree.js +26 -10
  117. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  118. package/dist/editor/sidebar/Divider.d.ts +6 -0
  119. package/dist/editor/sidebar/Divider.js +6 -0
  120. package/dist/editor/sidebar/Divider.js.map +1 -0
  121. package/dist/editor/sidebar/LeftToolbar.d.ts +1 -0
  122. package/dist/editor/sidebar/LeftToolbar.js +16 -0
  123. package/dist/editor/sidebar/LeftToolbar.js.map +1 -0
  124. package/dist/editor/sidebar/SEOInfo.d.ts +1 -0
  125. package/dist/editor/sidebar/SEOInfo.js +169 -0
  126. package/dist/editor/sidebar/SEOInfo.js.map +1 -0
  127. package/dist/editor/sidebar/Sidebar.js +1 -1
  128. package/dist/editor/sidebar/Sidebar.js.map +1 -1
  129. package/dist/editor/sidebar/SidebarView.d.ts +3 -2
  130. package/dist/editor/sidebar/SidebarView.js +22 -60
  131. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  132. package/dist/editor/sidebar/ViewSelector.js +66 -20
  133. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  134. package/dist/editor/ui/Icons.d.ts +4 -0
  135. package/dist/editor/ui/Icons.js +15 -3
  136. package/dist/editor/ui/Icons.js.map +1 -1
  137. package/dist/editor/ui/Section.js +1 -1
  138. package/dist/editor/ui/Section.js.map +1 -1
  139. package/dist/editor/ui/SimpleIconButton.d.ts +1 -2
  140. package/dist/editor/ui/SimpleIconButton.js +8 -13
  141. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  142. package/dist/editor/ui/SimpleTabs.js +2 -2
  143. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  144. package/dist/editor/ui/SimpleToolbar.js +1 -1
  145. package/dist/editor/ui/SimpleToolbar.js.map +1 -1
  146. package/dist/editor/ui/Splitter.d.ts +4 -0
  147. package/dist/editor/ui/Splitter.js +6 -7
  148. package/dist/editor/ui/Splitter.js.map +1 -1
  149. package/dist/editor/views/CompareView.js +16 -4
  150. package/dist/editor/views/CompareView.js.map +1 -1
  151. package/dist/editor/views/SingleEditView.d.ts +2 -1
  152. package/dist/editor/views/SingleEditView.js +2 -2
  153. package/dist/editor/views/SingleEditView.js.map +1 -1
  154. package/dist/page-wizard/steps/ContentStep.js +1 -1
  155. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  156. package/dist/revision.d.ts +2 -2
  157. package/dist/revision.js +2 -2
  158. package/dist/splash-screen/NewPage.js +8 -6
  159. package/dist/splash-screen/NewPage.js.map +1 -1
  160. package/dist/splash-screen/RecentPages.js +3 -8
  161. package/dist/splash-screen/RecentPages.js.map +1 -1
  162. package/dist/styles.css +1519 -543
  163. package/dist/tour/Tour.js +79 -10
  164. package/dist/tour/Tour.js.map +1 -1
  165. package/dist/tour/default-tour.js +55 -45
  166. package/dist/tour/default-tour.js.map +1 -1
  167. package/dist/types.d.ts +19 -1
  168. package/package.json +13 -5
  169. package/src/components/ui/badge.tsx +46 -0
  170. package/src/components/ui/button.tsx +3 -3
  171. package/src/components/ui/command.tsx +184 -0
  172. package/src/components/ui/dialog.tsx +143 -0
  173. package/src/components/ui/dropdown-menu.tsx +257 -0
  174. package/src/components/ui/input.tsx +21 -0
  175. package/src/components/ui/menubar.tsx +276 -0
  176. package/src/components/ui/popover.tsx +113 -0
  177. package/src/components/ui/switch.tsx +31 -0
  178. package/src/components/ui/tooltip.tsx +61 -0
  179. package/src/config/config.tsx +102 -65
  180. package/src/config/types.ts +3 -3
  181. package/src/editor/ContentTree.tsx +1 -1
  182. package/src/editor/Editor.tsx +8 -2
  183. package/src/editor/FieldList.tsx +2 -2
  184. package/src/editor/FieldListField.tsx +1 -1
  185. package/src/editor/ImageEditor.tsx +44 -21
  186. package/src/editor/MainLayout.tsx +21 -16
  187. package/src/editor/MobileLayout.tsx +3 -2
  188. package/src/editor/PictureEditor.tsx +74 -45
  189. package/src/editor/Titlebar.tsx +12 -24
  190. package/src/editor/ai/GhostWriter.tsx +1 -1
  191. package/src/editor/client/EditorClient.tsx +55 -13
  192. package/src/editor/client/editContext.ts +5 -0
  193. package/src/editor/client/operations.ts +2 -2
  194. package/src/editor/client/pageModelBuilder.ts +3 -7
  195. package/src/editor/commands/itemCommands.tsx +272 -0
  196. package/src/editor/field-types/MultiLineText.tsx +1 -1
  197. package/src/editor/field-types/SingleLineText.tsx +1 -1
  198. package/src/editor/menubar/ActiveUsers.tsx +271 -5
  199. package/src/editor/menubar/ItemActionsMenu.tsx +89 -0
  200. package/src/editor/menubar/ItemLanguageVersion.tsx +7 -5
  201. package/src/editor/menubar/LanguageSelector.tsx +105 -134
  202. package/src/editor/menubar/PageSelector.tsx +25 -27
  203. package/src/editor/menubar/PageViewerControls.tsx +126 -78
  204. package/src/editor/menubar/PreviewSecondaryControls.tsx +0 -2
  205. package/src/editor/menubar/User.tsx +2 -2
  206. package/src/editor/menubar/VersionSelector.tsx +124 -99
  207. package/src/editor/menubar/WorkflowButton.tsx +115 -0
  208. package/src/editor/page-editor-chrome/FrameMenu.tsx +5 -5
  209. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +2 -2
  210. package/src/editor/page-viewer/EditorForm.tsx +112 -87
  211. package/src/editor/page-viewer/PageViewer.tsx +75 -92
  212. package/src/editor/page-viewer/PageViewerFrame.tsx +1 -1
  213. package/src/editor/reviews/Comments.tsx +19 -20
  214. package/src/editor/reviews/SuggestedEdit.tsx +3 -3
  215. package/src/editor/services/contentService.ts +28 -0
  216. package/src/editor/services/editService.ts +12 -0
  217. package/src/editor/services/systemService.ts +5 -2
  218. package/src/editor/sidebar/ComponentTree.tsx +34 -12
  219. package/src/editor/sidebar/Divider.tsx +22 -0
  220. package/src/editor/sidebar/LeftToolbar.tsx +36 -0
  221. package/src/editor/sidebar/SEOInfo.tsx +265 -0
  222. package/src/editor/sidebar/Sidebar.tsx +1 -0
  223. package/src/editor/sidebar/SidebarView.tsx +77 -111
  224. package/src/editor/sidebar/ViewSelector.tsx +211 -43
  225. package/src/editor/ui/Icons.tsx +155 -10
  226. package/src/editor/ui/Section.tsx +1 -1
  227. package/src/editor/ui/SimpleIconButton.tsx +30 -28
  228. package/src/editor/ui/SimpleTabs.tsx +3 -3
  229. package/src/editor/ui/SimpleToolbar.tsx +1 -1
  230. package/src/editor/ui/Splitter.tsx +14 -7
  231. package/src/editor/views/CompareView.tsx +23 -11
  232. package/src/editor/views/SingleEditView.tsx +3 -0
  233. package/src/page-wizard/steps/ContentStep.tsx +0 -1
  234. package/src/revision.ts +2 -2
  235. package/src/splash-screen/NewPage.tsx +18 -13
  236. package/src/splash-screen/RecentPages.tsx +4 -10
  237. package/src/tour/Tour.tsx +125 -34
  238. package/src/tour/default-tour.tsx +55 -45
  239. package/src/types.ts +21 -1
  240. package/styles.css +301 -1
  241. package/dist/editor/menubar/ActionsMenu.js +0 -49
  242. package/dist/editor/menubar/ActionsMenu.js.map +0 -1
  243. package/dist/editor/menubar/SecondaryControls.d.ts +0 -1
  244. package/dist/editor/menubar/SecondaryControls.js +0 -17
  245. package/dist/editor/menubar/SecondaryControls.js.map +0 -1
  246. package/src/editor/menubar/ActionsMenu.tsx +0 -94
  247. package/src/editor/menubar/SecondaryControls.tsx +0 -45
@@ -29,7 +29,7 @@ export function SuggestedEditComponent({ edit }: { edit: SuggestedEditType }) {
29
29
  const [ignoreFormatting, setIgnoreFormatting] = useState(true);
30
30
  const [clipUnchanged, setClipUnchanged] = useState(true);
31
31
 
32
- const canApply = editContext?.mode === "edit" && edit.status !== "Applied";
32
+ const canApply = editContext?.mode === "edit" && edit.status !== "applied";
33
33
 
34
34
  // Load the full item from the repository.
35
35
  useEffect(() => {
@@ -247,7 +247,7 @@ export function SuggestedEditComponent({ edit }: { edit: SuggestedEditType }) {
247
247
  refresh: "immediate",
248
248
  });
249
249
  // Update the suggestion status to "Applied" and persist the update.
250
- edit.status = "Applied";
250
+ edit.status = "applied";
251
251
  await createOrUpdateSuggestedEdit(edit);
252
252
  setApplied(true);
253
253
  setPatchWarning("");
@@ -269,7 +269,7 @@ export function SuggestedEditComponent({ edit }: { edit: SuggestedEditType }) {
269
269
  rawValue: edit.newValue,
270
270
  refresh: "immediate",
271
271
  });
272
- edit.status = "Applied";
272
+ edit.status = "applied";
273
273
  await createOrUpdateSuggestedEdit(edit);
274
274
  setApplied(true);
275
275
  setPatchWarning("");
@@ -178,3 +178,31 @@ export async function getContentEditorWarnings(item: ItemDescriptor) {
178
178
  }
179
179
  return [];
180
180
  }
181
+
182
+ export type ExportItemsRequest = {
183
+ items: ItemDescriptor[];
184
+ versionOption: "latest" | "all";
185
+ };
186
+
187
+ export type ExportItemsResult = {
188
+ yaml: string;
189
+ itemCount: number;
190
+ };
191
+
192
+ export type ImportItemsRequest = {
193
+ targetItem: ItemDescriptor;
194
+ yaml: string;
195
+ };
196
+
197
+ export type ImportItemsResult = {
198
+ createdItems: ItemDescriptor[];
199
+ itemCount: number;
200
+ };
201
+
202
+ export async function exportItems(request: ExportItemsRequest) {
203
+ return await post<ExportItemsResult>("/alpaca/editor/exportItems", request);
204
+ }
205
+
206
+ export async function importItems(request: ImportItemsRequest) {
207
+ return await post<ImportItemsResult>("/alpaca/editor/importItems", request);
208
+ }
@@ -452,6 +452,18 @@ export async function getUserInfo(): Promise<UserInfo> {
452
452
  return await response.json();
453
453
  }
454
454
 
455
+ export type ItemVisitor = {
456
+ userName: string;
457
+ visitedAt: string; // ISO date string
458
+ };
459
+
460
+ export async function getItemVisitors(
461
+ item: ItemDescriptor,
462
+ ): Promise<ExecutionResult<ItemVisitor[]>> {
463
+ const url = `/alpaca/editor/ItemVisitors?itemId=${item.id}&language=${item.language}&version=${item.version}`;
464
+ return get<ItemVisitor[]>(url);
465
+ }
466
+
455
467
  export async function executeMoveItems(
456
468
  items: ItemDescriptor[],
457
469
  target: ItemDescriptor,
@@ -1,5 +1,8 @@
1
- import { get } from "./serviceHelper";
2
- import { SystemStatus } from "../../types";
1
+ import { get, post } from "./serviceHelper";
2
+ import { SystemStatus, UserPreferences } from "../../types";
3
3
  export function getSystemStatus() {
4
4
  return get<SystemStatus>("/alpaca/editor/status");
5
5
  }
6
+ export function saveUserPreferences(preferences: UserPreferences) {
7
+ return post<UserPreferences>("/alpaca/editor/savepreferences", preferences);
8
+ }
@@ -102,8 +102,12 @@ export function ComponentTree({}) {
102
102
  data: p,
103
103
  parent: parent,
104
104
  type: "placeholder",
105
+ className: !p.editable ? "text-gray-400" : "",
106
+ tags: [],
105
107
  };
108
+
106
109
  node.children = p.components.map((c) => mapComponentNode(c, node));
110
+
107
111
  return node;
108
112
  }
109
113
 
@@ -111,16 +115,27 @@ export function ComponentTree({}) {
111
115
  c: Component,
112
116
  parent: CustomTreeNode,
113
117
  ): CustomTreeNode[] {
114
- if (!c?.placeholders) return [];
115
-
116
- // Filter out non-editable placeholders
117
- const editablePlaceholders = c.placeholders.filter((p) => p.editable);
118
- if (editablePlaceholders.length > 1)
119
- return editablePlaceholders.map((x) => mapPlaceholderNode(x, parent));
120
- else if (editablePlaceholders.length > 0) {
121
- return editablePlaceholders[0]!.components.map((c) =>
122
- mapComponentNode(c, parent),
123
- );
118
+ if (!c?.placeholders) {
119
+ return [];
120
+ }
121
+
122
+ // Show all placeholders (both editable and non-editable)
123
+ const allPlaceholders = c.placeholders;
124
+
125
+ if (allPlaceholders.length > 1) {
126
+ return allPlaceholders.map((x) => mapPlaceholderNode(x, parent));
127
+ } else if (allPlaceholders.length > 0) {
128
+ const placeholder = allPlaceholders[0]!;
129
+ // For single placeholders, always map the components directly (don't show placeholder node)
130
+ return placeholder.components.map((c) => {
131
+ const componentNode = mapComponentNode(c, parent);
132
+ // Apply gray styling to components in non-editable placeholders
133
+ if (!placeholder.editable) {
134
+ componentNode.className =
135
+ (componentNode.className || "") + " text-gray-400";
136
+ }
137
+ return componentNode;
138
+ });
124
139
  }
125
140
  return [];
126
141
  }
@@ -134,6 +149,8 @@ export function ComponentTree({}) {
134
149
  componentId: c.id,
135
150
  label: c.name,
136
151
  icon: c.datasourceItem?.icon ? (
152
+ <img src={c.datasourceItem.icon} width={16} height={16} />
153
+ ) : c.icon ? (
137
154
  <img src={c.icon} width={16} height={16} />
138
155
  ) : (
139
156
  "pi pi-stop"
@@ -237,6 +254,7 @@ export function ComponentTree({}) {
237
254
  children: [],
238
255
  type: "placeholder",
239
256
  });
257
+
240
258
  const dict: { [key: string]: CustomTreeNode } = {};
241
259
  createMap(treeNodes, dict);
242
260
 
@@ -248,7 +266,9 @@ export function ComponentTree({}) {
248
266
  type: "other",
249
267
  };
250
268
 
251
- if (otherNode.children?.length) treeNodes.push(otherNode);
269
+ if (otherNode.children?.length) {
270
+ treeNodes.push(otherNode);
271
+ }
252
272
 
253
273
  const pageNode: CustomTreeNode = {
254
274
  key: "page",
@@ -259,7 +279,9 @@ export function ComponentTree({}) {
259
279
  type: "page",
260
280
  };
261
281
 
262
- setRootNodes([pageNode, ...treeNodes]);
282
+ const finalNodes = [pageNode, ...treeNodes];
283
+
284
+ setRootNodes(finalNodes);
263
285
  setNodeDictionary(dict);
264
286
  }, [page]);
265
287
 
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface DividerProps {
5
+ className?: string;
6
+ orientation?: "horizontal" | "vertical";
7
+ }
8
+
9
+ export function Divider({
10
+ className,
11
+ orientation = "horizontal",
12
+ }: DividerProps) {
13
+ return (
14
+ <div
15
+ className={cn(
16
+ "border-gray-3",
17
+ orientation === "horizontal" ? "w-full border-b" : "h-full border-r",
18
+ className,
19
+ )}
20
+ />
21
+ );
22
+ }
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+ import { DashboardIcon } from "../ui/Icons";
4
+ import { useEditContext } from "../client/editContext";
5
+ import { ViewSelector } from "./ViewSelector";
6
+ import { Divider } from "./Divider";
7
+
8
+ export function LeftToolbar() {
9
+ const editContext = useEditContext();
10
+ const showViewNames =
11
+ editContext?.userInfo.preferences?.showViewNames ?? false;
12
+ return (
13
+ <div
14
+ className={cn(
15
+ "border-gray-4 flex items-center gap-3 border-r bg-white p-2",
16
+ editContext?.isMobile
17
+ ? "scrollbar-hide flex-row overflow-x-auto"
18
+ : `flex-col ${showViewNames ? "w-18" : "w-11"}`,
19
+ )}
20
+ >
21
+ {/* Dashboard/Home button */}
22
+ <a
23
+ className="cursor-pointer rounded-sm bg-gradient-to-r from-blue-500 to-purple-500 transition-all hover:scale-105 hover:bg-gradient-to-r hover:from-blue-600 hover:to-purple-600"
24
+ onClick={() => {
25
+ editContext?.switchView("splash-screen");
26
+ }}
27
+ >
28
+ <DashboardIcon />
29
+ </a>
30
+
31
+ <Divider />
32
+
33
+ <ViewSelector />
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,265 @@
1
+ import { useState } from "react";
2
+ import { Button } from "primereact/button";
3
+ import { Divider } from "primereact/divider";
4
+ import { useEditContext } from "../client/editContext";
5
+
6
+ interface SEOData {
7
+ title?: string;
8
+ description?: string;
9
+ keywords?: string[];
10
+ url?: string;
11
+ isAnalyzing?: boolean;
12
+ lastAnalyzed?: Date;
13
+ }
14
+
15
+ export function SEOInfo() {
16
+ const [seoData, setSeoData] = useState<SEOData>({});
17
+ const [isAnalyzing, setIsAnalyzing] = useState(false);
18
+ const editContext = useEditContext();
19
+
20
+ const runSEOAnalysis = async () => {
21
+ if (!editContext) {
22
+ console.error("No edit context available");
23
+ return;
24
+ }
25
+
26
+ setIsAnalyzing(true);
27
+
28
+ try {
29
+ // Get current page content for analysis
30
+
31
+ const item = editContext.item || editContext.contentEditorItem;
32
+
33
+ console.log("Starting SEO Analysis...", { item });
34
+
35
+ // Extract title and description from page fields if available
36
+ const titleField = item?.fields?.find((f) =>
37
+ f.name?.toLowerCase().includes("title"),
38
+ );
39
+ const descriptionField = item?.fields?.find(
40
+ (f) =>
41
+ f.name?.toLowerCase().includes("description") ||
42
+ f.name?.toLowerCase().includes("summary"),
43
+ );
44
+
45
+ // Update SEO data immediately with current content (this makes the button responsive)
46
+ const currentTitle = (titleField?.value as string) || item?.name || "";
47
+ const currentDescription = (descriptionField?.value as string) || "";
48
+ const currentUrl = item?.path || "";
49
+
50
+ setSeoData({
51
+ title: currentTitle,
52
+ description: currentDescription,
53
+ keywords: [], // Will be populated by AI analysis
54
+ url: currentUrl,
55
+ lastAnalyzed: new Date(),
56
+ });
57
+
58
+ console.log("Updated SEO data with current content:", {
59
+ title: currentTitle,
60
+ description: currentDescription,
61
+ url: currentUrl,
62
+ });
63
+
64
+ // Show immediate feedback
65
+ editContext.showToast({
66
+ severity: "info",
67
+ summary: "SEO Analysis Started",
68
+ detail: "Analyzing current page content...",
69
+ });
70
+
71
+ // Try to send AI analysis request
72
+ const aiServiceUrl =
73
+ editContext.configuration.services.aiService.promptUrl;
74
+ console.log("AI Service URL:", aiServiceUrl);
75
+
76
+ if (!aiServiceUrl) {
77
+ throw new Error("AI service URL not configured");
78
+ }
79
+
80
+ // Prepare SEO optimization prompt in correct message format
81
+ const seoPrompt = `Analyze the current page for SEO optimization and provide specific recommendations.
82
+
83
+ Current page details:
84
+ - Page: ${item?.name || "Unknown"}
85
+ - Path: ${item?.path || "Unknown"}
86
+ - Title: ${currentTitle || "No title found"}
87
+ - Description: ${currentDescription || "No description found"}
88
+
89
+ Please analyze and provide:
90
+ 1. Title optimization suggestions (should be 50-60 characters)
91
+ 2. Meta description recommendations (120-155 characters)
92
+ 3. Keyword analysis and suggestions
93
+ 4. Content structure improvements
94
+ 5. Overall SEO recommendations
95
+
96
+ Focus on actionable insights for improving search engine visibility.`;
97
+
98
+ // Format as proper message for AI service
99
+ const messages = [
100
+ {
101
+ id: Date.now(),
102
+ content: seoPrompt,
103
+ role: "user",
104
+ name: "user",
105
+ },
106
+ ];
107
+
108
+ // Send to AI service using correct format
109
+ const response = await fetch(aiServiceUrl, {
110
+ method: "POST",
111
+ headers: {
112
+ "Content-Type": "application/json",
113
+ },
114
+ body: JSON.stringify({
115
+ profileId: "Editor", // Use default editor profile
116
+ messages: messages,
117
+ selection: editContext.selection || [],
118
+ addSelectedComponents: true,
119
+ selectedText: null,
120
+ model: null, // Let AI service use default model
121
+ sessionId: null,
122
+ itemid: editContext.currentItemDescriptor?.id,
123
+ language: editContext.currentItemDescriptor?.language,
124
+ version: editContext.currentItemDescriptor?.version,
125
+ }),
126
+ });
127
+
128
+ console.log("AI Service Response:", response.status, response.statusText);
129
+
130
+ if (response.ok) {
131
+ // Handle streaming response like the AI terminal does
132
+ if (response.body) {
133
+ const reader = response.body.getReader();
134
+ const decoder = new TextDecoder();
135
+ let buffer = "";
136
+
137
+ while (true) {
138
+ const { done, value } = await reader.read();
139
+ if (done) break;
140
+
141
+ buffer += decoder.decode(value, { stream: true });
142
+ const lines = buffer.split("\n");
143
+
144
+ if (lines.length > 0) {
145
+ buffer = lines.pop() || "";
146
+
147
+ for (let line of lines) {
148
+ if (line.trim() === "") continue;
149
+
150
+ try {
151
+ const jsonData = JSON.parse(line);
152
+ console.log("AI Response chunk:", jsonData);
153
+
154
+ // The AI response contains messages - this will show up in the comments panel
155
+ if (jsonData.messages && jsonData.messages.length > 0) {
156
+ // Final response received
157
+ console.log("Final AI response:", jsonData);
158
+ }
159
+ } catch (e) {
160
+ console.error("Error parsing AI response line:", line, e);
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ // Process any remaining buffer content
167
+ if (buffer.trim() !== "") {
168
+ try {
169
+ const jsonData = JSON.parse(buffer);
170
+ console.log("Final AI buffer:", jsonData);
171
+ } catch (e) {
172
+ console.error("Error parsing final buffer:", e);
173
+ }
174
+ }
175
+ }
176
+
177
+ // Show success message
178
+ editContext.showToast({
179
+ severity: "success",
180
+ summary: "SEO Analysis Complete",
181
+ detail:
182
+ "AI analysis completed. Check the Comments panel for SEO recommendations.",
183
+ });
184
+ } else {
185
+ const errorText = await response.text();
186
+ throw new Error(
187
+ `AI service responded with ${response.status}: ${errorText}`,
188
+ );
189
+ }
190
+ } catch (error) {
191
+ console.error("SEO analysis failed:", error);
192
+ editContext.showToast({
193
+ severity: "warn",
194
+ summary: "SEO Analysis Partial",
195
+ detail: "Current page data extracted. AI analysis unavailable.",
196
+ });
197
+ } finally {
198
+ setIsAnalyzing(false);
199
+ }
200
+ };
201
+
202
+ return (
203
+ <div className="space-y-4 p-4">
204
+ {/* Analysis Button */}
205
+ <Button
206
+ label="Run SEO Analysis"
207
+ icon="pi pi-search"
208
+ onClick={runSEOAnalysis}
209
+ loading={isAnalyzing}
210
+ className="w-full"
211
+ severity="success"
212
+ />
213
+
214
+ <Divider />
215
+
216
+ {/* SEO Information */}
217
+ <div className="space-y-3">
218
+ <div>
219
+ <label className="mb-1 block text-sm font-medium text-gray-700">
220
+ Page Title
221
+ </label>
222
+ <div className="rounded bg-gray-50 p-2 text-sm text-gray-600">
223
+ {seoData.title || "No title detected"}
224
+ </div>
225
+ {seoData.title && (
226
+ <div className="mt-1 text-xs text-gray-500">
227
+ Length: {seoData.title.length}/60 characters
228
+ </div>
229
+ )}
230
+ </div>
231
+
232
+ <div>
233
+ <label className="mb-1 block text-sm font-medium text-gray-700">
234
+ Meta Description
235
+ </label>
236
+ <div className="rounded bg-gray-50 p-2 text-sm text-gray-600">
237
+ {seoData.description || "No description detected"}
238
+ </div>
239
+ {seoData.description && (
240
+ <div className="mt-1 text-xs text-gray-500">
241
+ Length: {seoData.description.length}/155 characters
242
+ </div>
243
+ )}
244
+ </div>
245
+
246
+ <div>
247
+ <label className="mb-1 block text-sm font-medium text-gray-700">
248
+ Keywords
249
+ </label>
250
+ <div className="rounded bg-gray-50 p-2 text-sm text-gray-600">
251
+ {seoData.keywords && seoData.keywords.length > 0
252
+ ? seoData.keywords.join(", ")
253
+ : "No keywords detected"}
254
+ </div>
255
+ </div>
256
+
257
+ {seoData.lastAnalyzed && (
258
+ <div className="border-t pt-2 text-xs text-gray-500">
259
+ Last analyzed: {seoData.lastAnalyzed.toLocaleString()}
260
+ </div>
261
+ )}
262
+ </div>
263
+ </div>
264
+ );
265
+ }
@@ -15,6 +15,7 @@ export function Sidebar() {
15
15
  sidebar={view.leftSidebar}
16
16
  editContext={editContext}
17
17
  active={true}
18
+ onClose={() => editContext?.switchView("page-editor")}
18
19
  />
19
20
  );
20
21
  }