@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.
- package/dist/components/ui/copy-button.d.ts +10 -0
- package/dist/components/ui/copy-button.js +33 -0
- package/dist/components/ui/copy-button.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +3 -0
- package/dist/components/ui/sonner.js +14 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/config/config.js +4 -16
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +6 -3
- package/dist/editor/ComponentInfo.js +2 -2
- package/dist/editor/ComponentInfo.js.map +1 -1
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +4 -8
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/FieldListField.js +3 -10
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ItemInfo.js +3 -3
- package/dist/editor/ItemInfo.js.map +1 -1
- package/dist/editor/ai/Agents.d.ts +6 -0
- package/dist/editor/ai/Agents.js +48 -0
- package/dist/editor/ai/Agents.js.map +1 -0
- package/dist/editor/ai/AiTerminal.js +4 -2
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/client/EditorClient.js +48 -91
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +3 -2
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/commands/itemCommands.js +5 -24
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/component-designer/ComponentEditor.js +3 -5
- package/dist/editor/component-designer/ComponentEditor.js.map +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js +20 -25
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditor.d.ts +4 -3
- package/dist/editor/field-types/RichTextEditor.js +16 -3
- package/dist/editor/field-types/RichTextEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.d.ts +6 -5
- package/dist/editor/field-types/RichTextEditorComponent.js +59 -60
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +7 -5
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/field-types/richtext/components/EditorDropdown.d.ts +11 -0
- package/dist/editor/field-types/richtext/components/EditorDropdown.js +83 -0
- package/dist/editor/field-types/richtext/components/EditorDropdown.js.map +1 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +5 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.js +562 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -0
- package/dist/editor/field-types/richtext/components/ToolbarButton.d.ts +3 -0
- package/dist/editor/field-types/richtext/components/ToolbarButton.js +13 -0
- package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -0
- package/dist/editor/field-types/richtext/config/pluginFactory.d.ts +17 -0
- package/dist/editor/field-types/richtext/config/pluginFactory.js +14 -0
- package/dist/editor/field-types/richtext/config/pluginFactory.js.map +1 -0
- package/dist/editor/field-types/richtext/hooks/useProfileCache.d.ts +68 -0
- package/dist/editor/field-types/richtext/hooks/useProfileCache.js +208 -0
- package/dist/editor/field-types/richtext/hooks/useProfileCache.js.map +1 -0
- package/dist/editor/field-types/richtext/hooks/useRichTextProfile.d.ts +25 -0
- package/dist/editor/field-types/richtext/hooks/useRichTextProfile.js +64 -0
- package/dist/editor/field-types/richtext/hooks/useRichTextProfile.js.map +1 -0
- package/dist/editor/field-types/richtext/index.d.ts +5 -0
- package/dist/editor/field-types/richtext/index.js +6 -0
- package/dist/editor/field-types/richtext/index.js.map +1 -0
- package/dist/editor/field-types/richtext/types.d.ts +139 -0
- package/dist/editor/field-types/richtext/types.js +107 -0
- package/dist/editor/field-types/richtext/types.js.map +1 -0
- package/dist/editor/field-types/richtext/utils/conversion.d.ts +5 -0
- package/dist/editor/field-types/richtext/utils/conversion.js +539 -0
- package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -0
- package/dist/editor/field-types/richtext/utils/plugins.d.ts +97 -0
- package/dist/editor/field-types/richtext/utils/plugins.js +272 -0
- package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -0
- package/dist/editor/field-types/richtext/utils/profileMapper.d.ts +38 -0
- package/dist/editor/field-types/richtext/utils/profileMapper.js +366 -0
- package/dist/editor/field-types/richtext/utils/profileMapper.js.map +1 -0
- package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +37 -0
- package/dist/editor/field-types/richtext/utils/profileServiceCache.js +117 -0
- package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -0
- package/dist/editor/media-selector/AiImageSearch.js +2 -5
- package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +2 -5
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +2 -5
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/FavoritesControls.d.ts +8 -0
- package/dist/editor/menubar/FavoritesControls.js +124 -0
- package/dist/editor/menubar/FavoritesControls.js.map +1 -0
- package/dist/editor/menubar/ItemLanguageVersion.js +2 -1
- package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +3 -6
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/reviews/reviewCommands.js +3 -8
- package/dist/editor/reviews/reviewCommands.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +8 -0
- package/dist/editor/services/contentService.js +3 -0
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/favouritesService.d.ts +33 -0
- package/dist/editor/services/favouritesService.js +22 -0
- package/dist/editor/services/favouritesService.js.map +1 -0
- package/dist/editor/sidebar/ComponentTree.js +7 -1
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/Debug.js +2 -2
- package/dist/editor/sidebar/Debug.js.map +1 -1
- package/dist/editor/sidebar/SEOInfo.js +4 -15
- package/dist/editor/sidebar/SEOInfo.js.map +1 -1
- package/dist/editor/ui/ItemSearch.js +2 -5
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/PerfectTree.d.ts +4 -2
- package/dist/editor/ui/PerfectTree.js +16 -7
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/Section.js +1 -1
- package/dist/editor/utils/itemutils.js +3 -1
- package/dist/editor/utils/itemutils.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.d.ts +32 -0
- package/dist/editor/utils/keyboardNavigation.js +156 -0
- package/dist/editor/utils/keyboardNavigation.js.map +1 -0
- package/dist/editor/views/ItemEditor.js +10 -3
- package/dist/editor/views/ItemEditor.js.map +1 -1
- package/dist/page-wizard/PageWizard.d.ts +2 -2
- package/dist/page-wizard/steps/ContentStep.js +7 -7
- package/dist/page-wizard/steps/ContentStep.js.map +1 -1
- package/dist/page-wizard/steps/schema.js +4 -2
- package/dist/page-wizard/steps/schema.js.map +1 -1
- package/dist/page-wizard/steps/usePageCreator.js +1 -1
- package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +7 -10
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/styles.css +34 -5
- package/package.json +6 -1
- package/src/components/ui/copy-button.tsx +75 -0
- package/src/components/ui/sonner.tsx +25 -0
- package/src/config/config.tsx +5 -19
- package/src/config/types.ts +6 -3
- package/src/editor/ComponentInfo.tsx +5 -4
- package/src/editor/ContentTree.tsx +5 -6
- package/src/editor/FieldListField.tsx +4 -25
- package/src/editor/ItemInfo.tsx +5 -5
- package/src/editor/ai/Agents.tsx +125 -0
- package/src/editor/ai/AiTerminal.tsx +4 -0
- package/src/editor/client/EditorClient.tsx +58 -119
- package/src/editor/client/editContext.ts +3 -2
- package/src/editor/commands/itemCommands.tsx +10 -25
- package/src/editor/component-designer/ComponentEditor.tsx +8 -10
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +73 -69
- package/src/editor/field-types/RichTextEditor.tsx +40 -3
- package/src/editor/field-types/RichTextEditorComponent.tsx +74 -77
- package/src/editor/field-types/TreeListEditor.tsx +7 -7
- package/src/editor/field-types/richtext/components/EditorDropdown.css +81 -0
- package/src/editor/field-types/richtext/components/EditorDropdown.tsx +165 -0
- package/src/editor/field-types/richtext/components/ReactSlate.css +161 -0
- package/src/editor/field-types/richtext/components/ReactSlate.tsx +801 -0
- package/src/editor/field-types/richtext/components/ToolbarButton.tsx +23 -0
- package/src/editor/field-types/richtext/config/pluginFactory.tsx +22 -0
- package/src/editor/field-types/richtext/hooks/useProfileCache.ts +270 -0
- package/src/editor/field-types/richtext/hooks/useRichTextProfile.ts +94 -0
- package/src/editor/field-types/richtext/index.ts +5 -0
- package/src/editor/field-types/richtext/types.ts +269 -0
- package/src/editor/field-types/richtext/utils/conversion.ts +589 -0
- package/src/editor/field-types/richtext/utils/plugins.ts +346 -0
- package/src/editor/field-types/richtext/utils/profileMapper.ts +424 -0
- package/src/editor/field-types/richtext/utils/profileServiceCache.ts +154 -0
- package/src/editor/media-selector/AiImageSearch.tsx +2 -5
- package/src/editor/media-selector/MediaFolderBrowser.tsx +2 -5
- package/src/editor/media-selector/TreeSelector.tsx +2 -5
- package/src/editor/menubar/FavoritesControls.tsx +250 -0
- package/src/editor/menubar/ItemLanguageVersion.tsx +3 -1
- package/src/editor/menubar/PageSelector.tsx +76 -75
- package/src/editor/reviews/reviewCommands.tsx +3 -8
- package/src/editor/services/contentService.ts +12 -0
- package/src/editor/services/favouritesService.ts +60 -0
- package/src/editor/sidebar/ComponentTree.tsx +12 -1
- package/src/editor/sidebar/Debug.tsx +4 -3
- package/src/editor/sidebar/SEOInfo.tsx +6 -16
- package/src/editor/ui/ItemSearch.tsx +2 -5
- package/src/editor/ui/PerfectTree.tsx +19 -6
- package/src/editor/ui/Section.tsx +1 -1
- package/src/editor/utils/{itemutils.ts → itemutils.tsx} +12 -12
- package/src/editor/utils/keyboardNavigation.ts +234 -0
- package/src/editor/views/ItemEditor.tsx +22 -1
- package/src/page-wizard/PageWizard.tsx +2 -2
- package/src/page-wizard/steps/ContentStep.tsx +7 -9
- package/src/page-wizard/steps/schema.ts +10 -7
- package/src/page-wizard/steps/usePageCreator.ts +1 -0
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +28 -24
- package/dist/editor/ui/CopyToClipboardButton.d.ts +0 -3
- package/dist/editor/ui/CopyToClipboardButton.js +0 -16
- package/dist/editor/ui/CopyToClipboardButton.js.map +0 -1
- 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
|
-
|
|
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
|
-
<
|
|
183
|
-
<
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<div className="
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
)}
|
|
245
|
+
)}
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
</
|
|
258
|
-
</
|
|
259
|
-
</
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
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
|
-
<
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|