@alpaca-editor/core 1.0.3992 → 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 (190) 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 -16
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/config/types.d.ts +6 -3
  10. package/dist/editor/ComponentInfo.js +2 -2
  11. package/dist/editor/ComponentInfo.js.map +1 -1
  12. package/dist/editor/ContentTree.d.ts +2 -1
  13. package/dist/editor/ContentTree.js +4 -8
  14. package/dist/editor/ContentTree.js.map +1 -1
  15. package/dist/editor/FieldListField.js +3 -10
  16. package/dist/editor/FieldListField.js.map +1 -1
  17. package/dist/editor/ItemInfo.js +3 -3
  18. package/dist/editor/ItemInfo.js.map +1 -1
  19. package/dist/editor/ai/Agents.d.ts +6 -0
  20. package/dist/editor/ai/Agents.js +48 -0
  21. package/dist/editor/ai/Agents.js.map +1 -0
  22. package/dist/editor/ai/AiTerminal.js +4 -2
  23. package/dist/editor/ai/AiTerminal.js.map +1 -1
  24. package/dist/editor/client/EditorClient.js +48 -91
  25. package/dist/editor/client/EditorClient.js.map +1 -1
  26. package/dist/editor/client/editContext.d.ts +3 -2
  27. package/dist/editor/client/editContext.js.map +1 -1
  28. package/dist/editor/commands/itemCommands.js +5 -24
  29. package/dist/editor/commands/itemCommands.js.map +1 -1
  30. package/dist/editor/component-designer/ComponentEditor.js +3 -5
  31. package/dist/editor/component-designer/ComponentEditor.js.map +1 -1
  32. package/dist/editor/field-types/InternalLinkFieldEditor.js +20 -25
  33. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  34. package/dist/editor/field-types/RichTextEditor.d.ts +4 -3
  35. package/dist/editor/field-types/RichTextEditor.js +16 -3
  36. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  37. package/dist/editor/field-types/RichTextEditorComponent.d.ts +6 -5
  38. package/dist/editor/field-types/RichTextEditorComponent.js +59 -60
  39. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  40. package/dist/editor/field-types/TreeListEditor.js +7 -5
  41. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  42. package/dist/editor/field-types/richtext/components/EditorDropdown.d.ts +11 -0
  43. package/dist/editor/field-types/richtext/components/EditorDropdown.js +83 -0
  44. package/dist/editor/field-types/richtext/components/EditorDropdown.js.map +1 -0
  45. package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +5 -0
  46. package/dist/editor/field-types/richtext/components/ReactSlate.js +562 -0
  47. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -0
  48. package/dist/editor/field-types/richtext/components/ToolbarButton.d.ts +3 -0
  49. package/dist/editor/field-types/richtext/components/ToolbarButton.js +13 -0
  50. package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -0
  51. package/dist/editor/field-types/richtext/config/pluginFactory.d.ts +17 -0
  52. package/dist/editor/field-types/richtext/config/pluginFactory.js +14 -0
  53. package/dist/editor/field-types/richtext/config/pluginFactory.js.map +1 -0
  54. package/dist/editor/field-types/richtext/hooks/useProfileCache.d.ts +68 -0
  55. package/dist/editor/field-types/richtext/hooks/useProfileCache.js +208 -0
  56. package/dist/editor/field-types/richtext/hooks/useProfileCache.js.map +1 -0
  57. package/dist/editor/field-types/richtext/hooks/useRichTextProfile.d.ts +25 -0
  58. package/dist/editor/field-types/richtext/hooks/useRichTextProfile.js +64 -0
  59. package/dist/editor/field-types/richtext/hooks/useRichTextProfile.js.map +1 -0
  60. package/dist/editor/field-types/richtext/index.d.ts +5 -0
  61. package/dist/editor/field-types/richtext/index.js +6 -0
  62. package/dist/editor/field-types/richtext/index.js.map +1 -0
  63. package/dist/editor/field-types/richtext/types.d.ts +139 -0
  64. package/dist/editor/field-types/richtext/types.js +107 -0
  65. package/dist/editor/field-types/richtext/types.js.map +1 -0
  66. package/dist/editor/field-types/richtext/utils/conversion.d.ts +5 -0
  67. package/dist/editor/field-types/richtext/utils/conversion.js +539 -0
  68. package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -0
  69. package/dist/editor/field-types/richtext/utils/plugins.d.ts +97 -0
  70. package/dist/editor/field-types/richtext/utils/plugins.js +272 -0
  71. package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -0
  72. package/dist/editor/field-types/richtext/utils/profileMapper.d.ts +38 -0
  73. package/dist/editor/field-types/richtext/utils/profileMapper.js +366 -0
  74. package/dist/editor/field-types/richtext/utils/profileMapper.js.map +1 -0
  75. package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +37 -0
  76. package/dist/editor/field-types/richtext/utils/profileServiceCache.js +117 -0
  77. package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -0
  78. package/dist/editor/media-selector/AiImageSearch.js +2 -5
  79. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  80. package/dist/editor/media-selector/MediaFolderBrowser.js +2 -5
  81. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  82. package/dist/editor/media-selector/TreeSelector.js +2 -5
  83. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  84. package/dist/editor/menubar/FavoritesControls.d.ts +8 -0
  85. package/dist/editor/menubar/FavoritesControls.js +124 -0
  86. package/dist/editor/menubar/FavoritesControls.js.map +1 -0
  87. package/dist/editor/menubar/ItemLanguageVersion.js +2 -1
  88. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  89. package/dist/editor/menubar/PageSelector.js +3 -6
  90. package/dist/editor/menubar/PageSelector.js.map +1 -1
  91. package/dist/editor/reviews/reviewCommands.js +3 -8
  92. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  93. package/dist/editor/services/contentService.d.ts +8 -0
  94. package/dist/editor/services/contentService.js +3 -0
  95. package/dist/editor/services/contentService.js.map +1 -1
  96. package/dist/editor/services/favouritesService.d.ts +33 -0
  97. package/dist/editor/services/favouritesService.js +22 -0
  98. package/dist/editor/services/favouritesService.js.map +1 -0
  99. package/dist/editor/sidebar/ComponentTree.js +7 -1
  100. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  101. package/dist/editor/sidebar/Debug.js +2 -2
  102. package/dist/editor/sidebar/Debug.js.map +1 -1
  103. package/dist/editor/sidebar/SEOInfo.js +4 -15
  104. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  105. package/dist/editor/ui/ItemSearch.js +2 -5
  106. package/dist/editor/ui/ItemSearch.js.map +1 -1
  107. package/dist/editor/ui/PerfectTree.d.ts +4 -2
  108. package/dist/editor/ui/PerfectTree.js +16 -7
  109. package/dist/editor/ui/PerfectTree.js.map +1 -1
  110. package/dist/editor/ui/Section.js +1 -1
  111. package/dist/editor/utils/itemutils.js +3 -1
  112. package/dist/editor/utils/itemutils.js.map +1 -1
  113. package/dist/editor/utils/keyboardNavigation.d.ts +32 -0
  114. package/dist/editor/utils/keyboardNavigation.js +156 -0
  115. package/dist/editor/utils/keyboardNavigation.js.map +1 -0
  116. package/dist/editor/views/ItemEditor.js +10 -3
  117. package/dist/editor/views/ItemEditor.js.map +1 -1
  118. package/dist/page-wizard/PageWizard.d.ts +2 -2
  119. package/dist/page-wizard/steps/ContentStep.js +7 -7
  120. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  121. package/dist/page-wizard/steps/schema.js +4 -2
  122. package/dist/page-wizard/steps/schema.js.map +1 -1
  123. package/dist/page-wizard/steps/usePageCreator.js +1 -1
  124. package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
  125. package/dist/revision.d.ts +2 -2
  126. package/dist/revision.js +2 -2
  127. package/dist/splash-screen/NewPage.js +7 -10
  128. package/dist/splash-screen/NewPage.js.map +1 -1
  129. package/dist/styles.css +34 -5
  130. package/package.json +6 -1
  131. package/src/components/ui/copy-button.tsx +75 -0
  132. package/src/components/ui/sonner.tsx +25 -0
  133. package/src/config/config.tsx +5 -19
  134. package/src/config/types.ts +6 -3
  135. package/src/editor/ComponentInfo.tsx +5 -4
  136. package/src/editor/ContentTree.tsx +5 -6
  137. package/src/editor/FieldListField.tsx +4 -25
  138. package/src/editor/ItemInfo.tsx +5 -5
  139. package/src/editor/ai/Agents.tsx +125 -0
  140. package/src/editor/ai/AiTerminal.tsx +4 -0
  141. package/src/editor/client/EditorClient.tsx +58 -119
  142. package/src/editor/client/editContext.ts +3 -2
  143. package/src/editor/commands/itemCommands.tsx +10 -25
  144. package/src/editor/component-designer/ComponentEditor.tsx +8 -10
  145. package/src/editor/field-types/InternalLinkFieldEditor.tsx +73 -69
  146. package/src/editor/field-types/RichTextEditor.tsx +40 -3
  147. package/src/editor/field-types/RichTextEditorComponent.tsx +74 -77
  148. package/src/editor/field-types/TreeListEditor.tsx +7 -7
  149. package/src/editor/field-types/richtext/components/EditorDropdown.css +81 -0
  150. package/src/editor/field-types/richtext/components/EditorDropdown.tsx +165 -0
  151. package/src/editor/field-types/richtext/components/ReactSlate.css +161 -0
  152. package/src/editor/field-types/richtext/components/ReactSlate.tsx +801 -0
  153. package/src/editor/field-types/richtext/components/ToolbarButton.tsx +23 -0
  154. package/src/editor/field-types/richtext/config/pluginFactory.tsx +22 -0
  155. package/src/editor/field-types/richtext/hooks/useProfileCache.ts +270 -0
  156. package/src/editor/field-types/richtext/hooks/useRichTextProfile.ts +94 -0
  157. package/src/editor/field-types/richtext/index.ts +5 -0
  158. package/src/editor/field-types/richtext/types.ts +269 -0
  159. package/src/editor/field-types/richtext/utils/conversion.ts +589 -0
  160. package/src/editor/field-types/richtext/utils/plugins.ts +346 -0
  161. package/src/editor/field-types/richtext/utils/profileMapper.ts +424 -0
  162. package/src/editor/field-types/richtext/utils/profileServiceCache.ts +154 -0
  163. package/src/editor/media-selector/AiImageSearch.tsx +2 -5
  164. package/src/editor/media-selector/MediaFolderBrowser.tsx +2 -5
  165. package/src/editor/media-selector/TreeSelector.tsx +2 -5
  166. package/src/editor/menubar/FavoritesControls.tsx +250 -0
  167. package/src/editor/menubar/ItemLanguageVersion.tsx +3 -1
  168. package/src/editor/menubar/PageSelector.tsx +76 -75
  169. package/src/editor/reviews/reviewCommands.tsx +3 -8
  170. package/src/editor/services/contentService.ts +12 -0
  171. package/src/editor/services/favouritesService.ts +60 -0
  172. package/src/editor/sidebar/ComponentTree.tsx +12 -1
  173. package/src/editor/sidebar/Debug.tsx +4 -3
  174. package/src/editor/sidebar/SEOInfo.tsx +6 -16
  175. package/src/editor/ui/ItemSearch.tsx +2 -5
  176. package/src/editor/ui/PerfectTree.tsx +19 -6
  177. package/src/editor/ui/Section.tsx +1 -1
  178. package/src/editor/utils/{itemutils.ts → itemutils.tsx} +12 -12
  179. package/src/editor/utils/keyboardNavigation.ts +234 -0
  180. package/src/editor/views/ItemEditor.tsx +22 -1
  181. package/src/page-wizard/PageWizard.tsx +2 -2
  182. package/src/page-wizard/steps/ContentStep.tsx +7 -9
  183. package/src/page-wizard/steps/schema.ts +10 -7
  184. package/src/page-wizard/steps/usePageCreator.ts +1 -0
  185. package/src/revision.ts +2 -2
  186. package/src/splash-screen/NewPage.tsx +28 -24
  187. package/dist/editor/ui/CopyToClipboardButton.d.ts +0 -3
  188. package/dist/editor/ui/CopyToClipboardButton.js +0 -16
  189. package/dist/editor/ui/CopyToClipboardButton.js.map +0 -1
  190. package/src/editor/ui/CopyToClipboardButton.tsx +0 -24
