@alpaca-editor/core 1.0.4172 → 1.0.4174
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/agents-view/AgentsView.d.ts +5 -0
- package/dist/agents-view/AgentsView.js +213 -0
- package/dist/agents-view/AgentsView.js.map +1 -0
- package/dist/components/ui/context-menu.js +4 -4
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/config/config.js +56 -1
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +2 -1
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +18 -3
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/ContextMenu.js +1 -1
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/FieldList.js +7 -3
- package/dist/editor/FieldList.js.map +1 -1
- package/dist/editor/FieldListField.d.ts +3 -2
- package/dist/editor/FieldListField.js +4 -4
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.d.ts +2 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +5 -2
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/MainLayout.js +1 -1
- package/dist/editor/QuickItemSwitcher.d.ts +9 -0
- package/dist/editor/QuickItemSwitcher.js +74 -0
- package/dist/editor/QuickItemSwitcher.js.map +1 -0
- package/dist/editor/ai/AgentCostDisplay.js +7 -11
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentProfilesOverview.d.ts +9 -0
- package/dist/editor/ai/AgentProfilesOverview.js +16 -0
- package/dist/editor/ai/AgentProfilesOverview.js.map +1 -0
- package/dist/editor/ai/AgentTerminal.js +285 -748
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +112 -54
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
- package/dist/editor/ai/AiResponseMessage.js +4 -2
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.js +17 -17
- package/dist/editor/ai/useAgentStatus.js +7 -0
- package/dist/editor/ai/useAgentStatus.js.map +1 -1
- package/dist/editor/client/EditorShell.js +230 -4
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +0 -12
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.js +1 -1
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/commands/itemCommands.js +1 -0
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/control-center/parhelia-setup/Overview.d.ts +1 -0
- package/dist/editor/control-center/parhelia-setup/Overview.js +91 -0
- package/dist/editor/control-center/parhelia-setup/Overview.js.map +1 -0
- package/dist/editor/field-types/AttachmentEditor.js +3 -6
- package/dist/editor/field-types/AttachmentEditor.js.map +1 -1
- package/dist/editor/field-types/DropLinkEditor.js +2 -2
- package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
- package/dist/editor/field-types/DropListEditor.js +2 -1
- package/dist/editor/field-types/DropListEditor.js.map +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -3
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.d.ts +2 -1
- package/dist/editor/field-types/MultiLineText.js +2 -2
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RawEditor.d.ts +2 -1
- package/dist/editor/field-types/RawEditor.js +2 -2
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.d.ts +2 -1
- package/dist/editor/field-types/SingleLineText.js +2 -2
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +9 -7
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +68 -7
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +1 -1
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/ActiveUsers.js +2 -2
- package/dist/editor/menubar/FavoritesControls.js +2 -2
- package/dist/editor/menubar/FavoritesControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +10 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +14 -0
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +21 -3
- package/dist/editor/services/agentService.js +24 -8
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +6 -1
- package/dist/editor/services/aiService.js +36 -5
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +1 -1
- package/dist/editor/services/contentService.js +4 -2
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +5 -1
- package/dist/editor/services/editService.js +1 -1
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/services/setupService.d.ts +21 -0
- package/dist/editor/services/setupService.js +10 -0
- package/dist/editor/services/setupService.js.map +1 -0
- package/dist/editor/sidebar/ComponentTree.js +15 -1
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.js +1 -1
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/ui/ItemSearch.d.ts +1 -0
- package/dist/editor/ui/ItemSearch.js +2 -2
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/PerfectTree.d.ts +5 -1
- package/dist/editor/ui/PerfectTree.js +308 -29
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.d.ts +2 -0
- package/dist/editor/utils/keyboardNavigation.js +80 -2
- package/dist/editor/utils/keyboardNavigation.js.map +1 -1
- package/dist/editor/views/SingleEditView.js +6 -4
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/SplashScreen.js +78 -4
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +157 -23
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
- package/src/agents-view/AgentsView.tsx +431 -0
- package/src/components/ui/context-menu.tsx +4 -4
- package/src/config/config.tsx +61 -0
- package/src/editor/ConfirmationDialog.tsx +42 -10
- package/src/editor/ContentTree.tsx +20 -1
- package/src/editor/ContextMenu.tsx +4 -1
- package/src/editor/FieldList.tsx +10 -4
- package/src/editor/FieldListField.tsx +7 -0
- package/src/editor/FieldListFieldWithFallbacks.tsx +10 -0
- package/src/editor/MainLayout.tsx +1 -1
- package/src/editor/QuickItemSwitcher.tsx +217 -0
- package/src/editor/ai/AgentCostDisplay.tsx +59 -60
- package/src/editor/ai/AgentProfilesOverview.tsx +81 -0
- package/src/editor/ai/AgentTerminal.tsx +321 -775
- package/src/editor/ai/Agents.tsx +157 -91
- package/src/editor/ai/AiResponseMessage.tsx +12 -1
- package/src/editor/ai/ContextInfoBar.tsx +17 -17
- package/src/editor/ai/useAgentStatus.ts +6 -0
- package/src/editor/client/EditorShell.tsx +288 -3
- package/src/editor/client/hooks/useSocketMessageHandler.ts +0 -15
- package/src/editor/client/ui/EditorChrome.tsx +1 -1
- package/src/editor/commands/itemCommands.tsx +1 -0
- package/src/editor/control-center/parhelia-setup/Overview.tsx +184 -0
- package/src/editor/field-types/AttachmentEditor.tsx +13 -50
- package/src/editor/field-types/DropLinkEditor.tsx +2 -2
- package/src/editor/field-types/DropListEditor.tsx +2 -1
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -3
- package/src/editor/field-types/MultiLineText.tsx +3 -0
- package/src/editor/field-types/RawEditor.tsx +3 -0
- package/src/editor/field-types/SingleLineText.tsx +3 -0
- package/src/editor/field-types/TreeListEditor.tsx +32 -24
- package/src/editor/media-selector/MediaFolderBrowser.tsx +93 -9
- package/src/editor/media-selector/TreeSelector.tsx +1 -0
- package/src/editor/menubar/ActiveUsers.tsx +2 -2
- package/src/editor/menubar/FavoritesControls.tsx +5 -5
- package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
- package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +17 -0
- package/src/editor/services/agentService.ts +46 -11
- package/src/editor/services/aiService.ts +48 -7
- package/src/editor/services/contentService.ts +4 -1
- package/src/editor/services/editService.ts +8 -3
- package/src/editor/services/setupService.ts +35 -0
- package/src/editor/sidebar/ComponentTree.tsx +16 -1
- package/src/editor/sidebar/SidebarView.tsx +1 -1
- package/src/editor/ui/ItemSearch.tsx +3 -1
- package/src/editor/ui/PerfectTree.tsx +393 -42
- package/src/editor/utils/keyboardNavigation.ts +97 -1
- package/src/editor/views/SingleEditView.tsx +27 -13
- package/src/revision.ts +2 -2
- package/src/splash-screen/SplashScreen.tsx +134 -2
- package/src/types.ts +15 -0
|
@@ -81,6 +81,7 @@ import {
|
|
|
81
81
|
EditSession,
|
|
82
82
|
FieldDescriptor,
|
|
83
83
|
HistoryEntry,
|
|
84
|
+
NavigationHistoryEntry,
|
|
84
85
|
InsertOption,
|
|
85
86
|
LanguageVersions,
|
|
86
87
|
LinkComponentOperation,
|
|
@@ -120,6 +121,7 @@ import { Toaster } from "../../components/ui/sonner";
|
|
|
120
121
|
|
|
121
122
|
import { Tour } from "../../tour/Tour";
|
|
122
123
|
import { usePageViewContext } from "../page-viewer/pageViewContext";
|
|
124
|
+
import { QuickItemSwitcher } from "../QuickItemSwitcher";
|
|
123
125
|
|
|
124
126
|
import {
|
|
125
127
|
getComments,
|
|
@@ -373,6 +375,36 @@ export function EditorShell({
|
|
|
373
375
|
);
|
|
374
376
|
});
|
|
375
377
|
|
|
378
|
+
// Navigation history for browser history (view + item combinations)
|
|
379
|
+
const [navigationHistory, setNavigationHistory] = useState<
|
|
380
|
+
NavigationHistoryEntry[]
|
|
381
|
+
>(() => {
|
|
382
|
+
// Initialize from browse history with current view
|
|
383
|
+
if (!userInfo.browseHistory) return [];
|
|
384
|
+
|
|
385
|
+
return userInfo.browseHistory.map(
|
|
386
|
+
(entry): NavigationHistoryEntry => ({
|
|
387
|
+
viewName:
|
|
388
|
+
searchParams.get("view") ??
|
|
389
|
+
configuration.editor.views[0]?.name ??
|
|
390
|
+
"content-editor",
|
|
391
|
+
item: {
|
|
392
|
+
id: entry.itemId,
|
|
393
|
+
language: entry.itemLanguage,
|
|
394
|
+
version: entry.itemVersion,
|
|
395
|
+
},
|
|
396
|
+
timestamp: new Date(entry.visitedAt).getTime(),
|
|
397
|
+
displayName:
|
|
398
|
+
searchParams.get("view") ??
|
|
399
|
+
configuration.editor.views[0]?.name ??
|
|
400
|
+
"content-editor",
|
|
401
|
+
itemName: entry.itemName,
|
|
402
|
+
itemPath: entry.itemPath,
|
|
403
|
+
itemIcon: entry.icon,
|
|
404
|
+
}),
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
376
408
|
const [centerPanelView, setCenterPanelView] = useState<ReactNode>();
|
|
377
409
|
const [timings, setTimings] = useState<Timings>({});
|
|
378
410
|
|
|
@@ -388,7 +420,7 @@ export function EditorShell({
|
|
|
388
420
|
|
|
389
421
|
const [enableCompletions, setEnableCompletions] = useState(false);
|
|
390
422
|
const [showComponentNavigator, setShowComponentNavigator] = useState(
|
|
391
|
-
userPreferences.showComponentNavigator ??
|
|
423
|
+
userPreferences.showComponentNavigator ?? false,
|
|
392
424
|
);
|
|
393
425
|
const [showAgentsPanel, setShowAgentsPanel] = useState(
|
|
394
426
|
userPreferences.showAgentsPanel ?? false,
|
|
@@ -409,6 +441,11 @@ export function EditorShell({
|
|
|
409
441
|
|
|
410
442
|
const [favorites, setFavorites] = useState<any[]>([]);
|
|
411
443
|
|
|
444
|
+
// Quick item switcher state
|
|
445
|
+
const [quickSwitcherVisible, setQuickSwitcherVisible] = useState(false);
|
|
446
|
+
const [quickSwitcherSelectedIndex, setQuickSwitcherSelectedIndex] =
|
|
447
|
+
useState(0);
|
|
448
|
+
|
|
412
449
|
// Track initial load to know when to sync state from URL vs URL from state
|
|
413
450
|
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
|
414
451
|
|
|
@@ -1151,6 +1188,44 @@ export function EditorShell({
|
|
|
1151
1188
|
[browseHistory, setBrowseHistory],
|
|
1152
1189
|
);
|
|
1153
1190
|
|
|
1191
|
+
// Add entry to navigation history (view + item combination)
|
|
1192
|
+
const addNavigationEntry = useCallback((view: string, item?: FullItem) => {
|
|
1193
|
+
const navEntry: NavigationHistoryEntry = {
|
|
1194
|
+
viewName: view,
|
|
1195
|
+
item: item
|
|
1196
|
+
? {
|
|
1197
|
+
id: item.id,
|
|
1198
|
+
language: item.language,
|
|
1199
|
+
version: item.version,
|
|
1200
|
+
}
|
|
1201
|
+
: undefined,
|
|
1202
|
+
timestamp: Date.now(),
|
|
1203
|
+
displayName: view,
|
|
1204
|
+
itemName: item?.name,
|
|
1205
|
+
itemPath: item?.path,
|
|
1206
|
+
itemIcon: item?.icon,
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
setNavigationHistory((history) => {
|
|
1210
|
+
// Remove duplicates with same view + item identity (including version),
|
|
1211
|
+
// and also handle entries with no item (both undefined)
|
|
1212
|
+
const filtered = history.filter((entry) => {
|
|
1213
|
+
const sameView = entry.viewName === view;
|
|
1214
|
+
const bothNoItem = !entry.item && !item;
|
|
1215
|
+
const sameItem =
|
|
1216
|
+
!!entry.item &&
|
|
1217
|
+
!!item &&
|
|
1218
|
+
entry.item.id === item.id &&
|
|
1219
|
+
entry.item.language === item.language &&
|
|
1220
|
+
entry.item.version === item.version;
|
|
1221
|
+
return !(sameView && (bothNoItem || sameItem));
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// Add new entry at the beginning and limit to 25 items
|
|
1225
|
+
return [navEntry, ...filtered].slice(0, 25);
|
|
1226
|
+
});
|
|
1227
|
+
}, []);
|
|
1228
|
+
|
|
1154
1229
|
const loadItem = useCallback(
|
|
1155
1230
|
async (
|
|
1156
1231
|
itemToLoad: ItemDescriptor | string,
|
|
@@ -1196,6 +1271,8 @@ export function EditorShell({
|
|
|
1196
1271
|
options?.addToBrowseHistory === undefined
|
|
1197
1272
|
) {
|
|
1198
1273
|
addToBrowseHistory(item);
|
|
1274
|
+
// Also add to navigation history with current view
|
|
1275
|
+
addNavigationEntry(viewName, item);
|
|
1199
1276
|
}
|
|
1200
1277
|
|
|
1201
1278
|
return item;
|
|
@@ -1206,6 +1283,7 @@ export function EditorShell({
|
|
|
1206
1283
|
viewName,
|
|
1207
1284
|
loadHistory,
|
|
1208
1285
|
addToBrowseHistory,
|
|
1286
|
+
addNavigationEntry,
|
|
1209
1287
|
],
|
|
1210
1288
|
);
|
|
1211
1289
|
|
|
@@ -1416,6 +1494,9 @@ export function EditorShell({
|
|
|
1416
1494
|
// Track previous view name before switching
|
|
1417
1495
|
setPreviousViewName(editContext.viewName);
|
|
1418
1496
|
|
|
1497
|
+
// Add to navigation history with current item
|
|
1498
|
+
addNavigationEntry(viewName, contentEditorItem);
|
|
1499
|
+
|
|
1419
1500
|
if (typeof document.startViewTransition === "function") {
|
|
1420
1501
|
document.startViewTransition(() => {
|
|
1421
1502
|
flushSync(() => {
|
|
@@ -1511,6 +1592,155 @@ export function EditorShell({
|
|
|
1511
1592
|
[operations, ignoreBlur, sessionId],
|
|
1512
1593
|
);
|
|
1513
1594
|
|
|
1595
|
+
// Quick switcher handlers
|
|
1596
|
+
const showQuickSwitcher = useCallback(
|
|
1597
|
+
(show: boolean) => {
|
|
1598
|
+
console.log(
|
|
1599
|
+
`[QuickSwitcher] showQuickSwitcher called: show=${show}, historyLength=${navigationHistory.length}`,
|
|
1600
|
+
);
|
|
1601
|
+
if (show && navigationHistory.length > 1) {
|
|
1602
|
+
setQuickSwitcherVisible(true);
|
|
1603
|
+
// Start with index 1 (second entry - previous entry) for quick switching
|
|
1604
|
+
setQuickSwitcherSelectedIndex(1);
|
|
1605
|
+
console.log("[QuickSwitcher] Switcher opened, selected index: 1");
|
|
1606
|
+
console.log(
|
|
1607
|
+
"[QuickSwitcher] Navigation history:",
|
|
1608
|
+
navigationHistory.map((h, i) => `[${i}] ${h.displayName}`),
|
|
1609
|
+
);
|
|
1610
|
+
} else {
|
|
1611
|
+
setQuickSwitcherVisible(false);
|
|
1612
|
+
console.log("[QuickSwitcher] Switcher closed");
|
|
1613
|
+
}
|
|
1614
|
+
},
|
|
1615
|
+
[navigationHistory],
|
|
1616
|
+
);
|
|
1617
|
+
|
|
1618
|
+
const cycleQuickSwitcher = useCallback(
|
|
1619
|
+
(direction: "next" | "prev" | "up" | "down") => {
|
|
1620
|
+
console.log(
|
|
1621
|
+
`[QuickSwitcher] cycleQuickSwitcher: direction=${direction}, visible=${quickSwitcherVisible}`,
|
|
1622
|
+
);
|
|
1623
|
+
if (!quickSwitcherVisible) return;
|
|
1624
|
+
const maxItems = Math.min(5, navigationHistory.length);
|
|
1625
|
+
|
|
1626
|
+
setQuickSwitcherSelectedIndex((current) => {
|
|
1627
|
+
let newIndex = current;
|
|
1628
|
+
|
|
1629
|
+
// Determine grid layout (responsive columns)
|
|
1630
|
+
// Matches the grid in QuickItemSwitcher: 2/3/4/5 columns
|
|
1631
|
+
const getColumnsForWidth = () => {
|
|
1632
|
+
const width = window.innerWidth;
|
|
1633
|
+
if (width >= 1280) return 5; // xl
|
|
1634
|
+
if (width >= 1024) return 4; // lg
|
|
1635
|
+
if (width >= 768) return 3; // md
|
|
1636
|
+
return 2; // default
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
const columns = getColumnsForWidth();
|
|
1640
|
+
|
|
1641
|
+
switch (direction) {
|
|
1642
|
+
case "next":
|
|
1643
|
+
newIndex = (current + 1) % maxItems;
|
|
1644
|
+
break;
|
|
1645
|
+
case "prev":
|
|
1646
|
+
newIndex = (current - 1 + maxItems) % maxItems;
|
|
1647
|
+
break;
|
|
1648
|
+
case "down":
|
|
1649
|
+
newIndex = current + columns;
|
|
1650
|
+
if (newIndex >= maxItems) {
|
|
1651
|
+
// Wrap to first row
|
|
1652
|
+
newIndex = current % columns;
|
|
1653
|
+
}
|
|
1654
|
+
break;
|
|
1655
|
+
case "up":
|
|
1656
|
+
newIndex = current - columns;
|
|
1657
|
+
if (newIndex < 0) {
|
|
1658
|
+
// Wrap to last row
|
|
1659
|
+
const currentCol = current % columns;
|
|
1660
|
+
const rows = Math.ceil(maxItems / columns);
|
|
1661
|
+
newIndex = (rows - 1) * columns + currentCol;
|
|
1662
|
+
if (newIndex >= maxItems) {
|
|
1663
|
+
newIndex = maxItems - columns + currentCol;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
break;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
console.log(
|
|
1670
|
+
`[QuickSwitcher] Cycling from index ${current} to ${newIndex} (${columns} columns)`,
|
|
1671
|
+
);
|
|
1672
|
+
return newIndex;
|
|
1673
|
+
});
|
|
1674
|
+
},
|
|
1675
|
+
[quickSwitcherVisible, browseHistory],
|
|
1676
|
+
);
|
|
1677
|
+
|
|
1678
|
+
const handleQuickSwitcherSelect = useCallback(
|
|
1679
|
+
(index: number) => {
|
|
1680
|
+
console.log(`[QuickSwitcher] Selecting entry at index ${index}`);
|
|
1681
|
+
const selectedEntry = navigationHistory[index];
|
|
1682
|
+
if (selectedEntry) {
|
|
1683
|
+
console.log(`[QuickSwitcher] Navigating to:`, selectedEntry);
|
|
1684
|
+
|
|
1685
|
+
// First switch view
|
|
1686
|
+
if (selectedEntry.viewName !== viewName) {
|
|
1687
|
+
switchView(selectedEntry.viewName, { skipConfirmation: true });
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// Then load item if it exists and is different from current
|
|
1691
|
+
if (selectedEntry.item) {
|
|
1692
|
+
const isDifferentItem =
|
|
1693
|
+
!contentEditorItem ||
|
|
1694
|
+
contentEditorItem.id !== selectedEntry.item.id ||
|
|
1695
|
+
contentEditorItem.language !== selectedEntry.item.language ||
|
|
1696
|
+
contentEditorItem.version !== selectedEntry.item.version;
|
|
1697
|
+
|
|
1698
|
+
if (isDifferentItem) {
|
|
1699
|
+
console.log(
|
|
1700
|
+
`[QuickSwitcher] Loading different item:`,
|
|
1701
|
+
selectedEntry.item,
|
|
1702
|
+
);
|
|
1703
|
+
loadItem(
|
|
1704
|
+
{
|
|
1705
|
+
id: selectedEntry.item.id,
|
|
1706
|
+
language: selectedEntry.item.language,
|
|
1707
|
+
version: selectedEntry.item.version,
|
|
1708
|
+
},
|
|
1709
|
+
{ addToBrowseHistory: false, skipViewChange: true },
|
|
1710
|
+
);
|
|
1711
|
+
} else {
|
|
1712
|
+
console.log(`[QuickSwitcher] Same item, skipping reload`);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// Move selected entry to first position in navigation history by identity
|
|
1717
|
+
setNavigationHistory((history) => {
|
|
1718
|
+
const filtered = history.filter((entry) => {
|
|
1719
|
+
const sameView = entry.viewName === selectedEntry.viewName;
|
|
1720
|
+
const bothNoItem = !entry.item && !selectedEntry.item;
|
|
1721
|
+
const sameItem =
|
|
1722
|
+
!!entry.item &&
|
|
1723
|
+
!!selectedEntry.item &&
|
|
1724
|
+
entry.item.id === selectedEntry.item.id &&
|
|
1725
|
+
entry.item.language === selectedEntry.item.language &&
|
|
1726
|
+
entry.item.version === selectedEntry.item.version;
|
|
1727
|
+
return !(sameView && (bothNoItem || sameItem));
|
|
1728
|
+
});
|
|
1729
|
+
return [selectedEntry, ...filtered].slice(0, 25);
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
setQuickSwitcherVisible(false);
|
|
1733
|
+
},
|
|
1734
|
+
[
|
|
1735
|
+
navigationHistory,
|
|
1736
|
+
loadItem,
|
|
1737
|
+
switchView,
|
|
1738
|
+
viewName,
|
|
1739
|
+
contentEditorItem,
|
|
1740
|
+
setNavigationHistory,
|
|
1741
|
+
],
|
|
1742
|
+
);
|
|
1743
|
+
|
|
1514
1744
|
const { handleKeyDown } = useKeyboardNavigation({
|
|
1515
1745
|
editContextRef,
|
|
1516
1746
|
operations,
|
|
@@ -1522,10 +1752,57 @@ export function EditorShell({
|
|
|
1522
1752
|
showInfoToast,
|
|
1523
1753
|
showErrorToast,
|
|
1524
1754
|
executeCommand,
|
|
1755
|
+
showQuickSwitcher,
|
|
1756
|
+
cycleQuickSwitcher,
|
|
1525
1757
|
});
|
|
1526
1758
|
|
|
1527
1759
|
useGlobalEditorKeyDown(handleKeyDown);
|
|
1528
1760
|
|
|
1761
|
+
// Global keydown/keyup handler to manage quick switcher
|
|
1762
|
+
useEffect(() => {
|
|
1763
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
1764
|
+
if (!quickSwitcherVisible) return;
|
|
1765
|
+
|
|
1766
|
+
// Enter to select and close
|
|
1767
|
+
if (event.key === "Enter") {
|
|
1768
|
+
event.preventDefault();
|
|
1769
|
+
handleQuickSwitcherSelect(quickSwitcherSelectedIndex);
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
const handleKeyUp = (event: KeyboardEvent) => {
|
|
1774
|
+
// Close on Escape without selecting
|
|
1775
|
+
if (event.key === "Escape" && quickSwitcherVisible) {
|
|
1776
|
+
setQuickSwitcherVisible(false);
|
|
1777
|
+
}
|
|
1778
|
+
// Close and select when releasing Ctrl, Alt, or Meta (Win key)
|
|
1779
|
+
if (
|
|
1780
|
+
(event.key === "Control" ||
|
|
1781
|
+
event.key === "Alt" ||
|
|
1782
|
+
event.key === "Meta") &&
|
|
1783
|
+
quickSwitcherVisible
|
|
1784
|
+
) {
|
|
1785
|
+
// Small delay to ensure the key release is not part of the initial trigger
|
|
1786
|
+
setTimeout(() => {
|
|
1787
|
+
handleQuickSwitcherSelect(quickSwitcherSelectedIndex);
|
|
1788
|
+
}, 50);
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
if (typeof window !== "undefined") {
|
|
1793
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1794
|
+
window.addEventListener("keyup", handleKeyUp);
|
|
1795
|
+
return () => {
|
|
1796
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
1797
|
+
window.removeEventListener("keyup", handleKeyUp);
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
}, [
|
|
1801
|
+
quickSwitcherVisible,
|
|
1802
|
+
quickSwitcherSelectedIndex,
|
|
1803
|
+
handleQuickSwitcherSelect,
|
|
1804
|
+
]);
|
|
1805
|
+
|
|
1529
1806
|
// Global ctrl+click handler for GUID detection and navigation
|
|
1530
1807
|
useEffect(() => {
|
|
1531
1808
|
const isGuid = (text: string): boolean => {
|
|
@@ -1614,8 +1891,8 @@ export function EditorShell({
|
|
|
1614
1891
|
};
|
|
1615
1892
|
|
|
1616
1893
|
const handleCtrlClick = async (event: globalThis.MouseEvent) => {
|
|
1617
|
-
// Only proceed if Ctrl
|
|
1618
|
-
if (
|
|
1894
|
+
// Only proceed if Ctrl (or Cmd on Mac) is pressed
|
|
1895
|
+
if (!event.ctrlKey && !event.metaKey) return;
|
|
1619
1896
|
|
|
1620
1897
|
const target = event.target as Element;
|
|
1621
1898
|
const text = getTextFromElement(target);
|
|
@@ -2287,6 +2564,7 @@ export function EditorShell({
|
|
|
2287
2564
|
setCurrentWizardId,
|
|
2288
2565
|
favorites,
|
|
2289
2566
|
loadFavorites,
|
|
2567
|
+
isQuickSwitcherVisible: quickSwitcherVisible,
|
|
2290
2568
|
// Context factory registry methods
|
|
2291
2569
|
registerContextFactory: (
|
|
2292
2570
|
name: string,
|
|
@@ -2736,6 +3014,13 @@ export function EditorShell({
|
|
|
2736
3014
|
{dialog}
|
|
2737
3015
|
<Toaster position="top-center" />{" "}
|
|
2738
3016
|
<ConfirmationDialog ref={confirmationDialogRef} />
|
|
3017
|
+
<QuickItemSwitcher
|
|
3018
|
+
visible={quickSwitcherVisible}
|
|
3019
|
+
entries={navigationHistory.slice(0, 5)}
|
|
3020
|
+
selectedIndex={quickSwitcherSelectedIndex}
|
|
3021
|
+
onSelect={handleQuickSwitcherSelect}
|
|
3022
|
+
onClose={() => setQuickSwitcherVisible(false)}
|
|
3023
|
+
/>
|
|
2739
3024
|
<EditContextMenu ref={contextMenuRef} />
|
|
2740
3025
|
{media.mediaSelectorVisible && (
|
|
2741
3026
|
<MediaSelector
|
|
@@ -334,21 +334,6 @@ export function useSocketMessageHandler(deps: {
|
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
// Agent WebSocket messages - forward to agent terminal via listeners
|
|
338
|
-
if (
|
|
339
|
-
message.type === "agent:name:updated" ||
|
|
340
|
-
message.type === "agent:status:changed" ||
|
|
341
|
-
message.type === "agent:run:start" ||
|
|
342
|
-
message.type === "agent:user:message" ||
|
|
343
|
-
message.type === "agent:run:delta" ||
|
|
344
|
-
message.type === "agent:run:status" ||
|
|
345
|
-
message.type === "agent:run:complete" ||
|
|
346
|
-
message.type === "agent:run:error"
|
|
347
|
-
) {
|
|
348
|
-
// These messages are handled by AgentTerminal via socketMessageListeners
|
|
349
|
-
// Just pass through to listeners
|
|
350
|
-
}
|
|
351
|
-
|
|
352
337
|
socketMessageListeners.current.forEach((listener) => listener(message));
|
|
353
338
|
},
|
|
354
339
|
[currentItemDescriptor, sessionId],
|
|
@@ -48,6 +48,7 @@ export const deleteItemCommand: ItemCommand = {
|
|
|
48
48
|
id: "deleteItem",
|
|
49
49
|
label: "Delete",
|
|
50
50
|
icon: <Trash2 strokeWidth={1} />,
|
|
51
|
+
keyBinding: "Delete",
|
|
51
52
|
disabled: (context: ItemCommandContext) =>
|
|
52
53
|
!context.data?.items || !context.data?.items[0]?.canDelete || false,
|
|
53
54
|
execute: async (context: ItemCommandContext) => {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
applySetup,
|
|
4
|
+
getSetupStatus,
|
|
5
|
+
SetupCategoryDto,
|
|
6
|
+
} from "../../services/setupService";
|
|
7
|
+
import { RefreshCw } from "lucide-react";
|
|
8
|
+
|
|
9
|
+
export function ParheliaSetupOverview() {
|
|
10
|
+
const [categories, setCategories] = useState<SetupCategoryDto[]>([]);
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
const [error, setError] = useState<string | null>(null);
|
|
13
|
+
const [applying, setApplying] = useState(false);
|
|
14
|
+
const [includeDeps, setIncludeDeps] = useState(true);
|
|
15
|
+
|
|
16
|
+
const load = async () => {
|
|
17
|
+
try {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
setError(null);
|
|
20
|
+
const status = await getSetupStatus(false);
|
|
21
|
+
setCategories(status.categories || []);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
setError(e instanceof Error ? e.message : "Failed to load setup status");
|
|
24
|
+
} finally {
|
|
25
|
+
setLoading(false);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
load();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="h-full overflow-auto p-4">
|
|
35
|
+
<div className="mb-4 flex items-center justify-between">
|
|
36
|
+
<div className="text-lg font-semibold text-gray-800">
|
|
37
|
+
Parhelia Setup
|
|
38
|
+
</div>
|
|
39
|
+
<div className="flex items-center gap-2">
|
|
40
|
+
<label className="mr-2 flex items-center gap-2 text-sm text-gray-700">
|
|
41
|
+
<input
|
|
42
|
+
type="checkbox"
|
|
43
|
+
checked={includeDeps}
|
|
44
|
+
onChange={(e) => setIncludeDeps(e.target.checked)}
|
|
45
|
+
/>
|
|
46
|
+
Include dependencies
|
|
47
|
+
</label>
|
|
48
|
+
<button
|
|
49
|
+
onClick={load}
|
|
50
|
+
disabled={loading || applying}
|
|
51
|
+
className="flex items-center gap-2 rounded bg-gray-100 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
|
|
52
|
+
>
|
|
53
|
+
<RefreshCw className="h-4 w-4" strokeWidth={1} />
|
|
54
|
+
{loading ? "Refreshing..." : "Refresh"}
|
|
55
|
+
</button>
|
|
56
|
+
<button
|
|
57
|
+
onClick={async () => {
|
|
58
|
+
try {
|
|
59
|
+
setApplying(true);
|
|
60
|
+
setError(null);
|
|
61
|
+
const keys = categories
|
|
62
|
+
.flatMap((c) => c.items)
|
|
63
|
+
.filter((i) => i.status === "Missing")
|
|
64
|
+
.map((i) => i.key);
|
|
65
|
+
if (keys.length === 0) return;
|
|
66
|
+
await applySetup(keys, includeDeps);
|
|
67
|
+
await load();
|
|
68
|
+
} catch (e) {
|
|
69
|
+
setError(
|
|
70
|
+
e instanceof Error ? e.message : "Failed to apply setup",
|
|
71
|
+
);
|
|
72
|
+
} finally {
|
|
73
|
+
setApplying(false);
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
disabled={loading || applying}
|
|
77
|
+
className="flex items-center gap-2 rounded bg-indigo-600 px-3 py-1.5 text-sm text-white hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50"
|
|
78
|
+
>
|
|
79
|
+
{applying ? "Applying..." : "Create Missing"}
|
|
80
|
+
</button>
|
|
81
|
+
<button
|
|
82
|
+
onClick={async () => {
|
|
83
|
+
try {
|
|
84
|
+
setApplying(true);
|
|
85
|
+
setError(null);
|
|
86
|
+
const keys = categories
|
|
87
|
+
.flatMap((c) => c.items)
|
|
88
|
+
.filter((i) => i.status === "Outdated")
|
|
89
|
+
.map((i) => i.key);
|
|
90
|
+
if (keys.length === 0) return;
|
|
91
|
+
await applySetup(keys, includeDeps);
|
|
92
|
+
await load();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
setError(
|
|
95
|
+
e instanceof Error ? e.message : "Failed to apply setup",
|
|
96
|
+
);
|
|
97
|
+
} finally {
|
|
98
|
+
setApplying(false);
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
101
|
+
disabled={loading || applying}
|
|
102
|
+
className="flex items-center gap-2 rounded bg-amber-600 px-3 py-1.5 text-sm text-white hover:bg-amber-700 disabled:cursor-not-allowed disabled:opacity-50"
|
|
103
|
+
>
|
|
104
|
+
{applying ? "Applying..." : "Update Outdated"}
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{error && (
|
|
110
|
+
<div className="mb-3 rounded border border-red-200 bg-red-50 p-3 text-sm text-red-700">
|
|
111
|
+
{error}
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
<div className="space-y-6">
|
|
116
|
+
{categories.map((cat) => (
|
|
117
|
+
<div key={cat.name}>
|
|
118
|
+
<div className="mb-2 text-sm font-semibold text-gray-700">
|
|
119
|
+
{cat.name}
|
|
120
|
+
</div>
|
|
121
|
+
<div className="divide-y rounded border">
|
|
122
|
+
{cat.items.map((it) => (
|
|
123
|
+
<div
|
|
124
|
+
key={it.key}
|
|
125
|
+
className="flex items-center justify-between px-3 py-2 text-sm"
|
|
126
|
+
>
|
|
127
|
+
<div className="min-w-0">
|
|
128
|
+
<div className="truncate font-medium">{it.key}</div>
|
|
129
|
+
<div className="truncate text-xs text-gray-500">
|
|
130
|
+
{it.targetPath}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="ml-3 shrink-0">
|
|
134
|
+
<span
|
|
135
|
+
className={`rounded px-2 py-0.5 text-xs font-medium ${
|
|
136
|
+
it.status === "Missing"
|
|
137
|
+
? "bg-yellow-100 text-yellow-700"
|
|
138
|
+
: it.status === "UpToDate"
|
|
139
|
+
? "bg-green-100 text-green-700"
|
|
140
|
+
: it.status === "Outdated"
|
|
141
|
+
? "bg-amber-100 text-amber-700"
|
|
142
|
+
: it.status === "Modified"
|
|
143
|
+
? "bg-blue-100 text-blue-700"
|
|
144
|
+
: "bg-gray-100 text-gray-700"
|
|
145
|
+
}`}
|
|
146
|
+
>
|
|
147
|
+
{it.status}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="ml-3 shrink-0">
|
|
151
|
+
{it.status === "Missing" || it.status === "Outdated" ? (
|
|
152
|
+
<button
|
|
153
|
+
onClick={async () => {
|
|
154
|
+
try {
|
|
155
|
+
setApplying(true);
|
|
156
|
+
setError(null);
|
|
157
|
+
await applySetup([it.key], includeDeps);
|
|
158
|
+
await load();
|
|
159
|
+
} catch (e) {
|
|
160
|
+
setError(
|
|
161
|
+
e instanceof Error
|
|
162
|
+
? e.message
|
|
163
|
+
: "Failed to apply setup",
|
|
164
|
+
);
|
|
165
|
+
} finally {
|
|
166
|
+
setApplying(false);
|
|
167
|
+
}
|
|
168
|
+
}}
|
|
169
|
+
disabled={loading || applying}
|
|
170
|
+
className="rounded bg-gray-800 px-2 py-1 text-xs text-white hover:bg-black disabled:cursor-not-allowed disabled:opacity-50"
|
|
171
|
+
>
|
|
172
|
+
{it.status === "Missing" ? "Install" : "Update"}
|
|
173
|
+
</button>
|
|
174
|
+
) : null}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
))}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { FileIcon, ImageOff } from "lucide-react";
|
|
5
5
|
|
|
6
|
-
import { Button } from "../../components/ui/button";
|
|
7
6
|
import { cn } from "../../lib/utils";
|
|
8
7
|
import { AttachmentField } from "../fieldTypes";
|
|
9
8
|
|
|
@@ -86,38 +85,18 @@ export function AttachmentEditor({ field, readOnly }: AttachmentEditorProps) {
|
|
|
86
85
|
|
|
87
86
|
if (isImage) {
|
|
88
87
|
return (
|
|
89
|
-
<div
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
<div className="flex items-center gap-2">
|
|
104
|
-
<Button asChild size="xs" variant="secondary">
|
|
105
|
-
<a href={src} target="_blank" rel="noopener noreferrer">
|
|
106
|
-
<ExternalLink className="h-3.5 w-3.5" strokeWidth={1} /> Open
|
|
107
|
-
</a>
|
|
108
|
-
</Button>
|
|
109
|
-
<Button asChild size="xs" variant="secondary">
|
|
110
|
-
<a
|
|
111
|
-
href={src}
|
|
112
|
-
download={fileName ?? true}
|
|
113
|
-
{...(field?.value?.mimeType
|
|
114
|
-
? { type: field.value.mimeType }
|
|
115
|
-
: {})}
|
|
116
|
-
>
|
|
117
|
-
<Download className="h-3.5 w-3.5" strokeWidth={1} /> Download
|
|
118
|
-
</a>
|
|
119
|
-
</Button>
|
|
120
|
-
</div>
|
|
88
|
+
<div
|
|
89
|
+
className={cn(
|
|
90
|
+
"focus-shadow flex max-h-72 w-full max-w-xs items-center justify-center overflow-hidden rounded border border-gray-300 bg-gray-50",
|
|
91
|
+
readOnly && "opacity-80",
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
<img
|
|
95
|
+
src={src}
|
|
96
|
+
alt={fileName ?? "Attachment"}
|
|
97
|
+
className="max-h-72 w-full object-contain"
|
|
98
|
+
loading="lazy"
|
|
99
|
+
/>
|
|
121
100
|
</div>
|
|
122
101
|
);
|
|
123
102
|
}
|
|
@@ -133,22 +112,6 @@ export function AttachmentEditor({ field, readOnly }: AttachmentEditorProps) {
|
|
|
133
112
|
</span>
|
|
134
113
|
)}
|
|
135
114
|
</div>
|
|
136
|
-
<div className="flex items-center gap-2">
|
|
137
|
-
<Button asChild size="xs" variant="secondary">
|
|
138
|
-
<a href={src} target="_blank" rel="noopener noreferrer">
|
|
139
|
-
<ExternalLink className="h-3.5 w-3.5" strokeWidth={1} /> Open
|
|
140
|
-
</a>
|
|
141
|
-
</Button>
|
|
142
|
-
<Button asChild size="xs" variant="secondary">
|
|
143
|
-
<a
|
|
144
|
-
href={src}
|
|
145
|
-
download={fileName ?? true}
|
|
146
|
-
{...(field?.value?.mimeType ? { type: field.value.mimeType } : {})}
|
|
147
|
-
>
|
|
148
|
-
<Download className="h-3.5 w-3.5" strokeWidth={1} /> Download
|
|
149
|
-
</a>
|
|
150
|
-
</Button>
|
|
151
|
-
</div>
|
|
152
115
|
</div>
|
|
153
116
|
);
|
|
154
117
|
}
|