@alpaca-editor/core 1.0.4103 → 1.0.4104
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/context-menu.d.ts +1 -1
- package/dist/components/ui/context-menu.js +8 -8
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/config/config.js +8 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/editor/ContentTree.js +15 -16
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/ContextMenu.js +37 -6
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/MainLayout.js +1 -1
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/ai/AgentHistory.js +1 -1
- package/dist/editor/ai/AgentTerminal.js +187 -13
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +1 -1
- package/dist/editor/ai/types.d.ts +25 -0
- package/dist/editor/ai/types.js +2 -0
- package/dist/editor/ai/types.js.map +1 -0
- package/dist/editor/client/EditorShell.js +29 -0
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +3 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/commands/agentCommands.d.ts +9 -0
- package/dist/editor/commands/agentCommands.js +30 -0
- package/dist/editor/commands/agentCommands.js.map +1 -0
- package/dist/editor/commands/itemCommands.js +14 -14
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/component-designer/aiContext.d.ts +1 -1
- package/dist/editor/context-menu/InsertMenu.d.ts +2 -1
- package/dist/editor/context-menu/InsertMenu.js +20 -15
- package/dist/editor/context-menu/InsertMenu.js.map +1 -1
- package/dist/editor/field-types/NameValueListEditor.d.ts +7 -0
- package/dist/editor/field-types/NameValueListEditor.js +99 -0
- package/dist/editor/field-types/NameValueListEditor.js.map +1 -0
- package/dist/editor/fieldTypes.d.ts +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +47 -10
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +3 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +30 -4
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +44 -5
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +22 -2
- package/dist/editor/services/agentService.js +26 -1
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +2 -1
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/sidebar/GraphQL.js +47 -90
- package/dist/editor/sidebar/GraphQL.js.map +1 -1
- package/dist/editor/ui/DragPreview.d.ts +13 -0
- package/dist/editor/ui/DragPreview.js +35 -0
- package/dist/editor/ui/DragPreview.js.map +1 -0
- package/dist/editor/ui/ItemNameDialogNew.js +2 -2
- package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
- package/dist/editor/ui/PerfectTree.js +3 -15
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +37 -9
- package/dist/tour/default-tour.js +34 -38
- package/dist/tour/default-tour.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/context-menu.tsx +31 -19
- package/src/config/config.tsx +9 -1
- package/src/config/types.ts +1 -0
- package/src/editor/ContentTree.tsx +13 -18
- package/src/editor/ContextMenu.tsx +112 -19
- package/src/editor/MainLayout.tsx +1 -1
- package/src/editor/ai/AgentHistory.tsx +2 -2
- package/src/editor/ai/AgentTerminal.tsx +226 -15
- package/src/editor/ai/AiResponseMessage.tsx +1 -1
- package/src/editor/ai/types.ts +27 -0
- package/src/editor/client/EditorShell.tsx +31 -0
- package/src/editor/client/editContext.ts +12 -1
- package/src/editor/commands/agentCommands.tsx +49 -0
- package/src/editor/commands/itemCommands.tsx +26 -14
- package/src/editor/component-designer/aiContext.ts +1 -1
- package/src/editor/context-menu/InsertMenu.tsx +64 -39
- package/src/editor/field-types/NameValueListEditor.tsx +197 -0
- package/src/editor/fieldTypes.ts +1 -1
- package/src/editor/page-editor-chrome/FrameMenu.tsx +61 -13
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +82 -20
- package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +77 -24
- package/src/editor/services/agentService.ts +53 -2
- package/src/editor/services/aiService.ts +3 -1
- package/src/editor/sidebar/GraphQL.tsx +50 -99
- package/src/editor/ui/DragPreview.tsx +44 -0
- package/src/editor/ui/ItemNameDialogNew.tsx +7 -3
- package/src/editor/ui/PerfectTree.tsx +2 -17
- package/src/revision.ts +2 -2
- package/src/tour/default-tour.tsx +34 -46
- package/dist/editor/ai/AiTerminal.d.ts +0 -47
- package/dist/editor/ai/AiTerminal.js +0 -300
- package/dist/editor/ai/AiTerminal.js.map +0 -1
- package/src/editor/ai/AiTerminal.tsx +0 -570
- package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx_ +0 -11
|
@@ -1621,6 +1621,16 @@ export function EditorShell({
|
|
|
1621
1621
|
|
|
1622
1622
|
const editContext = useMemo<EditContextType>(() => {
|
|
1623
1623
|
// console.log('🔄 EditContext useMemo is being recalculated');
|
|
1624
|
+
// Simple registry for per-view context factories (e.g., GraphQL context)
|
|
1625
|
+
// Stored outside the EditContext object via ref to avoid re-renders
|
|
1626
|
+
if (!(globalThis as any).__editorContextFactoriesRef) {
|
|
1627
|
+
(globalThis as any).__editorContextFactoriesRef = {
|
|
1628
|
+
map: new Map<string, () => Promise<any> | any>(),
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
const factoriesRef = (globalThis as any).__editorContextFactoriesRef as {
|
|
1632
|
+
map: Map<string, () => Promise<any> | any>;
|
|
1633
|
+
};
|
|
1624
1634
|
const context = {
|
|
1625
1635
|
operations: operationsContext.ops,
|
|
1626
1636
|
itemsRepository,
|
|
@@ -2069,6 +2079,27 @@ export function EditorShell({
|
|
|
2069
2079
|
setCurrentWizardId,
|
|
2070
2080
|
favorites,
|
|
2071
2081
|
loadFavorites,
|
|
2082
|
+
// Context factory registry methods
|
|
2083
|
+
registerContextFactory: (
|
|
2084
|
+
name: string,
|
|
2085
|
+
factory: () => Promise<any> | any,
|
|
2086
|
+
) => {
|
|
2087
|
+
try {
|
|
2088
|
+
factoriesRef.map.set(name, factory);
|
|
2089
|
+
} catch {}
|
|
2090
|
+
},
|
|
2091
|
+
unregisterContextFactory: (name: string) => {
|
|
2092
|
+
try {
|
|
2093
|
+
factoriesRef.map.delete(name);
|
|
2094
|
+
} catch {}
|
|
2095
|
+
},
|
|
2096
|
+
getContextFactory: (name: string) => {
|
|
2097
|
+
try {
|
|
2098
|
+
return factoriesRef.map.get(name);
|
|
2099
|
+
} catch {
|
|
2100
|
+
return undefined;
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2072
2103
|
};
|
|
2073
2104
|
|
|
2074
2105
|
return context as unknown as EditContextType;
|
|
@@ -176,7 +176,9 @@ export type EditContextType = {
|
|
|
176
176
|
showToast: (message: string) => void;
|
|
177
177
|
sessionId: string;
|
|
178
178
|
openSplashScreen: () => void;
|
|
179
|
-
getComponentCommands: (
|
|
179
|
+
getComponentCommands: (
|
|
180
|
+
components: Component[],
|
|
181
|
+
) => Promise<ComponentCommand[]>;
|
|
180
182
|
selectMedia: ({
|
|
181
183
|
selectedIdPath,
|
|
182
184
|
mode,
|
|
@@ -372,6 +374,15 @@ export type EditContextType = {
|
|
|
372
374
|
loadFavorites: () => Promise<void>;
|
|
373
375
|
currentWizardId: string | null;
|
|
374
376
|
setCurrentWizardId: (wizardId: string | null) => void;
|
|
377
|
+
|
|
378
|
+
// Context factory registry (optional): panels can register named factories
|
|
379
|
+
// that produce structured context objects for agent prompts.
|
|
380
|
+
registerContextFactory?: (
|
|
381
|
+
name: string,
|
|
382
|
+
factory: () => Promise<any> | any,
|
|
383
|
+
) => void;
|
|
384
|
+
unregisterContextFactory?: (name: string) => void;
|
|
385
|
+
getContextFactory?: (name: string) => (() => Promise<any> | any) | undefined;
|
|
375
386
|
};
|
|
376
387
|
|
|
377
388
|
const EditContext = React.createContext<EditContextType | undefined>(undefined);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Command, CommandContext } from "./commands";
|
|
3
|
+
import { Wand2 } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
export type CreateAgentCommandData = {
|
|
6
|
+
profileName?: string;
|
|
7
|
+
profileId?: string;
|
|
8
|
+
contextFactory?: string;
|
|
9
|
+
initialPrompt?: string;
|
|
10
|
+
additionalData?: Record<string, any>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const createAgentCommand: Command<CreateAgentCommandData> = {
|
|
14
|
+
id: "createAgent",
|
|
15
|
+
label: "Start Agent",
|
|
16
|
+
icon: <Wand2 strokeWidth={1} className="text-violet-600" />,
|
|
17
|
+
execute: async (context: CommandContext<CreateAgentCommandData>) => {
|
|
18
|
+
const edit = context.editContext;
|
|
19
|
+
try {
|
|
20
|
+
edit.setShowAgentsPanel?.(true);
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
profileName,
|
|
24
|
+
profileId,
|
|
25
|
+
contextFactory,
|
|
26
|
+
initialPrompt,
|
|
27
|
+
additionalData,
|
|
28
|
+
} = context.data || {};
|
|
29
|
+
|
|
30
|
+
const metadata = {
|
|
31
|
+
profile: profileName,
|
|
32
|
+
additionalData: {
|
|
33
|
+
...(additionalData || {}),
|
|
34
|
+
profileName,
|
|
35
|
+
profileId,
|
|
36
|
+
contextFactory,
|
|
37
|
+
initialPrompt,
|
|
38
|
+
},
|
|
39
|
+
} as any;
|
|
40
|
+
|
|
41
|
+
window.dispatchEvent(
|
|
42
|
+
new CustomEvent("editor:addNewAgent", { detail: { metadata } }),
|
|
43
|
+
);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("Failed to start agent", e);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
disabled: () => false,
|
|
49
|
+
};
|
|
@@ -16,7 +16,19 @@ import {
|
|
|
16
16
|
} from "../ui/CopyMoveTargetSelectorDialog";
|
|
17
17
|
import { defaultTranslateAll } from "./localizeItem/LocalizeItemUtils";
|
|
18
18
|
import { TranslationStatus } from "../../config/types";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
TriangleAlert,
|
|
21
|
+
Trash2,
|
|
22
|
+
Pencil,
|
|
23
|
+
Plus,
|
|
24
|
+
ArrowRight,
|
|
25
|
+
Copy,
|
|
26
|
+
UploadCloud,
|
|
27
|
+
Download,
|
|
28
|
+
Upload,
|
|
29
|
+
Globe,
|
|
30
|
+
CopyPlus,
|
|
31
|
+
} from "lucide-react";
|
|
20
32
|
|
|
21
33
|
export type ItemCommandData = CommandData & {
|
|
22
34
|
items: FullItem[];
|
|
@@ -29,7 +41,7 @@ export type ItemCommand = Command<ItemCommandData>;
|
|
|
29
41
|
export const deleteItemCommand: ItemCommand = {
|
|
30
42
|
id: "deleteItem",
|
|
31
43
|
label: "Delete",
|
|
32
|
-
icon:
|
|
44
|
+
icon: <Trash2 strokeWidth={1} />,
|
|
33
45
|
disabled: (context: ItemCommandContext) =>
|
|
34
46
|
!context.data?.items || !context.data?.items[0]?.canDelete || false,
|
|
35
47
|
execute: async (context: ItemCommandContext) => {
|
|
@@ -81,7 +93,7 @@ export const deleteItemCommand: ItemCommand = {
|
|
|
81
93
|
export const renameItemCommand: ItemCommand = {
|
|
82
94
|
id: "renameItem",
|
|
83
95
|
label: "Rename",
|
|
84
|
-
icon:
|
|
96
|
+
icon: <Pencil strokeWidth={1} />,
|
|
85
97
|
keyBinding: "F2",
|
|
86
98
|
disabled: (context: ItemCommandContext) =>
|
|
87
99
|
!context.data?.items ||
|
|
@@ -118,7 +130,7 @@ export type InsertItemCommand = Command<InsertItemCommandData>;
|
|
|
118
130
|
export const insertItemCommand: ItemCommand = {
|
|
119
131
|
id: "insertItem",
|
|
120
132
|
label: "Insert Item",
|
|
121
|
-
icon:
|
|
133
|
+
icon: <Plus strokeWidth={1} />,
|
|
122
134
|
disabled: (context: ItemCommandContext) =>
|
|
123
135
|
!context.data?.items ||
|
|
124
136
|
context.data.items.length !== 1 ||
|
|
@@ -175,7 +187,7 @@ export type MoveCopyItemsCommand = Command<MoveCopyItemsCommandData>;
|
|
|
175
187
|
export const moveItemsCommand: MoveCopyItemsCommand = {
|
|
176
188
|
id: "moveItems",
|
|
177
189
|
label: "Move Item(s)",
|
|
178
|
-
icon:
|
|
190
|
+
icon: <ArrowRight strokeWidth={1} />,
|
|
179
191
|
disabled: (context: MoveCopyItemsCommandContext) =>
|
|
180
192
|
!context.data?.items ||
|
|
181
193
|
context.data.items.some((x) => !x.canDelete) ||
|
|
@@ -215,7 +227,7 @@ export const moveItemsCommand: MoveCopyItemsCommand = {
|
|
|
215
227
|
export const copyItemsCommand: MoveCopyItemsCommand = {
|
|
216
228
|
id: "copyItems",
|
|
217
229
|
label: "Copy Item(s)",
|
|
218
|
-
icon:
|
|
230
|
+
icon: <Copy strokeWidth={1} />,
|
|
219
231
|
disabled: (context: MoveCopyItemsCommandContext) =>
|
|
220
232
|
!context.data?.items || false,
|
|
221
233
|
execute: async (context: MoveCopyItemsCommandContext) => {
|
|
@@ -259,7 +271,7 @@ export const copyItemsCommand: MoveCopyItemsCommand = {
|
|
|
259
271
|
export const duplicateItemCommand: MoveCopyItemsCommand = {
|
|
260
272
|
id: "duplicateItem",
|
|
261
273
|
label: "Duplicate Item",
|
|
262
|
-
icon:
|
|
274
|
+
icon: <CopyPlus strokeWidth={1} />,
|
|
263
275
|
disabled: (context: MoveCopyItemsCommandContext) =>
|
|
264
276
|
!context.data?.items || context.data.items.length !== 1 || false,
|
|
265
277
|
execute: async (context: MoveCopyItemsCommandContext) => {
|
|
@@ -316,7 +328,7 @@ export type LocalizeItemCommand = Command<LocalizeItemCommandData>;
|
|
|
316
328
|
export const localizeItemCommand: LocalizeItemCommand = {
|
|
317
329
|
id: "localizeItem",
|
|
318
330
|
label: "Localize",
|
|
319
|
-
icon:
|
|
331
|
+
icon: <Globe strokeWidth={1} />,
|
|
320
332
|
disabled: (context: LocalizeItemCommandContext) =>
|
|
321
333
|
!context.data?.items || context.data.items.length === 0 || false,
|
|
322
334
|
execute: async (context: LocalizeItemCommandContext) => {
|
|
@@ -344,7 +356,7 @@ export const localizeItemCommand: LocalizeItemCommand = {
|
|
|
344
356
|
export const publishItemCommand: ItemCommand = {
|
|
345
357
|
id: "publishItem",
|
|
346
358
|
label: "Publish",
|
|
347
|
-
icon:
|
|
359
|
+
icon: <UploadCloud strokeWidth={1} />,
|
|
348
360
|
disabled: (context: ItemCommandContext) =>
|
|
349
361
|
!context.data?.items ||
|
|
350
362
|
context.data.items.length !== 1 ||
|
|
@@ -360,7 +372,7 @@ export const publishItemCommand: ItemCommand = {
|
|
|
360
372
|
export const exportItemsCommand: ItemCommand = {
|
|
361
373
|
id: "exportItem",
|
|
362
374
|
label: "Export",
|
|
363
|
-
icon:
|
|
375
|
+
icon: <Download strokeWidth={1} />,
|
|
364
376
|
disabled: (context: ItemCommandContext) =>
|
|
365
377
|
!context.data?.items || context.data.items.length === 0 || false,
|
|
366
378
|
execute: async (context: ItemCommandContext) => {
|
|
@@ -393,7 +405,7 @@ export const exportItemsCommand: ItemCommand = {
|
|
|
393
405
|
</div>
|
|
394
406
|
),
|
|
395
407
|
header: "Export Items",
|
|
396
|
-
icon:
|
|
408
|
+
icon: <Download strokeWidth={1} />,
|
|
397
409
|
accept: () => {
|
|
398
410
|
const language =
|
|
399
411
|
(document.getElementById("export-language") as HTMLSelectElement)
|
|
@@ -456,7 +468,7 @@ export const exportItemsCommand: ItemCommand = {
|
|
|
456
468
|
</div>
|
|
457
469
|
),
|
|
458
470
|
header: "Export Results",
|
|
459
|
-
icon:
|
|
471
|
+
icon: <Copy strokeWidth={1} />,
|
|
460
472
|
accept: () => {},
|
|
461
473
|
rejectLabel: "Close",
|
|
462
474
|
showCancel: false,
|
|
@@ -478,7 +490,7 @@ export const exportItemsCommand: ItemCommand = {
|
|
|
478
490
|
export const importItemsCommand: ItemCommand = {
|
|
479
491
|
id: "importItems",
|
|
480
492
|
label: "Import",
|
|
481
|
-
icon:
|
|
493
|
+
icon: <Upload strokeWidth={1} />,
|
|
482
494
|
disabled: (context: ItemCommandContext) =>
|
|
483
495
|
!context.data?.items ||
|
|
484
496
|
context.data.items.length !== 1 ||
|
|
@@ -547,7 +559,7 @@ export const importItemsCommand: ItemCommand = {
|
|
|
547
559
|
context.editContext.confirm({
|
|
548
560
|
message: dialogContent,
|
|
549
561
|
header: "Import Items",
|
|
550
|
-
icon:
|
|
562
|
+
icon: <Upload strokeWidth={1} />,
|
|
551
563
|
accept: () => {
|
|
552
564
|
const textarea = document.getElementById(
|
|
553
565
|
"yaml-input",
|
|
@@ -9,15 +9,18 @@ import { SimpleTabs, Tab } from "../ui/SimpleTabs";
|
|
|
9
9
|
import ItemSearch, { ResultItem } from "../ui/ItemSearch";
|
|
10
10
|
import { templatesRootItemId } from "../../config/config";
|
|
11
11
|
import { Wizard } from "../../page-wizard/PageWizard";
|
|
12
|
+
import { ArrowRight, Plus } from "lucide-react";
|
|
12
13
|
|
|
13
14
|
export const InsertMenuTemplate = ({
|
|
14
15
|
insertOptions,
|
|
15
16
|
item,
|
|
16
17
|
commandCallback,
|
|
18
|
+
mode = "overlay",
|
|
17
19
|
}: {
|
|
18
20
|
insertOptions: ItemTreeNodeData[];
|
|
19
21
|
item: FullItem;
|
|
20
22
|
commandCallback?: (command: ItemCommand, result: any) => void;
|
|
23
|
+
mode?: "overlay" | "inline";
|
|
21
24
|
}) => {
|
|
22
25
|
const editContext = useEditContext();
|
|
23
26
|
const [filter, setFilter] = useState("");
|
|
@@ -155,7 +158,6 @@ export const InsertMenuTemplate = ({
|
|
|
155
158
|
id: x.id,
|
|
156
159
|
icon: (
|
|
157
160
|
<img
|
|
158
|
-
className="p-menuitem-icon"
|
|
159
161
|
src={x.icon}
|
|
160
162
|
style={{ height: "16px" }}
|
|
161
163
|
width="16"
|
|
@@ -171,38 +173,47 @@ export const InsertMenuTemplate = ({
|
|
|
171
173
|
label: "Options",
|
|
172
174
|
content: (
|
|
173
175
|
<div className="flex h-full flex-col gap-2">
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
176
|
+
{options ? (
|
|
177
|
+
<>
|
|
178
|
+
<FilterInput
|
|
179
|
+
ref={filterRef}
|
|
180
|
+
className="w-full text-xs"
|
|
181
|
+
placeholder="Filter"
|
|
182
|
+
value={filter}
|
|
183
|
+
onChange={setFilter}
|
|
184
|
+
/>
|
|
185
|
+
<div className="relative flex-1">
|
|
186
|
+
<div className="absolute inset-0 overflow-auto">
|
|
187
|
+
{options
|
|
188
|
+
.filter(
|
|
189
|
+
(x) =>
|
|
190
|
+
x.label.toLowerCase().indexOf(filter.toLowerCase()) >
|
|
191
|
+
-1,
|
|
192
|
+
)
|
|
193
|
+
.map((option: any) => (
|
|
194
|
+
<div
|
|
195
|
+
key={option.id}
|
|
196
|
+
className="flex cursor-pointer items-center gap-2 p-1 text-xs hover:bg-gray-100"
|
|
197
|
+
onClick={(ev) => {
|
|
198
|
+
ev.stopPropagation();
|
|
199
|
+
ev.preventDefault();
|
|
200
|
+
option.command(ev);
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{option.icon}
|
|
204
|
+
<span className="flex-1">
|
|
205
|
+
{highlightMatch(option.label, filter)}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</>
|
|
212
|
+
) : (
|
|
213
|
+
<div className="p-2 text-xs text-gray-500">
|
|
214
|
+
No options available.
|
|
204
215
|
</div>
|
|
205
|
-
|
|
216
|
+
)}
|
|
206
217
|
</div>
|
|
207
218
|
),
|
|
208
219
|
id: "options",
|
|
@@ -212,7 +223,7 @@ export const InsertMenuTemplate = ({
|
|
|
212
223
|
content: (
|
|
213
224
|
<div className="flex h-full flex-col">
|
|
214
225
|
{recentItems.length === 0 ? (
|
|
215
|
-
<div className="p-2 text-
|
|
226
|
+
<div className="p-2 text-xs text-gray-500">No recent templates</div>
|
|
216
227
|
) : (
|
|
217
228
|
recentItems.map((template) => (
|
|
218
229
|
<div
|
|
@@ -225,7 +236,6 @@ export const InsertMenuTemplate = ({
|
|
|
225
236
|
}}
|
|
226
237
|
>
|
|
227
238
|
<img
|
|
228
|
-
className="p-menuitem-icon"
|
|
229
239
|
src={template.icon}
|
|
230
240
|
style={{ height: "16px" }}
|
|
231
241
|
width="16"
|
|
@@ -311,7 +321,6 @@ export const InsertMenuTemplate = ({
|
|
|
311
321
|
}}
|
|
312
322
|
>
|
|
313
323
|
<img
|
|
314
|
-
className="p-menuitem-icon"
|
|
315
324
|
src={wizard.icon}
|
|
316
325
|
style={{ height: "16px" }}
|
|
317
326
|
width="16"
|
|
@@ -329,9 +338,25 @@ export const InsertMenuTemplate = ({
|
|
|
329
338
|
|
|
330
339
|
const isAnyTabLoading = isLoadingRecent || isLoadingWizards;
|
|
331
340
|
|
|
341
|
+
if (mode === "inline") {
|
|
342
|
+
return (
|
|
343
|
+
<div className="min-h-[380px] min-w-[450px] p-2">
|
|
344
|
+
<div className="flex flex-col text-xs">
|
|
345
|
+
<SimpleTabs
|
|
346
|
+
className="border-gray-3 mb-2 border-b"
|
|
347
|
+
tabs={tabs}
|
|
348
|
+
setActiveTab={setActiveTab}
|
|
349
|
+
activeTab={activeTab}
|
|
350
|
+
isLoading={isAnyTabLoading}
|
|
351
|
+
/>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
332
357
|
return (
|
|
333
358
|
<div
|
|
334
|
-
className="
|
|
359
|
+
className="flex items-center gap-2 text-xs"
|
|
335
360
|
style={{ display: "flex", alignItems: "center", cursor: "pointer" }}
|
|
336
361
|
onMouseEnter={() => {
|
|
337
362
|
if (hideTimeoutRef.current) {
|
|
@@ -350,9 +375,9 @@ export const InsertMenuTemplate = ({
|
|
|
350
375
|
ev.preventDefault();
|
|
351
376
|
}}
|
|
352
377
|
>
|
|
353
|
-
<
|
|
354
|
-
<span
|
|
355
|
-
{
|
|
378
|
+
<Plus strokeWidth={1} size={16} className="text-gray-2" />
|
|
379
|
+
<span>Insert</span>
|
|
380
|
+
<ArrowRight strokeWidth={1} size={16} className="text-gray-2 ml-auto" />
|
|
356
381
|
<div
|
|
357
382
|
ref={opRef}
|
|
358
383
|
className="absolute z-50 rounded-md bg-white p-2 shadow-lg"
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useEffect, useMemo, useState } from "react";
|
|
5
|
+
import { useEditContext } from "../client/editContext";
|
|
6
|
+
import { Button } from "../../components/ui/button";
|
|
7
|
+
import { Input } from "../../components/ui/input";
|
|
8
|
+
import { ArrowDown, ArrowUp, Plus, Trash2 } from "lucide-react";
|
|
9
|
+
import { EnhancedNameValueListField, KeyValuePair } from "../fieldTypes";
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
field: EnhancedNameValueListField;
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function parseUrlEncoded(raw?: string | null): KeyValuePair[] {
|
|
17
|
+
if (!raw) return [];
|
|
18
|
+
const parts = raw.split("&").filter((p) => p.length > 0);
|
|
19
|
+
const pairs: KeyValuePair[] = [];
|
|
20
|
+
for (const part of parts) {
|
|
21
|
+
const eqIndex = part.indexOf("=");
|
|
22
|
+
const rawKey = eqIndex >= 0 ? part.substring(0, eqIndex) : part;
|
|
23
|
+
const rawValue = eqIndex >= 0 ? part.substring(eqIndex + 1) : "";
|
|
24
|
+
// Convert + to space before decode to align with Sitecore's HttpUtility behavior
|
|
25
|
+
const key = decodeURIComponent(rawKey.replace(/\+/g, " "));
|
|
26
|
+
const value = decodeURIComponent(rawValue.replace(/\+/g, " "));
|
|
27
|
+
pairs.push({ key, value });
|
|
28
|
+
}
|
|
29
|
+
return pairs;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toUrlEncoded(pairs: KeyValuePair[]): string {
|
|
33
|
+
const filtered = pairs.filter((p) => (p.key || "").trim().length > 0);
|
|
34
|
+
const segments = filtered.map((p) => {
|
|
35
|
+
const encKey = encodeURIComponent(p.key);
|
|
36
|
+
const encValue = encodeURIComponent(p.value || "");
|
|
37
|
+
// Replace %20 with + for compatibility with typical form encoding in Sitecore tools
|
|
38
|
+
return `${encKey.replace(/%20/g, "+")}=${encValue.replace(/%20/g, "+")}`;
|
|
39
|
+
});
|
|
40
|
+
return segments.join("&");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function NameValueListEditor({ field, readOnly }: Props) {
|
|
44
|
+
const editContext = useEditContext();
|
|
45
|
+
const [pairs, setPairs] = useState<KeyValuePair[]>([]);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
// Prefer typed value, fallback to raw
|
|
49
|
+
const fromValue = (
|
|
50
|
+
Array.isArray(field.value) ? (field.value as KeyValuePair[]) : undefined
|
|
51
|
+
) as KeyValuePair[] | undefined;
|
|
52
|
+
if (fromValue && fromValue.length >= 0) {
|
|
53
|
+
setPairs(
|
|
54
|
+
fromValue.map((x) => ({ key: x.key || "", value: x.value || "" })),
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
setPairs(parseUrlEncoded(field.rawValue));
|
|
58
|
+
}
|
|
59
|
+
}, [field.value, field.rawValue, field.descriptor.item.id, field.id]);
|
|
60
|
+
|
|
61
|
+
if (!editContext) return null;
|
|
62
|
+
|
|
63
|
+
const commit = async (next: KeyValuePair[]) => {
|
|
64
|
+
const cleaned = next.filter((p) => (p.key || "").trim().length > 0);
|
|
65
|
+
const raw = toUrlEncoded(cleaned);
|
|
66
|
+
setPairs(next);
|
|
67
|
+
await editContext.operations.editField({
|
|
68
|
+
field: field.descriptor,
|
|
69
|
+
value: cleaned,
|
|
70
|
+
rawValue: raw,
|
|
71
|
+
refresh: "waitForQuietPeriod",
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const addRow = async () => {
|
|
76
|
+
if (readOnly) return;
|
|
77
|
+
const next = [...pairs, { key: "", value: "" }];
|
|
78
|
+
setPairs(next);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const updateKey = async (index: number, key: string) => {
|
|
82
|
+
if (readOnly) return;
|
|
83
|
+
const next = pairs.map((p, i) => (i === index ? { ...p, key } : p));
|
|
84
|
+
setPairs(next);
|
|
85
|
+
await commit(next);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const updateValue = async (index: number, value: string) => {
|
|
89
|
+
if (readOnly) return;
|
|
90
|
+
const next = pairs.map((p, i) => (i === index ? { ...p, value } : p));
|
|
91
|
+
setPairs(next);
|
|
92
|
+
await commit(next);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const removeRow = async (index: number) => {
|
|
96
|
+
if (readOnly) return;
|
|
97
|
+
const next = pairs.filter((_, i) => i !== index);
|
|
98
|
+
await commit(next);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const moveRow = async (index: number, offset: number) => {
|
|
102
|
+
if (readOnly) return;
|
|
103
|
+
const newIndex = index + offset;
|
|
104
|
+
if (newIndex < 0 || newIndex >= pairs.length) return;
|
|
105
|
+
const next = [...pairs];
|
|
106
|
+
const [item] = next.splice(index, 1);
|
|
107
|
+
next.splice(newIndex, 0, item);
|
|
108
|
+
await commit(next);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
className={`focus-shadow rounded-sm border border-gray-200 ${readOnly ? "bg-gray-5" : "bg-white"}`}
|
|
114
|
+
>
|
|
115
|
+
<div className="flex items-center justify-between border-b border-gray-200 px-2 py-1.5">
|
|
116
|
+
<Button
|
|
117
|
+
type="button"
|
|
118
|
+
size="sm"
|
|
119
|
+
variant="ghost"
|
|
120
|
+
disabled={readOnly}
|
|
121
|
+
onClick={addRow}
|
|
122
|
+
className="h-7 px-2"
|
|
123
|
+
aria-label="Add entry"
|
|
124
|
+
>
|
|
125
|
+
<Plus className="h-4 w-4" strokeWidth={1} />
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
{pairs.length === 0 && (
|
|
130
|
+
<div className="p-2 text-center text-xs text-gray-500">
|
|
131
|
+
No entries
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
{pairs.map((pair, index) => (
|
|
135
|
+
<div
|
|
136
|
+
key={index}
|
|
137
|
+
className="flex items-center gap-1 border-b border-gray-100 p-1 last:border-b-0"
|
|
138
|
+
>
|
|
139
|
+
<Input
|
|
140
|
+
value={pair.key}
|
|
141
|
+
placeholder="Name"
|
|
142
|
+
disabled={readOnly}
|
|
143
|
+
className="bg-gray-5 h-7 p-1 text-xs"
|
|
144
|
+
onChange={(e) => updateKey(index, e.target.value)}
|
|
145
|
+
/>
|
|
146
|
+
<span className="px-1 text-gray-400">=</span>
|
|
147
|
+
<Input
|
|
148
|
+
value={pair.value}
|
|
149
|
+
placeholder="Value"
|
|
150
|
+
disabled={readOnly}
|
|
151
|
+
className="bg-gray-5 h-7 p-1 text-xs"
|
|
152
|
+
onChange={(e) => updateValue(index, e.target.value)}
|
|
153
|
+
/>
|
|
154
|
+
<div className="ml-auto flex items-center gap-0.5">
|
|
155
|
+
<Button
|
|
156
|
+
type="button"
|
|
157
|
+
size="icon"
|
|
158
|
+
variant="ghost"
|
|
159
|
+
disabled={readOnly || index === 0}
|
|
160
|
+
onClick={() => moveRow(index, -1)}
|
|
161
|
+
className="h-7 w-7"
|
|
162
|
+
aria-label="Move up"
|
|
163
|
+
title="Move up"
|
|
164
|
+
>
|
|
165
|
+
<ArrowUp className="h-4 w-4" strokeWidth={1} />
|
|
166
|
+
</Button>
|
|
167
|
+
<Button
|
|
168
|
+
type="button"
|
|
169
|
+
size="icon"
|
|
170
|
+
variant="ghost"
|
|
171
|
+
disabled={readOnly || index === pairs.length - 1}
|
|
172
|
+
onClick={() => moveRow(index, 1)}
|
|
173
|
+
className="h-7 w-7"
|
|
174
|
+
aria-label="Move down"
|
|
175
|
+
title="Move down"
|
|
176
|
+
>
|
|
177
|
+
<ArrowDown className="h-4 w-4" strokeWidth={1} />
|
|
178
|
+
</Button>
|
|
179
|
+
<Button
|
|
180
|
+
type="button"
|
|
181
|
+
size="icon"
|
|
182
|
+
variant="ghost"
|
|
183
|
+
disabled={readOnly}
|
|
184
|
+
onClick={() => removeRow(index)}
|
|
185
|
+
className="h-7 w-7 text-red-600 hover:text-red-700"
|
|
186
|
+
aria-label="Delete"
|
|
187
|
+
title="Delete"
|
|
188
|
+
>
|
|
189
|
+
<Trash2 className="h-4 w-4" strokeWidth={1} />
|
|
190
|
+
</Button>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|