@alpaca-editor/core 1.0.3993 → 1.0.3995

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 (123) hide show
  1. package/dist/components/ui/copy-button.d.ts +10 -0
  2. package/dist/components/ui/copy-button.js +33 -0
  3. package/dist/components/ui/copy-button.js.map +1 -0
  4. package/dist/components/ui/sonner.d.ts +3 -0
  5. package/dist/components/ui/sonner.js +14 -0
  6. package/dist/components/ui/sonner.js.map +1 -0
  7. package/dist/config/config.js +4 -4
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/editor/ComponentInfo.js +2 -2
  10. package/dist/editor/ComponentInfo.js.map +1 -1
  11. package/dist/editor/ContentTree.js +2 -6
  12. package/dist/editor/ContentTree.js.map +1 -1
  13. package/dist/editor/FieldListField.js +3 -10
  14. package/dist/editor/FieldListField.js.map +1 -1
  15. package/dist/editor/ItemInfo.js +3 -3
  16. package/dist/editor/ItemInfo.js.map +1 -1
  17. package/dist/editor/ai/Agents.d.ts +6 -0
  18. package/dist/editor/ai/Agents.js +48 -0
  19. package/dist/editor/ai/Agents.js.map +1 -0
  20. package/dist/editor/ai/AiTerminal.js +4 -2
  21. package/dist/editor/ai/AiTerminal.js.map +1 -1
  22. package/dist/editor/client/EditorClient.js +48 -91
  23. package/dist/editor/client/EditorClient.js.map +1 -1
  24. package/dist/editor/client/editContext.d.ts +3 -2
  25. package/dist/editor/client/editContext.js.map +1 -1
  26. package/dist/editor/commands/itemCommands.js +5 -24
  27. package/dist/editor/commands/itemCommands.js.map +1 -1
  28. package/dist/editor/component-designer/ComponentEditor.js +3 -5
  29. package/dist/editor/component-designer/ComponentEditor.js.map +1 -1
  30. package/dist/editor/field-types/RichTextEditor.d.ts +2 -1
  31. package/dist/editor/field-types/RichTextEditor.js +2 -2
  32. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  33. package/dist/editor/field-types/RichTextEditorComponent.d.ts +2 -1
  34. package/dist/editor/field-types/RichTextEditorComponent.js +25 -15
  35. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  36. package/dist/editor/field-types/richtext/components/EditorDropdown.d.ts +2 -2
  37. package/dist/editor/field-types/richtext/components/EditorDropdown.js +40 -53
  38. package/dist/editor/field-types/richtext/components/EditorDropdown.js.map +1 -1
  39. package/dist/editor/field-types/richtext/components/ReactSlate.js +8 -4
  40. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  41. package/dist/editor/field-types/richtext/utils/profileMapper.js +4 -4
  42. package/dist/editor/field-types/richtext/utils/profileMapper.js.map +1 -1
  43. package/dist/editor/media-selector/AiImageSearch.js +2 -5
  44. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  45. package/dist/editor/media-selector/MediaFolderBrowser.js +2 -5
  46. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  47. package/dist/editor/media-selector/TreeSelector.js +2 -5
  48. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  49. package/dist/editor/menubar/FavoritesControls.d.ts +8 -0
  50. package/dist/editor/menubar/FavoritesControls.js +124 -0
  51. package/dist/editor/menubar/FavoritesControls.js.map +1 -0
  52. package/dist/editor/menubar/ItemLanguageVersion.js +2 -1
  53. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  54. package/dist/editor/menubar/PageSelector.js +3 -6
  55. package/dist/editor/menubar/PageSelector.js.map +1 -1
  56. package/dist/editor/reviews/reviewCommands.js +3 -8
  57. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  58. package/dist/editor/services/favouritesService.d.ts +33 -0
  59. package/dist/editor/services/favouritesService.js +22 -0
  60. package/dist/editor/services/favouritesService.js.map +1 -0
  61. package/dist/editor/sidebar/Debug.js +2 -2
  62. package/dist/editor/sidebar/Debug.js.map +1 -1
  63. package/dist/editor/sidebar/SEOInfo.js +4 -15
  64. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  65. package/dist/editor/ui/ItemSearch.js +2 -5
  66. package/dist/editor/ui/ItemSearch.js.map +1 -1
  67. package/dist/editor/ui/Section.js +1 -1
  68. package/dist/editor/utils/keyboardNavigation.d.ts +32 -0
  69. package/dist/editor/utils/keyboardNavigation.js +156 -0
  70. package/dist/editor/utils/keyboardNavigation.js.map +1 -0
  71. package/dist/page-wizard/PageWizard.d.ts +2 -2
  72. package/dist/page-wizard/steps/ContentStep.js +6 -4
  73. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  74. package/dist/page-wizard/steps/schema.js +4 -2
  75. package/dist/page-wizard/steps/schema.js.map +1 -1
  76. package/dist/revision.d.ts +2 -2
  77. package/dist/revision.js +2 -2
  78. package/dist/splash-screen/NewPage.js +7 -10
  79. package/dist/splash-screen/NewPage.js.map +1 -1
  80. package/dist/styles.css +25 -5
  81. package/package.json +3 -1
  82. package/src/components/ui/copy-button.tsx +75 -0
  83. package/src/components/ui/sonner.tsx +25 -0
  84. package/src/config/config.tsx +5 -7
  85. package/src/editor/ComponentInfo.tsx +5 -4
  86. package/src/editor/ContentTree.tsx +2 -6
  87. package/src/editor/FieldListField.tsx +4 -25
  88. package/src/editor/ItemInfo.tsx +4 -4
  89. package/src/editor/ai/Agents.tsx +125 -0
  90. package/src/editor/ai/AiTerminal.tsx +4 -0
  91. package/src/editor/client/EditorClient.tsx +58 -119
  92. package/src/editor/client/editContext.ts +3 -2
  93. package/src/editor/commands/itemCommands.tsx +10 -25
  94. package/src/editor/component-designer/ComponentEditor.tsx +8 -10
  95. package/src/editor/field-types/RichTextEditor.tsx +10 -1
  96. package/src/editor/field-types/RichTextEditorComponent.tsx +25 -11
  97. package/src/editor/field-types/richtext/components/EditorDropdown.css +81 -0
  98. package/src/editor/field-types/richtext/components/EditorDropdown.tsx +57 -72
  99. package/src/editor/field-types/richtext/components/ReactSlate.css +1 -3
  100. package/src/editor/field-types/richtext/components/ReactSlate.tsx +13 -4
  101. package/src/editor/field-types/richtext/utils/profileMapper.ts +4 -4
  102. package/src/editor/media-selector/AiImageSearch.tsx +2 -5
  103. package/src/editor/media-selector/MediaFolderBrowser.tsx +2 -5
  104. package/src/editor/media-selector/TreeSelector.tsx +2 -5
  105. package/src/editor/menubar/FavoritesControls.tsx +250 -0
  106. package/src/editor/menubar/ItemLanguageVersion.tsx +3 -1
  107. package/src/editor/menubar/PageSelector.tsx +76 -75
  108. package/src/editor/reviews/reviewCommands.tsx +3 -8
  109. package/src/editor/services/favouritesService.ts +60 -0
  110. package/src/editor/sidebar/Debug.tsx +4 -3
  111. package/src/editor/sidebar/SEOInfo.tsx +6 -16
  112. package/src/editor/ui/ItemSearch.tsx +2 -5
  113. package/src/editor/ui/Section.tsx +1 -1
  114. package/src/editor/utils/keyboardNavigation.ts +234 -0
  115. package/src/page-wizard/PageWizard.tsx +2 -2
  116. package/src/page-wizard/steps/ContentStep.tsx +6 -6
  117. package/src/page-wizard/steps/schema.ts +10 -7
  118. package/src/revision.ts +2 -2
  119. package/src/splash-screen/NewPage.tsx +28 -24
  120. package/dist/editor/ui/CopyToClipboardButton.d.ts +0 -3
  121. package/dist/editor/ui/CopyToClipboardButton.js +0 -16
  122. package/dist/editor/ui/CopyToClipboardButton.js.map +0 -1
  123. package/src/editor/ui/CopyToClipboardButton.tsx +0 -24
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { useEditContext } from "../client/editContext";
3
+ import { toast } from "sonner";
3
4
 