@@ -0,0 +1,250 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Star, ChevronDown, X } from "lucide-react";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuTrigger,
7
+ DropdownMenuItem,
8
+ DropdownMenuSeparator,
9
+ } from "../../components/ui/dropdown-menu";
10
+ import { Button } from "../../components/ui/button";
11
+ import {
12
+ Tooltip,
13
+ TooltipContent,
14
+ TooltipTrigger,
15
+ } from "../../components/ui/tooltip";
16
+ import * as favouritesService from "../services/favouritesService";
17
+ import { ItemDescriptor, FullItem } from "../pageModel";
18
+ import { EditContextType } from "../client/editContext";
19
+ import { toast } from "sonner";
20
+
21
+ interface FavoritesControlsProps {
22
+ itemDescriptor: ItemDescriptor;
23
+ editContext: EditContextType;
24
+ }
25
+
26
+ export function FavoritesControls({
27
+ itemDescriptor,
28
+ editContext,
29
+ }: FavoritesControlsProps) {
30
+ // Favorites state
31
+ const [isFavorite, setIsFavorite] = useState(false);
32
+ const [favorites, setFavorites] = useState<favouritesService.FavoriteItem[]>(
33
+ [],
34
+ );
35
+ const [favoritesLoading, setFavoritesLoading] = useState(false);
36
+ const [favoriteItems, setFavoriteItems] = useState<Map<string, FullItem>>(
37
+ new Map(),
38
+ );
39
+
40
+ // Check if current page is favorite by looking at the favorites list
41
+ const checkFavoriteStatus = () => {
42
+ if (!itemDescriptor) {
43
+ setIsFavorite(false);
44
+ return;
45
+ }
46
+
47
+ const isCurrentItemFavorite = favorites.some(
48
+ (favorite) =>
49
+ favorite.itemId === itemDescriptor.id &&
50
+ favorite.language === itemDescriptor.language,
51
+ );
52
+ setIsFavorite(isCurrentItemFavorite);
53
+ };
54
+
55
+ // Toggle favorite status
56
+ const toggleFavorite = async () => {
57
+ if (!itemDescriptor) return;
58
+
59
+ try {
60
+ const result = await favouritesService.toggleFavorite({
61
+ itemId: itemDescriptor.id,
62
+ language: itemDescriptor.language,
63
+ priority: 1, // Default priority
64
+ });
65
+
66
+ if (result.type === "success" && result.data) {
67
+ // Refresh favorites list (isFavorite will be updated automatically via useEffect)
68
+ loadFavorites();
69
+ } else {
70
+ }
71
+ } catch (error) {
72
+ toast.error("Failed to toggle favorite");
73
+ }
74
+ };
75
+
76
+ // Load all favorites
77
+ const loadFavorites = async () => {
78
+ setFavoritesLoading(true);
79
+ try {
80
+ const result = await favouritesService.getAllFavorites();
81
+
82
+ if (result.type === "success" && result.data) {
83
+ // Sort favorites by priority descending (highest priority first)
84
+ const sortedFavorites = result.data.sort(
85
+ (a, b) => b.priority - a.priority,
86
+ );
87
+ setFavorites(sortedFavorites);
88
+
89
+ // Load item details for each favorite
90
+ const itemMap = new Map<string, FullItem>();
91
+ for (const favorite of sortedFavorites) {
92
+ try {
93
+ const item = await editContext?.itemsRepository.getItem({
94
+ id: favorite.itemId,
95
+ language: favorite.language,
96
+ version: 0,
97
+ });
98
+ if (item) {
99
+ itemMap.set(`${favorite.itemId}-${favorite.language}`, item);
100
+ }
101
+ } catch (error) {
102
+ // Silently ignore item loading errors
103
+ }
104
+ }
105
+ setFavoriteItems(itemMap);
106
+ }
107
+ } catch (error) {
108
+ // Silently ignore favorites loading errors
109
+ } finally {
110
+ setFavoritesLoading(false);
111
+ }
112
+ };
113
+
114
+ // Navigate to favorite item
115
+ const navigateToFavorite = async (
116
+ favorite: favouritesService.FavoriteItem,
117
+ ) => {
118
+ editContext?.loadItem({
119
+ id: favorite.itemId,
120
+ language: favorite.language,
121
+ version: 0, // Use latest version
122
+ });
123
+ };
124
+
125
+ // Remove a specific favorite item
126
+ const removeFavorite = async (
127
+ favorite: favouritesService.FavoriteItem,
128
+ event: React.MouseEvent,
129
+ ) => {
130
+ event.stopPropagation(); // Prevent navigation when clicking remove button
131
+
132
+ try {
133
+ const result = await favouritesService.toggleFavorite({
134
+ itemId: favorite.itemId,
135
+ language: favorite.language,
136
+ priority: favorite.priority,
137
+ });
138
+
139
+ if (result.type === "success" && result.data) {
140
+ // Refresh favorites list (isFavorite will be updated automatically via useEffect)
141
+ loadFavorites();
142
+ } else {
143
+ toast.error("Failed to remove favorite");
144
+ }
145
+ } catch (error) {
146
+ toast.error("Failed to remove favorite");
147
+ }
148
+ };
149
+
150
+ useEffect(() => {
151
+ loadFavorites();
152
+ }, [itemDescriptor]);
153
+
154
+ // Check favorite status whenever favorites list or itemDescriptor changes
155
+ useEffect(() => {
156
+ checkFavoriteStatus();
157
+ }, [favorites, itemDescriptor]);
158
+
159
+ return (
160
+ <div className="flex items-center">
161
+ {/* Star button for toggling favorite */}
162
+ <Tooltip>
163
+ <TooltipTrigger asChild>
164
+ <Button
165
+ variant="ghost"
166
+ size="sm"
167
+ onClick={toggleFavorite}
168
+ className="h-8 w-7"
169
+ >
170
+ {isFavorite ? (
171
+ <Star
172
+ className="fill-theme-secondary text-theme-secondary h-4 w-4"
173
+ strokeWidth={1}
174
+ />
175
+ ) : (
176
+ <Star className="h-4 w-4 text-gray-400" strokeWidth={1} />
177
+ )}
178
+ </Button>
179
+ </TooltipTrigger>
180
+ <TooltipContent>
181
+ {isFavorite ? "Remove from favorites" : "Add to favorites"}
182
+ </TooltipContent>
183
+ </Tooltip>
184
+
185
+ {/* Favorites dropdown */}
186
+ <DropdownMenu>
187
+ <Tooltip>
188
+ <TooltipTrigger asChild>
189
+ <DropdownMenuTrigger asChild>
190
+ <Button variant="ghost" size="sm" className="h-8 w-7 p-0">
191
+ <ChevronDown className="h-4 w-4" strokeWidth={1} />
192
+ </Button>
193
+ </DropdownMenuTrigger>
194
+ </TooltipTrigger>
195
+ <TooltipContent>View favorites</TooltipContent>
196
+ </Tooltip>
197
+ <DropdownMenuContent align="start" className="w-80">
198
+ <div className="px-2 py-1.5 text-sm font-medium">Favorites</div>
199
+ <DropdownMenuSeparator />
200
+ {favoritesLoading ? (
201
+ <div className="px-2 py-4 text-center text-sm text-gray-500">
202
+ Loading favorites...
203
+ </div>
204
+ ) : favorites.length === 0 ? (
205
+ <div className="px-2 py-4 text-center text-sm text-gray-500">
206
+ No favorites yet
207
+ </div>
208
+ ) : (
209
+ <div className="max-h-60 overflow-auto">
210
+ {favorites.map((favorite) => {
211
+ const itemKey = `${favorite.itemId}-${favorite.language}`;
212
+ const item = favoriteItems.get(itemKey);
213
+
214
+ return (
215
+ <DropdownMenuItem
216
+ key={itemKey}
217
+ onClick={() => navigateToFavorite(favorite)}
218
+ className="flex items-start justify-between px-2 py-2"
219
+ >
220
+ <div className="flex min-w-0 flex-1 flex-col">
221
+ <div className="truncate text-sm font-medium">
222
+ {item?.name || favorite.itemId}
223
+ </div>
224
+ <div className="truncate text-xs text-gray-500">
225
+ {item?.path || favorite.language}
226
+ </div>
227
+ </div>
228
+ <Tooltip>
229
+ <TooltipTrigger asChild>
230
+ <Button
231
+ variant="ghost"
232
+ size="sm"
233
+ onClick={(e) => removeFavorite(favorite, e)}
234
+ className="ml-2 h-6 w-6 flex-shrink-0 p-0 opacity-70 hover:opacity-100"
235
+ >
236
+ <X className="h-3 w-3" />
237
+ </Button>
238
+ </TooltipTrigger>
239
+ <TooltipContent>Remove from favorites</TooltipContent>
240
+ </Tooltip>
241
+ </DropdownMenuItem>
242
+ );
243
+ })}
244
+ </div>
245
+ )}
246
+ </DropdownMenuContent>
247
+ </DropdownMenu>
248
+ </div>
249
+ );
250
+ }
@@ -8,6 +8,7 @@ import { NavButtons } from "./NavButtons";
8
8
  import { getItemDescriptor } from "../utils";
