@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.
- 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 -4
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ComponentInfo.js +2 -2
- package/dist/editor/ComponentInfo.js.map +1 -1
- package/dist/editor/ContentTree.js +2 -6
- 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/RichTextEditor.d.ts +2 -1
- package/dist/editor/field-types/RichTextEditor.js +2 -2
- package/dist/editor/field-types/RichTextEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.d.ts +2 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +25 -15
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/richtext/components/EditorDropdown.d.ts +2 -2
- package/dist/editor/field-types/richtext/components/EditorDropdown.js +40 -53
- package/dist/editor/field-types/richtext/components/EditorDropdown.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +8 -4
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/profileMapper.js +4 -4
- package/dist/editor/field-types/richtext/utils/profileMapper.js.map +1 -1
- 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/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/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/Section.js +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/page-wizard/PageWizard.d.ts +2 -2
- package/dist/page-wizard/steps/ContentStep.js +6 -4
- 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/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 +25 -5
- package/package.json +3 -1
- package/src/components/ui/copy-button.tsx +75 -0
- package/src/components/ui/sonner.tsx +25 -0
- package/src/config/config.tsx +5 -7
- package/src/editor/ComponentInfo.tsx +5 -4
- package/src/editor/ContentTree.tsx +2 -6
- package/src/editor/FieldListField.tsx +4 -25
- package/src/editor/ItemInfo.tsx +4 -4
- 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/RichTextEditor.tsx +10 -1
- package/src/editor/field-types/RichTextEditorComponent.tsx +25 -11
- package/src/editor/field-types/richtext/components/EditorDropdown.css +81 -0
- package/src/editor/field-types/richtext/components/EditorDropdown.tsx +57 -72
- package/src/editor/field-types/richtext/components/ReactSlate.css +1 -3
- package/src/editor/field-types/richtext/components/ReactSlate.tsx +13 -4
- package/src/editor/field-types/richtext/utils/profileMapper.ts +4 -4
- 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/favouritesService.ts +60 -0
- 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/Section.tsx +1 -1
- package/src/editor/utils/keyboardNavigation.ts +234 -0
- package/src/page-wizard/PageWizard.tsx +2 -2
- package/src/page-wizard/steps/ContentStep.tsx +6 -6
- package/src/page-wizard/steps/schema.ts +10 -7
- 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
|
@@ -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);
|
|
@@ -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 {
|
|
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);
|
|
@@ -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-[
|
|
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
|
-
|
|
40
|
-
allowedChildrenComponentTypes
|
|
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
|
-
|
|
513
|
-
|
|
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,
|