4
5
  import { useDebouncedCallback } from "use-debounce";
5
6
  import { FilterInput } from "../../components/FilterInput";
@@ -106,11 +107,7 @@ export function PageSelector({
106
107
  if (result.type == "success") {
107
108
  setSearchResults(result.data as ResultItem[]);
108
109
  } else {
109
- editContext.showToast({
110
- severity: "error",
111
- summary: "Error searching",
112
- detail: result.response.statusText,
113
- });
110
+ toast.error(result.response.statusText || "Error searching");
114
111
  setSearchResults([]);
115
112
  }
116
113
  }
@@ -179,83 +176,87 @@ export function PageSelector({
179
176
  const hasResults = currentResults.length > 0;
180
177
 
181
178
  return (
182
- <Popover open={open} onOpenChange={setOpen}>
183
- <PopoverTrigger asChild>
184
- <div
185
- id="page-selector-button"
186
- className="text-dark text-sm0 flex cursor-pointer items-center gap-3 rounded-md p-[7px] py-[9px]"
187
- data-testid="page-selector-button"
188
- >
189
- <div className="flex flex-col">
190
- <div className="text-sm font-medium">{item?.name || "unknown"}</div>
191
- <div className="text-xs text-gray-500">{item?.path}</div>
192
- </div>
193
- </div>
194
- </PopoverTrigger>
195
- <PopoverContent className="w-96 p-0" align="start">
196
- <div className="flex h-[75vh] max-h-[600px] max-w-96 min-w-96 flex-col overflow-hidden">
197
- {/* Search/Filter bar with view toggle buttons */}
198
- <div className="flex gap-2 border-b border-gray-200 p-2">
199
- <div className="flex-1">
200
- <FilterInput
201
- value={query}
202
- onChange={setQuery}
203
- ref={inputRef}
204
- placeholder={
205
- showHistory ? "Filter history..." : "Search content..."
206
- }
207
- loading={loading}
208
- className="w-full"
209
- spinnerStyle={{ width: "18px", height: "18px" }}
210
- />
211
- </div>
212
- <div className="flex gap-1">
213
- <SimpleIconButton
214
- icon="pi pi-sitemap"
215
- label="Search Content"
216
- selected={!showHistory}
217
- onClick={() => setShowHistory(false)}
218
- size="small"
219
- />
220
- <SimpleIconButton
221
- icon="pi pi-history"
222
- label="Filter History"
223
- selected={showHistory}
224
- onClick={() => setShowHistory(true)}
225
- size="small"
226
- />
179
+ <div className="flex items-center gap-1">
180
+ <Popover open={open} onOpenChange={setOpen}>
181
+ <PopoverTrigger asChild>
182
+ <div
183
+ id="page-selector-button"
184
+ className="text-dark text-sm0 flex cursor-pointer items-center gap-3 rounded-md p-[7px] py-[9px]"
185
+ data-testid="page-selector-button"
186
+ >
187
+ <div className="flex flex-col">
188
+ <div className="text-sm font-medium">
189
+ {item?.name || "unknown"}
190
+ </div>
191
+ <div className="text-xs text-gray-500">{item?.path}</div>
227
192
  </div>
228
193
  </div>
194
+ </PopoverTrigger>
195
+ <PopoverContent className="w-96 p-0" align="start">
196
+ <div className="flex h-[75vh] max-h-[600px] max-w-96 min-w-96 flex-col overflow-hidden">
197
+ {/* Search/Filter bar with view toggle buttons */}
198
+ <div className="flex gap-2 border-b border-gray-200 p-2">
199
+ <div className="flex-1">
200
+ <FilterInput
201
+ value={query}
202
+ onChange={setQuery}
203
+ ref={inputRef}
204
+ placeholder={
205
+ showHistory ? "Filter history..." : "Search content..."
206
+ }
207
+ loading={loading}
208
+ className="w-full"
209
+ spinnerStyle={{ width: "18px", height: "18px" }}
210
+ />
211
+ </div>
212
+ <div className="flex gap-1">
213
+ <SimpleIconButton
214
+ icon="pi pi-sitemap"
215
+ label="Search Content"
216
+ selected={!showHistory}
217
+ onClick={() => setShowHistory(false)}
218
+ size="small"
219
+ />
220
+ <SimpleIconButton
221
+ icon="pi pi-history"
222
+ label="Filter History"
223
+ selected={showHistory}
224
+ onClick={() => setShowHistory(true)}
225
+ size="small"
226
+ />
227
+ </div>
228
+ </div>
229
229
 
230
- {/* Content area */}
231
- <div className="flex flex-1 flex-col overflow-hidden">
232
- {/* Results area - takes up to 50% when results are present */}
233
- {hasResults && (
234
- <div className="max-h-[50%] shrink-0 overflow-auto border-b border-gray-200">
235
- <div className="p-2">
236
- <ItemList
237
- items={currentResults}
238
- onItemHover={setHoveredItem}
239
- onItemClick={loadItem}
240
- maxItems={20}
241
- className="max-h-full overflow-auto"
242
- />
230
+ {/* Content area */}
231
+ <div className="flex flex-1 flex-col overflow-hidden">
232
+ {/* Results area - takes up to 50% when results are present */}
233
+ {hasResults && (
234
+ <div className="max-h-[50%] shrink-0 overflow-auto border-b border-gray-200">
235
+ <div className="p-2">
236
+ <ItemList
237
+ items={currentResults}
238
+ onItemHover={setHoveredItem}
239
+ onItemClick={loadItem}
240
+ maxItems={20}
241
+ className="max-h-full overflow-auto"
242
+ />
243
+ </div>
243
244
  </div>
244
- </div>
245
- )}
245
+ )}
246
246
 
247
- {/* Tree view - always visible, takes remaining space */}
248
- <div className="relative min-h-0 flex-1">
249
- <div className="absolute inset-0 overflow-auto">
250
- <ScrollingContentTree
251
- selectedItemId={hoveredItem?.id ?? item?.id}
252
- onSelectionChange={loadItem}
253
- />
247
+ {/* Tree view - always visible, takes remaining space */}
248
+ <div className="relative min-h-0 flex-1">
249
+ <div className="absolute inset-0 overflow-auto">
250
+ <ScrollingContentTree
251
+ selectedItemId={hoveredItem?.id ?? item?.id}
252
+ onSelectionChange={loadItem}
253
+ />
254
+ </div>
254
255
  </div>
255
256
  </div>
256
257
  </div>
257
- </div>
258
- </PopoverContent>
259
- </Popover>
258
+ </PopoverContent>
259
+ </Popover>
260
+ </div>
260
261
  );
261
262
  }
@@ -1,6 +1,7 @@
1
1
  import { Command, CommandContext, CommandData } from "../commands/commands";
2
2
  import { approveReview, rejectReview } from "../services/reviewsService";
3
3
  import { Check, X } from "lucide-react";
4
+ import { toast } from "sonner";
4
5
 
5
6
  export const approveReviewCommand: Command<CommandData> = {
6
7
  id: "approveReview",
@@ -11,10 +12,7 @@ export const approveReviewCommand: Command<CommandData> = {
11
12
  return;
12
13
  }
13
14
  await approveReview(context.editContext.currentItemDescriptor);
14
- context.editContext.showToast({
15
- summary: "Page/item approved",
16
- severity: "success",
17
- });
15
+ toast.success("Page/item approved");
18
16
  },
19
17
  disabled: (context) => {
20
18
  return !isReviewer(context);
@@ -29,10 +27,7 @@ export const rejectReviewCommand: Command<CommandData> = {
29
27
  return;
30
28
  }
31
29
  await rejectReview(context.editContext.currentItemDescriptor);
32
- context.editContext.showToast({
33
- summary: "Page/item rejected",
34
- severity: "error",
35
- });
30
+ toast.error("Page/item rejected");
36
31
  },
37
32
  disabled: (context) => {
38
33
  return !isReviewer(context);
@@ -0,0 +1,60 @@
1
+ import { get, post, ExecutionResult } from "./serviceHelper";
2
+
3
+ // Request/Response Types
4
+ export interface SaveFavoriteRequest {
5
+ itemId: string;
6
+ language: string;
7
+ priority: number;
8
+ }
9
+
10
+ export interface FavoriteItem {
11
+ itemId: string;
12
+ language: string;
13
+ priority: number;
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ }
17
+
18
+ export interface ToggleFavoriteResponse {
19
+ success: boolean;
20
+ isFavorite: boolean;
21
+ message: string;
22
+ }
23
+
24
+ export interface IsFavoriteResponse {
25
+ isFavorite: boolean;
26
+ }
27
+
28
+ // Service Functions
29
+
30
+ /**
31
+ * Gets all favorites for the current user
32
+ */
33
+ export async function getAllFavorites(): Promise<
34
+ ExecutionResult<FavoriteItem[]>
35
+ > {
36
+ return await get<FavoriteItem[]>("/alpaca/editor/favorites/index");
37
+ }
38
+
39
+ /**
40
+ * Toggles a favorite item (adds if not exists, removes if exists)
41
+ */
42
+ export async function toggleFavorite(
43
+ request: SaveFavoriteRequest,
44
+ ): Promise<ExecutionResult<ToggleFavoriteResponse>> {
45
+ return await post<ToggleFavoriteResponse>(
46
+ "/alpaca/editor/favorites/toggle",
47
+ request,
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Checks if an item is favorited by the current user
53
+ */
54
+ export async function isFavorite(
55
+ itemId: string,
56
+ language: string,
57
+ ): Promise<ExecutionResult<IsFavoriteResponse>> {
58
+ const url = `/alpaca/editor/favorites/isfavorite?itemId=${encodeURIComponent(itemId)}&language=${encodeURIComponent(language)}`;
59
+ return await get<IsFavoriteResponse>(url);
60
+ }
@@ -1,5 +1,5 @@
1
1
  import { SimpleTabs, Tab } from "../ui/SimpleTabs";
2
- import { CopyToClipboardButton } from "../ui/CopyToClipboardButton";
2
+ import { CopyButton } from "../../components/ui/copy-button";
3
3
  import { useEffect, useState } from "react";
4
4
 
5
5
  import { getComponentByIdFromHeadlessLayout } from "../componentTreeHelper";
@@ -102,12 +102,13 @@ export function Debug({}: {}) {
102
102
  />
103
103
 
104
104
  <div className="absolute top-2 right-1 h-4 w-4">
105
- <CopyToClipboardButton
106
- text={JSON.stringify(
105
+ <CopyButton
106
+ textToCopy={JSON.stringify(
107
107
  activeTab === 0 && component ? component : page,
108
108
  replacer,
109
109
  2,
110
110
  )}
111
+ iconOnly
111
112
  />
112
113
  </div>
113
114
  </div>
@@ -2,6 +2,7 @@ import { useState } from "react";
2
2
  import { Button } from "primereact/button";
3
3
  import { Divider } from "primereact/divider";
4
4
  import { useEditContext } from "../client/editContext";
5
+ import { toast } from "sonner";
5
6
 
6
7
  interface SEOData {
7
8
  title?: string;
@@ -62,11 +63,7 @@ export function SEOInfo() {
62
63
  });
63
64
 
64
65
  // Show immediate feedback
65
- editContext.showToast({
66
- severity: "info",
67
- summary: "SEO Analysis Started",
68
- detail: "Analyzing current page content...",
69
- });
66
+ toast.info("Analyzing current page content...");
70
67
 
71
68
  // Try to send AI analysis request
72
69
  const aiServiceUrl =
@@ -175,12 +172,9 @@ Focus on actionable insights for improving search engine visibility.`;
175
172
  }
176
173
 
177
174
  // 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
- });
175
+ toast.success(
176
+ "AI analysis completed. Check the Comments panel for SEO recommendations.",
177
+ );
184
178
  } else {
185
179
  const errorText = await response.text();
186
180
  throw new Error(
@@ -189,11 +183,7 @@ Focus on actionable insights for improving search engine visibility.`;
189
183
  }
190
184
  } catch (error) {
191
185
  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
- });
186
+ toast.warning("Current page data extracted. AI analysis unavailable.");
197
187
  } finally {
198
188
  setIsAnalyzing(false);
199
189
  }
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { useEditContext } from "../client/editContext";
3
3
  import { executeSearch } from "../services/aiService";
4
+ import { toast } from "sonner";
4
5
  import { useDebouncedCallback } from "use-debounce";
5
6
  import { ProgressSpinner } from "primereact/progressspinner";
6
7
  import { ItemDescriptor } from "../pageModel";
@@ -89,11 +90,7 @@ export const ItemSearch: React.FC<SearchProps> = ({
89
90
  if (result.type == "success") {
90
91
  setResults(() => result.data as ResultItem[]);
91
92
  } else {
92
- editContext.showToast({
93
- severity: "error",
94
- summary: "Error searching",
95
- detail: result.response.statusText,
96
- });
93
+ toast.error(result.response.statusText || "Error searching");
97
94
  }
98
95
  }
99
96
  setLoading(false);
@@ -18,7 +18,7 @@ export function Section({
18
18
  open
19
19
  ? "border-blue-500 bg-gray-600"
20
20
  : "border-gray-400 bg-gray-400 hover:border-gray-300 hover:bg-gray-500",
21
- "flex cursor-pointer items-center justify-between border-l-[8px] px-3 py-2 text-xs text-white transition-all duration-200 ease-in-out",
21
+ "flex cursor-pointer items-center justify-between border-l-[7px] px-3 py-2 text-xs text-white transition-all duration-200 ease-in-out",
22
22
  )}
23
23
  onClick={() => setOpen(!open)}
24
24
  >
@@ -0,0 +1,234 @@
1
+ import { EditContextType } from "../client/editContext";
2
+ import { EditorConfiguration } from "../../config/types";
3
+ import { ItemDescriptor, FullItem } from "../pageModel";
4
+ import { HistoryEntry } from "../../types";
5
+ import { useDebouncedCallback } from "use-debounce";
6
+ import { useCallback } from "react";
7
+
8
+ export interface KeyboardNavigationDependencies {
9
+ editContextRef: React.MutableRefObject<EditContextType | undefined>;
10
+ operations: any;
11
+ pageViewContext: any;
12
+ configuration: EditorConfiguration;
13
+ contentEditorItem: FullItem | undefined;
14
+ browseHistory: HistoryEntry[];
15
+ loadItem: (
16
+ itemToLoad: ItemDescriptor | string,
17
+ options?: { addToBrowseHistory?: boolean; skipViewChange?: boolean },
18
+ ) => Promise<FullItem | undefined>;
19
+ showInfoToast: (props: { summary?: string; details?: string }) => void;
20
+ showErrorToast: (props: { summary?: string; details?: string }) => void;
21
+ executeCommand: (params: {
22
+ command: any;
23
+ data?: any;
24
+ event?: React.SyntheticEvent;
25
+ }) => Promise<any>;
26
+ }
27
+
28
+ export function useKeyboardNavigation(deps: KeyboardNavigationDependencies) {
29
+ const {
30
+ editContextRef,
31
+ operations,
32
+ pageViewContext,
33
+ configuration,
34
+ contentEditorItem,
35
+ browseHistory,
36
+ loadItem,
37
+ showInfoToast,
38
+ executeCommand,
39
+ } = deps;
40
+
41
+ const handleKeyDownDebounced = useDebouncedCallback(
42
+ async (event: KeyboardEvent) => {
43
+ if (!event.key) return;
44
+
45
+ if (event.ctrlKey && event.key === "z") {
46
+ await operations.undo();
47
+ }
48
+ if (event.ctrlKey && event.key === "y") {
49
+ await operations.redo();
50
+ }
51
+ if (event.ctrlKey && event.key === "F11") {
52
+ event.preventDefault();
53
+ pageViewContext.setFullscreen(false);
54
+ }
55
+
56
+ // History Navigation (Alt + Arrow keys, Alt + Shift + Arrow keys for favorites only)
57
+ if (
58
+ event.altKey &&
59
+ (event.key === "ArrowLeft" || event.key === "ArrowRight")
60
+ ) {
61
+ event.preventDefault();
62
+ const currentItem = contentEditorItem;
63
+ if (!currentItem) return;
64
+
65
+ let historyToNavigate = browseHistory;
66
+
67
+ // If Shift is also pressed, filter history to only include favorites
68
+ if (event.shiftKey) {
69
+ if (editContextRef.current?.favorites) {
70
+ const favoriteIds = new Set(
71
+ editContextRef.current.favorites.map(
72
+ (fav: any) => `${fav.itemId}:${fav.language}`,
73
+ ),
74
+ );
75
+
76
+ historyToNavigate = browseHistory.filter((historyItem) =>
77
+ favoriteIds.has(`${historyItem.id}:${historyItem.language}`),
78
+ );
79
+
80
+ if (historyToNavigate.length === 0) {
81
+ showInfoToast({
82
+ summary: "Navigation",
83
+ details: "No favorite items found in browse history",
84
+ });
85
+ return;
86
+ }
87
+ } else {
88
+ showInfoToast({
89
+ summary: "Navigation",
90
+ details: "Favorites not loaded yet",
91
+ });
92
+ return;
93
+ }
94
+ }
95
+
96
+ const currentIndex = historyToNavigate.findIndex(
97
+ (x) => x.id === currentItem.id && x.language === currentItem.language,
98
+ );
99
+
100
+ if (
101
+ event.key === "ArrowLeft" &&
102
+ currentIndex < historyToNavigate.length - 1
103
+ ) {
104
+ // Go forward in history
105
+ const historyItem = historyToNavigate[currentIndex + 1];
106
+ if (historyItem) {
107
+ loadItem(
108
+ {
109
+ id: historyItem.id,
110
+ language: historyItem.language,
111
+ version: historyItem.version || 0,
112
+ },
113
+ { addToBrowseHistory: false },
114
+ );
115
+ }
116
+ } else if (event.key === "ArrowRight" && currentIndex > 0) {
117
+ // Go back in history
118
+ const historyItem = historyToNavigate[currentIndex - 1];
119
+ if (historyItem) {
120
+ loadItem(
121
+ {
122
+ id: historyItem.id,
123
+ language: historyItem.language,
124
+ version: historyItem.version || 0,
125
+ },
126
+ { addToBrowseHistory: false },
127
+ );
128
+ }
129
+ }
130
+ return;
131
+ }
132
+
133
+ // Quick access to favorites (Ctrl + 1-9)
134
+ if (event.altKey && /^[1-9]$/.test(event.key)) {
135
+ event.preventDefault();
136
+ const index = parseInt(event.key) - 1;
137
+
138
+ // Use favorites from editContext
139
+ if (editContextRef.current?.favorites) {
140
+ const favorites = editContextRef.current.favorites;
141
+ if (favorites[index]) {
142
+ const favorite = favorites[index];
143
+ loadItem({
144
+ id: favorite.itemId,
145
+ language: favorite.language,
146
+ version: 0,
147
+ });
148
+ } else {
149
+ showInfoToast({
150
+ summary: "Favorites",
151
+ details: `No favorite item at position ${event.key}`,
152
+ });
153
+ }
154
+ } else {
155
+ showInfoToast({
156
+ summary: "Favorites",
157
+ details: "Favorites not loaded yet",
158
+ });
159
+ }
160
+ return;
161
+ }
162
+
163
+ // Command execution
164
+ const command = configuration.commands.allItemCommands.find(
165
+ (x) => x.keyBinding === event.key,
166
+ );
167
+
168
+ if (command) {
169
+ event.preventDefault();
170
+ const contentEditorItem = editContextRef.current?.contentEditorItem;
171
+ if (!contentEditorItem) return;
172
+
173
+ const items =
174
+ editContextRef.current?.selection?.map((x) => ({
175
+ id: x,
176
+ language: contentEditorItem.language,
177
+ version: 0,
178
+ })) || [];
179
+
180
+ if (!items.length) items.push(contentEditorItem.descriptor);
181
+
182
+ if (items.length > 0) {
183
+ const fullItems =
184
+ await editContextRef.current?.itemsRepository.getItems(items);
185
+ executeCommand({
186
+ command,
187
+ data: {
188
+ items: fullItems,
189
+ },
190
+ });
191
+ }
192
+ }
193
+ },
194
+ 50,
195
+ );
196
+
197
+ const handleKeyDown = useCallback(
198
+ async (event: KeyboardEvent) => {
199
+ if (event.key === "Insert") {
200
+ event.preventDefault();
201
+ event.stopPropagation();
202
+ editContextRef.current?.setInsertMode((x) => !x);
203
+ }
204
+
205
+ if (event.ctrlKey && event.key === "s") {
206
+ event.preventDefault();
207
+ event.stopPropagation();
208
+ return;
209
+ }
210
+
211
+ const target = event.target as HTMLElement;
212
+ const isTyping =
213
+ target instanceof HTMLInputElement ||
214
+ target instanceof HTMLTextAreaElement ||
215
+ target.isContentEditable;
216
+
217
+ if (
218
+ (event.ctrlKey && event.key === "z") ||
219
+ (event.ctrlKey && event.key === "y")
220
+ ) {
221
+ if (!isTyping) {
222
+ event.preventDefault();
223
+ event.stopPropagation();
224
+ handleKeyDownDebounced(event);
225
+ }
226
+ return;
227
+ }
228
+ handleKeyDownDebounced(event);
229
+ },
230
+ [handleKeyDownDebounced, editContextRef],
231
+ );
232
+
233
+ return { handleKeyDown };
234
+ }
@@ -36,8 +36,8 @@ export type SchemaField = {
36
36
  export type WizardSchemaComponent = {
37
37
  type: string;
38
38
  fields: WizardSchemaField[];
39
- availableParentPlaceholders?: string[];
40
- allowedChildrenComponentTypes: string[];
39
+ validParentPlaceholders?: string[];
40
+ allowedChildrenComponentTypes?: string[];
41
41
  allowedOnRoot: boolean;
42
42
  };
43
43
 
@@ -507,11 +507,11 @@ export function ContentStep({
507
507
  const localAbortController = new AbortController();
508
508
  setAbortController(localAbortController);
509
509
 
510
- // Get the existing page model
511
- const existingPageModel = await convertToAiPageModel(
512
- editContextRef.current?.page!,
513
- editContextRef.current!,
514
- );
510
+ // Get the existing page model; TODO make optional
511
+ const existingPageModel = undefined; // await convertToAiPageModel(
512
+ //editContextRef.current?.page!,
513
+ //editContextRef.current!,
514
+ //);
515
515
 
516
516
  console.log("Existing page model: ", existingPageModel);
517
517
 
@@ -523,7 +523,7 @@ export function ContentStep({
523
523
  Generate a descriptive name for each component including the topic.
524
524
  Only use component types that are in the page schema.
525
525
  Keep existing components with their ids. Leave id field empty for new components.
526
- Existing page model: ${JSON.stringify(existingPageModel)}
526
+ ${existingPageModel && `Existing page model: ${JSON.stringify(existingPageModel)}`}
527
527
  Fill empty fields of existing components before you insert new components.
528
528
  Component types: ${JSON.stringify(
529
529
  filteredSchema,