9
9
  import { confirmCreateVersion } from "../utils/itemutils";
10
10
  import { ItemActionsMenu } from "./ItemActionsMenu";
11
+ import { FavoritesControls } from "./FavoritesControls";
11
12
 
12
13
  export function ItemLanguageVersion() {
13
14
  const editContext = useEditContext();
@@ -66,8 +67,9 @@ export function ItemLanguageVersion() {
66
67
  return (
67
68
  <div className="flex items-center gap-3">
68
69
  <PageSelector itemDescriptor={item} />
69
- <ItemActionsMenu isMobile={false} />
70
70
 
71
+ <ItemActionsMenu isMobile={false} />
72
+ <FavoritesControls itemDescriptor={item} editContext={editContext} />
71
73
  {languageSelector}
72
74
  {versionSelector}
73
75
 
@@ -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);
@@ -226,3 +226,15 @@ export async function exportItems(request: ExportItemsRequest) {
226
226
  export async function importItems(request: ImportItemsRequest) {
227
227
  return await post<ImportItemsResult>("/alpaca/editor/importItems", request);
228
228
  }
229
+
230
+ export type RichTextProfileResponse = {
231
+ toolbars: Array<{
232
+ buttons: Array<{
233
+ name: string;
234
+ }>;
235
+ }>;
236
+ };
237
+
238
+ export async function getRichTextProfile(itemPath: string) {
239
+ return await post<RichTextProfileResponse>(`/alpaca/editor/RichTextProfile`, {profile: itemPath});
240
+ }
@@ -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
+ }
@@ -46,6 +46,14 @@ export function ComponentTree({}) {
46
46
 
47
47
  const treeRef = useRef<HTMLDivElement>(null);
48
48
 
49
+ // Helper function to clean placeholder labels by removing _{guid} pattern
50
+ function cleanPlaceholderLabel(label: string): string {
51
+ return label.replace(
52
+ /_{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}}$/,
53
+ "",
54
+ );
55
+ }
56
+
49
57
  // Helper function to check if a component has editable descendants
50
58
  // function hasEditableDescendants(component: Component): boolean {
51
59
  // if (!component.placeholders) return false;
@@ -148,10 +156,13 @@ export function ComponentTree({}) {
148
156
  p: Placeholder,
149
157
  parent: CustomTreeNode,
150
158
  ): CustomTreeNode {
159
+ const rawLabel = p.description || p.name;
160
+ const cleanedLabel = cleanPlaceholderLabel(rawLabel);
161
+
151
162
  const node: CustomTreeNode = {
152
163
  key: p.key,
153
164
  componentId: p.key,
154
- label: p.description || p.name,
165
+ label: cleanedLabel,
155
166
  icon: "pi pi-folder",
156
167
  data: p,
157
168
  parent: parent,
@@ -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);