@alpaca-editor/core 1.0.3955 → 1.0.3959
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/build.css +1 -1
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/badge.js +23 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/button.js +3 -3
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/command.d.ts +18 -0
- package/dist/components/ui/command.js +35 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +15 -0
- package/dist/components/ui/dialog.js +37 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +25 -0
- package/dist/components/ui/dropdown-menu.js +52 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +7 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/menubar.d.ts +26 -0
- package/dist/components/ui/menubar.js +55 -0
- package/dist/components/ui/menubar.js.map +1 -0
- package/dist/components/ui/popover.d.ts +9 -0
- package/dist/components/ui/popover.js +63 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/switch.js +9 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tooltip.js +18 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/config/config.js +79 -63
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +3 -3
- package/dist/editor/ContentTree.js +1 -1
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/Editor.js +6 -2
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FieldList.js +1 -1
- package/dist/editor/FieldList.js.map +1 -1
- package/dist/editor/FieldListField.js +1 -1
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ImageEditor.js +16 -6
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/MainLayout.js +4 -4
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/MobileLayout.js +3 -3
- package/dist/editor/MobileLayout.js.map +1 -1
- package/dist/editor/PictureEditor.js +29 -15
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/Titlebar.js +6 -11
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/GhostWriter.js +1 -1
- package/dist/editor/ai/GhostWriter.js.map +1 -1
- package/dist/editor/client/EditorClient.d.ts +4 -2
- package/dist/editor/client/EditorClient.js +32 -11
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +4 -1
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/operations.js +2 -2
- package/dist/editor/client/pageModelBuilder.js +3 -6
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/commands/itemCommands.d.ts +2 -0
- package/dist/editor/commands/itemCommands.js +180 -0
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +1 -1
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +1 -1
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/menubar/ActiveUsers.js +98 -4
- package/dist/editor/menubar/ActiveUsers.js.map +1 -1
- package/dist/editor/menubar/{ActionsMenu.d.ts → ItemActionsMenu.d.ts} +1 -1
- package/dist/editor/menubar/ItemActionsMenu.js +23 -0
- package/dist/editor/menubar/ItemActionsMenu.js.map +1 -0
- package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
- package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
- package/dist/editor/menubar/LanguageSelector.d.ts +1 -2
- package/dist/editor/menubar/LanguageSelector.js +23 -23
- package/dist/editor/menubar/LanguageSelector.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +7 -8
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/PageViewerControls.js +22 -19
- package/dist/editor/menubar/PageViewerControls.js.map +1 -1
- package/dist/editor/menubar/PreviewSecondaryControls.js +2 -3
- package/dist/editor/menubar/PreviewSecondaryControls.js.map +1 -1
- package/dist/editor/menubar/User.js +1 -1
- package/dist/editor/menubar/User.js.map +1 -1
- package/dist/editor/menubar/VersionSelector.js +36 -31
- package/dist/editor/menubar/VersionSelector.js.map +1 -1
- package/dist/editor/menubar/WorkflowButton.d.ts +1 -0
- package/dist/editor/menubar/WorkflowButton.js +41 -0
- package/dist/editor/menubar/WorkflowButton.js.map +1 -0
- package/dist/editor/page-editor-chrome/FrameMenu.js +5 -5
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.js +2 -2
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.d.ts +2 -1
- package/dist/editor/page-viewer/EditorForm.js +61 -49
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewer.js +28 -44
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/reviews/Comments.js +9 -9
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +3 -3
- package/dist/editor/services/contentService.d.ts +18 -0
- package/dist/editor/services/contentService.js +6 -0
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +5 -0
- package/dist/editor/services/editService.js +4 -0
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/services/systemService.d.ts +2 -1
- package/dist/editor/services/systemService.js +4 -1
- package/dist/editor/services/systemService.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +26 -10
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/Divider.d.ts +6 -0
- package/dist/editor/sidebar/Divider.js +6 -0
- package/dist/editor/sidebar/Divider.js.map +1 -0
- package/dist/editor/sidebar/LeftToolbar.d.ts +1 -0
- package/dist/editor/sidebar/LeftToolbar.js +16 -0
- package/dist/editor/sidebar/LeftToolbar.js.map +1 -0
- package/dist/editor/sidebar/SEOInfo.d.ts +1 -0
- package/dist/editor/sidebar/SEOInfo.js +169 -0
- package/dist/editor/sidebar/SEOInfo.js.map +1 -0
- package/dist/editor/sidebar/Sidebar.js +1 -1
- package/dist/editor/sidebar/Sidebar.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.d.ts +3 -2
- package/dist/editor/sidebar/SidebarView.js +22 -60
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +66 -20
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/Icons.d.ts +4 -0
- package/dist/editor/ui/Icons.js +15 -3
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/ui/Section.js +1 -1
- package/dist/editor/ui/Section.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.d.ts +1 -2
- package/dist/editor/ui/SimpleIconButton.js +8 -13
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/ui/SimpleTabs.js +2 -2
- package/dist/editor/ui/SimpleTabs.js.map +1 -1
- package/dist/editor/ui/SimpleToolbar.js +1 -1
- package/dist/editor/ui/SimpleToolbar.js.map +1 -1
- package/dist/editor/ui/Splitter.d.ts +4 -0
- package/dist/editor/ui/Splitter.js +6 -7
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/views/CompareView.js +16 -4
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/editor/views/SingleEditView.d.ts +2 -1
- package/dist/editor/views/SingleEditView.js +2 -2
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/page-wizard/steps/ContentStep.js +1 -1
- package/dist/page-wizard/steps/ContentStep.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +8 -6
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +3 -8
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/styles.css +1519 -543
- package/dist/tour/Tour.js +79 -10
- package/dist/tour/Tour.js.map +1 -1
- package/dist/tour/default-tour.js +55 -45
- package/dist/tour/default-tour.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/package.json +13 -5
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/button.tsx +3 -3
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/menubar.tsx +276 -0
- package/src/components/ui/popover.tsx +113 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/config/config.tsx +102 -65
- package/src/config/types.ts +3 -3
- package/src/editor/ContentTree.tsx +1 -1
- package/src/editor/Editor.tsx +8 -2
- package/src/editor/FieldList.tsx +2 -2
- package/src/editor/FieldListField.tsx +1 -1
- package/src/editor/ImageEditor.tsx +44 -21
- package/src/editor/MainLayout.tsx +21 -16
- package/src/editor/MobileLayout.tsx +3 -2
- package/src/editor/PictureEditor.tsx +74 -45
- package/src/editor/Titlebar.tsx +12 -24
- package/src/editor/ai/GhostWriter.tsx +1 -1
- package/src/editor/client/EditorClient.tsx +55 -13
- package/src/editor/client/editContext.ts +5 -0
- package/src/editor/client/operations.ts +2 -2
- package/src/editor/client/pageModelBuilder.ts +3 -7
- package/src/editor/commands/itemCommands.tsx +272 -0
- package/src/editor/field-types/MultiLineText.tsx +1 -1
- package/src/editor/field-types/SingleLineText.tsx +1 -1
- package/src/editor/menubar/ActiveUsers.tsx +271 -5
- package/src/editor/menubar/ItemActionsMenu.tsx +89 -0
- package/src/editor/menubar/ItemLanguageVersion.tsx +7 -5
- package/src/editor/menubar/LanguageSelector.tsx +105 -134
- package/src/editor/menubar/PageSelector.tsx +25 -27
- package/src/editor/menubar/PageViewerControls.tsx +126 -78
- package/src/editor/menubar/PreviewSecondaryControls.tsx +0 -2
- package/src/editor/menubar/User.tsx +2 -2
- package/src/editor/menubar/VersionSelector.tsx +124 -99
- package/src/editor/menubar/WorkflowButton.tsx +115 -0
- package/src/editor/page-editor-chrome/FrameMenu.tsx +5 -5
- package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +2 -2
- package/src/editor/page-viewer/EditorForm.tsx +112 -87
- package/src/editor/page-viewer/PageViewer.tsx +75 -92
- package/src/editor/page-viewer/PageViewerFrame.tsx +1 -1
- package/src/editor/reviews/Comments.tsx +19 -20
- package/src/editor/reviews/SuggestedEdit.tsx +3 -3
- package/src/editor/services/contentService.ts +28 -0
- package/src/editor/services/editService.ts +12 -0
- package/src/editor/services/systemService.ts +5 -2
- package/src/editor/sidebar/ComponentTree.tsx +34 -12
- package/src/editor/sidebar/Divider.tsx +22 -0
- package/src/editor/sidebar/LeftToolbar.tsx +36 -0
- package/src/editor/sidebar/SEOInfo.tsx +265 -0
- package/src/editor/sidebar/Sidebar.tsx +1 -0
- package/src/editor/sidebar/SidebarView.tsx +77 -111
- package/src/editor/sidebar/ViewSelector.tsx +211 -43
- package/src/editor/ui/Icons.tsx +155 -10
- package/src/editor/ui/Section.tsx +1 -1
- package/src/editor/ui/SimpleIconButton.tsx +30 -28
- package/src/editor/ui/SimpleTabs.tsx +3 -3
- package/src/editor/ui/SimpleToolbar.tsx +1 -1
- package/src/editor/ui/Splitter.tsx +14 -7
- package/src/editor/views/CompareView.tsx +23 -11
- package/src/editor/views/SingleEditView.tsx +3 -0
- package/src/page-wizard/steps/ContentStep.tsx +0 -1
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +18 -13
- package/src/splash-screen/RecentPages.tsx +4 -10
- package/src/tour/Tour.tsx +125 -34
- package/src/tour/default-tour.tsx +55 -45
- package/src/types.ts +21 -1
- package/styles.css +301 -1
- package/dist/editor/menubar/ActionsMenu.js +0 -49
- package/dist/editor/menubar/ActionsMenu.js.map +0 -1
- package/dist/editor/menubar/SecondaryControls.d.ts +0 -1
- package/dist/editor/menubar/SecondaryControls.js +0 -17
- package/dist/editor/menubar/SecondaryControls.js.map +0 -1
- package/src/editor/menubar/ActionsMenu.tsx +0 -94
- package/src/editor/menubar/SecondaryControls.tsx +0 -45
|
@@ -2,6 +2,12 @@ import { Command, CommandContext, CommandData } from "./commands";
|
|
|
2
2
|
import { FullItem, ItemDescriptor } from "../pageModel";
|
|
3
3
|
|
|
4
4
|
import { ItemNameDialog, ItemNameDialogProps } from "../ui/ItemNameDialogNew";
|
|
5
|
+
import {
|
|
6
|
+
exportItems as exportItemsService,
|
|
7
|
+
importItems as importItemsService,
|
|
8
|
+
ExportItemsRequest,
|
|
9
|
+
ImportItemsRequest,
|
|
10
|
+
} from "../services/contentService";
|
|
5
11
|
|
|
6
12
|
import {
|
|
7
13
|
CopyMoveTargetSelectorDialog,
|
|
@@ -349,3 +355,269 @@ export const publishItemCommand: ItemCommand = {
|
|
|
349
355
|
await context.editContext.switchView("publish");
|
|
350
356
|
},
|
|
351
357
|
};
|
|
358
|
+
|
|
359
|
+
export const exportItemsCommand: ItemCommand = {
|
|
360
|
+
id: "exportItem",
|
|
361
|
+
label: "Export",
|
|
362
|
+
icon: "pi pi-download",
|
|
363
|
+
disabled: (context: ItemCommandContext) =>
|
|
364
|
+
!context.data?.items || context.data.items.length === 0 || false,
|
|
365
|
+
execute: async (context: ItemCommandContext) => {
|
|
366
|
+
const items = context.data?.items;
|
|
367
|
+
if (!items || items.length === 0) return;
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// Get export options from user
|
|
371
|
+
const exportOptions = await new Promise<{
|
|
372
|
+
versionOption: "latest" | "all";
|
|
373
|
+
} | null>((resolve) => {
|
|
374
|
+
context.editContext.confirm({
|
|
375
|
+
message: (
|
|
376
|
+
<div>
|
|
377
|
+
<div>Export {items.length} item(s) to YAML?</div>
|
|
378
|
+
<div style={{ marginTop: "10px" }}>
|
|
379
|
+
<label>Language: </label>
|
|
380
|
+
<select id="export-language" defaultValue="en">
|
|
381
|
+
<option value="en">English</option>
|
|
382
|
+
<option value="current">Current Language</option>
|
|
383
|
+
</select>
|
|
384
|
+
</div>
|
|
385
|
+
<div style={{ marginTop: "10px" }}>
|
|
386
|
+
<label>Version: </label>
|
|
387
|
+
<select id="export-version" defaultValue="latest">
|
|
388
|
+
<option value="latest">Latest</option>
|
|
389
|
+
<option value="published">Published</option>
|
|
390
|
+
</select>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
),
|
|
394
|
+
header: "Export Items",
|
|
395
|
+
icon: "pi pi-download",
|
|
396
|
+
accept: () => {
|
|
397
|
+
const language =
|
|
398
|
+
(document.getElementById("export-language") as HTMLSelectElement)
|
|
399
|
+
?.value || "en";
|
|
400
|
+
const versionOption =
|
|
401
|
+
(document.getElementById("export-version") as HTMLSelectElement)
|
|
402
|
+
?.value || "latest";
|
|
403
|
+
resolve({ versionOption: versionOption as "latest" | "all" });
|
|
404
|
+
},
|
|
405
|
+
reject: () => resolve(null),
|
|
406
|
+
showCancel: true,
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (!exportOptions) return;
|
|
411
|
+
|
|
412
|
+
const request: ExportItemsRequest = {
|
|
413
|
+
items: items.map((item) => item.descriptor),
|
|
414
|
+
versionOption: exportOptions.versionOption as "latest" | "all",
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const response = await exportItemsService(request);
|
|
418
|
+
|
|
419
|
+
if (response.type !== "success") {
|
|
420
|
+
throw new Error(response.details || "Export failed");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const result = response.data;
|
|
424
|
+
|
|
425
|
+
if (result && result.yaml) {
|
|
426
|
+
// Copy YAML content to clipboard
|
|
427
|
+
try {
|
|
428
|
+
await navigator.clipboard.writeText(result.yaml);
|
|
429
|
+
|
|
430
|
+
context.editContext.showToast({
|
|
431
|
+
severity: "success",
|
|
432
|
+
summary: "Export Complete",
|
|
433
|
+
detail: `Successfully exported ${result.itemCount} item(s) to clipboard`,
|
|
434
|
+
life: 3000,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return true;
|
|
438
|
+
} catch (clipboardError) {
|
|
439
|
+
// Fallback: Show YAML in a dialog if clipboard access fails
|
|
440
|
+
context.editContext.confirm({
|
|
441
|
+
message: (
|
|
442
|
+
<div>
|
|
443
|
+
<div>Export successful! Copy the YAML below:</div>
|
|
444
|
+
<textarea
|
|
445
|
+
readOnly
|
|
446
|
+
value={result.yaml}
|
|
447
|
+
style={{
|
|
448
|
+
width: "100%",
|
|
449
|
+
height: "300px",
|
|
450
|
+
marginTop: "10px",
|
|
451
|
+
fontFamily: "monospace",
|
|
452
|
+
fontSize: "12px",
|
|
453
|
+
}}
|
|
454
|
+
onClick={(e) => {
|
|
455
|
+
(e.target as HTMLTextAreaElement).select();
|
|
456
|
+
}}
|
|
457
|
+
/>
|
|
458
|
+
</div>
|
|
459
|
+
),
|
|
460
|
+
header: "Export Results",
|
|
461
|
+
icon: "pi pi-copy",
|
|
462
|
+
accept: () => {},
|
|
463
|
+
rejectLabel: "Close",
|
|
464
|
+
showCancel: false,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
context.editContext.showToast({
|
|
472
|
+
severity: "error",
|
|
473
|
+
summary: "Export Failed",
|
|
474
|
+
detail:
|
|
475
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
476
|
+
life: 5000,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return false;
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
export const importItemsCommand: ItemCommand = {
|
|
485
|
+
id: "importItems",
|
|
486
|
+
label: "Import",
|
|
487
|
+
icon: "pi pi-upload",
|
|
488
|
+
disabled: (context: ItemCommandContext) =>
|
|
489
|
+
!context.data?.items ||
|
|
490
|
+
context.data.items.length !== 1 ||
|
|
491
|
+
!context.data.items[0]?.canLock ||
|
|
492
|
+
false,
|
|
493
|
+
execute: async (context: ItemCommandContext) => {
|
|
494
|
+
const targetItem = context.data?.items[0];
|
|
495
|
+
if (!targetItem) return;
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
// Get YAML content from user
|
|
499
|
+
const yamlContent = await new Promise<string | null>((resolve) => {
|
|
500
|
+
const dialogContent = (
|
|
501
|
+
<div>
|
|
502
|
+
<div>
|
|
503
|
+
Import items into <em>{targetItem.name}</em>
|
|
504
|
+
</div>
|
|
505
|
+
<div style={{ marginTop: "10px" }}>
|
|
506
|
+
<label htmlFor="yaml-input">YAML Content:</label>
|
|
507
|
+
<textarea
|
|
508
|
+
id="yaml-input"
|
|
509
|
+
rows={15}
|
|
510
|
+
cols={60}
|
|
511
|
+
placeholder="Paste your YAML content here..."
|
|
512
|
+
style={{
|
|
513
|
+
width: "100%",
|
|
514
|
+
marginTop: "5px",
|
|
515
|
+
fontFamily: "monospace",
|
|
516
|
+
fontSize: "12px",
|
|
517
|
+
resize: "vertical",
|
|
518
|
+
}}
|
|
519
|
+
/>
|
|
520
|
+
</div>
|
|
521
|
+
<div style={{ marginTop: "10px", fontSize: "12px", color: "#666" }}>
|
|
522
|
+
<button
|
|
523
|
+
type="button"
|
|
524
|
+
onClick={async () => {
|
|
525
|
+
try {
|
|
526
|
+
const clipboardText = await navigator.clipboard.readText();
|
|
527
|
+
const textarea = document.getElementById(
|
|
528
|
+
"yaml-input",
|
|
529
|
+
) as HTMLTextAreaElement;
|
|
530
|
+
if (textarea) {
|
|
531
|
+
textarea.value = clipboardText;
|
|
532
|
+
}
|
|
533
|
+
} catch (error) {
|
|
534
|
+
console.warn("Could not read from clipboard:", error);
|
|
535
|
+
}
|
|
536
|
+
}}
|
|
537
|
+
style={{
|
|
538
|
+
padding: "5px 10px",
|
|
539
|
+
fontSize: "12px",
|
|
540
|
+
cursor: "pointer",
|
|
541
|
+
}}
|
|
542
|
+
>
|
|
543
|
+
📋 Paste from Clipboard
|
|
544
|
+
</button>
|
|
545
|
+
<span style={{ marginLeft: "10px" }}>
|
|
546
|
+
Tip: Use Ctrl+V to paste YAML content directly into the text
|
|
547
|
+
area
|
|
548
|
+
</span>
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
context.editContext.confirm({
|
|
554
|
+
message: dialogContent,
|
|
555
|
+
header: "Import Items",
|
|
556
|
+
icon: "pi pi-upload",
|
|
557
|
+
accept: () => {
|
|
558
|
+
const textarea = document.getElementById(
|
|
559
|
+
"yaml-input",
|
|
560
|
+
) as HTMLTextAreaElement;
|
|
561
|
+
const yamlValue = textarea?.value?.trim();
|
|
562
|
+
resolve(yamlValue || null);
|
|
563
|
+
},
|
|
564
|
+
reject: () => resolve(null),
|
|
565
|
+
showCancel: true,
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
if (!yamlContent) return;
|
|
570
|
+
|
|
571
|
+
const request: ImportItemsRequest = {
|
|
572
|
+
targetItem: targetItem.descriptor,
|
|
573
|
+
yaml: yamlContent,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const response = await importItemsService(request);
|
|
577
|
+
|
|
578
|
+
if (response.type !== "success") {
|
|
579
|
+
throw new Error(response.details || "Import failed");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const result = response.data;
|
|
583
|
+
|
|
584
|
+
if (result && result.createdItems) {
|
|
585
|
+
context.editContext.showToast({
|
|
586
|
+
severity: "success",
|
|
587
|
+
summary: "Import Complete",
|
|
588
|
+
detail: `Successfully imported ${result.itemCount} item(s)`,
|
|
589
|
+
life: 3000,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Refresh by requesting a refresh
|
|
593
|
+
context.editContext.requestRefresh("immediate");
|
|
594
|
+
|
|
595
|
+
return result.createdItems;
|
|
596
|
+
}
|
|
597
|
+
} catch (error) {
|
|
598
|
+
const errorMessage =
|
|
599
|
+
error instanceof Error ? error.message : "Unknown error occurred";
|
|
600
|
+
|
|
601
|
+
context.editContext.showToast({
|
|
602
|
+
severity: "error",
|
|
603
|
+
summary: "Import Failed",
|
|
604
|
+
detail: errorMessage,
|
|
605
|
+
life: 5000,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Handle specific error cases
|
|
609
|
+
if (errorMessage.includes("already exist")) {
|
|
610
|
+
context.editContext.confirm({
|
|
611
|
+
message: `Some items already exist in the target location. ${errorMessage}`,
|
|
612
|
+
header: "Import Conflict",
|
|
613
|
+
icon: "pi pi-exclamation-triangle",
|
|
614
|
+
accept: () => {},
|
|
615
|
+
reject: () => {},
|
|
616
|
+
showCancel: false,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return false;
|
|
622
|
+
},
|
|
623
|
+
};
|
|
@@ -70,7 +70,7 @@ export function MultiLineText({
|
|
|
70
70
|
key={fieldItem.id + field.id + fieldItem.language + fieldItem.version}
|
|
71
71
|
value={value}
|
|
72
72
|
disabled={readOnly}
|
|
73
|
-
className="focus-shadow p-2 text-sm"
|
|
73
|
+
className="focus-shadow bg-gray-5 p-2 text-sm"
|
|
74
74
|
style={{ width: "100%" }}
|
|
75
75
|
rows={12}
|
|
76
76
|
onChange={(e) => {
|
|
@@ -164,7 +164,7 @@ export function SingleLineText({
|
|
|
164
164
|
key={fieldItem.id + field.id + fieldItem.language + fieldItem.version}
|
|
165
165
|
value={value || ""}
|
|
166
166
|
disabled={readOnly}
|
|
167
|
-
className="focus-shadow p-1.5 text-sm"
|
|
167
|
+
className="focus-shadow bg-gray-5 p-1.5 text-sm"
|
|
168
168
|
style={{ width: "100%" }}
|
|
169
169
|
onChange={handleChange}
|
|
170
170
|
onSelect={handleSelect}
|
|
@@ -1,17 +1,283 @@
|
|
|
1
1
|
import { useEditContext } from "../client/editContext";
|
|
2
2
|
import { User } from "./User";
|
|
3
|
+
import {
|
|
4
|
+
Popover,
|
|
5
|
+
PopoverContent,
|
|
6
|
+
PopoverTrigger,
|
|
7
|
+
} from "../../components/ui/popover";
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
import { getItemVisitors, ItemVisitor } from "../services/editService";
|
|
10
|
+
import { VerticalDotsIcon } from "../ui/Icons";
|
|
11
|
+
import { AboutDialog } from "../client/AboutDialog";
|
|
12
|
+
|
|
13
|
+
type UserListItem = {
|
|
14
|
+
type: "active" | "visitor";
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
user?: {
|
|
17
|
+
displayName?: string;
|
|
18
|
+
name: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
};
|
|
21
|
+
visitedAt?: string;
|
|
22
|
+
isCurrentUser?: boolean;
|
|
23
|
+
};
|
|
3
24
|
|
|
4
25
|
export function ActiveUsers() {
|
|
5
26
|
const editContext = useEditContext();
|
|
27
|
+
const [visitors, setVisitors] = useState<ItemVisitor[]>([]);
|
|
28
|
+
const [isLoadingVisitors, setIsLoadingVisitors] = useState(false);
|
|
29
|
+
|
|
6
30
|
const mySession = editContext?.activeSessions.find(
|
|
7
31
|
(x) => x.sessionId === editContext?.sessionId,
|
|
8
32
|
);
|
|
9
33
|
|
|
34
|
+
const activeSessions =
|
|
35
|
+
editContext?.activeSessions.filter(
|
|
36
|
+
(x) => x.item?.id === mySession?.item?.id,
|
|
37
|
+
) || [];
|
|
38
|
+
|
|
39
|
+
// Fetch visitors when current item changes
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (editContext?.currentItemDescriptor) {
|
|
42
|
+
setIsLoadingVisitors(true);
|
|
43
|
+
getItemVisitors(editContext.currentItemDescriptor)
|
|
44
|
+
.then((result) => {
|
|
45
|
+
if (result.type === "success" && result.data) {
|
|
46
|
+
setVisitors(result.data);
|
|
47
|
+
} else {
|
|
48
|
+
console.warn("Failed to fetch visitors:", result);
|
|
49
|
+
setVisitors([]);
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.catch((error) => {
|
|
53
|
+
console.error("Error fetching visitors:", error);
|
|
54
|
+
setVisitors([]);
|
|
55
|
+
})
|
|
56
|
+
.finally(() => {
|
|
57
|
+
setIsLoadingVisitors(false);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}, [
|
|
61
|
+
editContext?.currentItemDescriptor?.id,
|
|
62
|
+
editContext?.currentItemDescriptor?.language,
|
|
63
|
+
editContext?.currentItemDescriptor?.version,
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
// Convert active sessions to unified format
|
|
67
|
+
const activeUsers: UserListItem[] = activeSessions.map((session) => ({
|
|
68
|
+
type: "active",
|
|
69
|
+
sessionId: session.sessionId,
|
|
70
|
+
user: session.user,
|
|
71
|
+
isCurrentUser: session.sessionId === editContext?.sessionId,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
// Convert visitors to unified format, excluding those who are currently active
|
|
75
|
+
const activeUserNames = new Set(
|
|
76
|
+
activeSessions.map((s) => s.user.name.toLowerCase()),
|
|
77
|
+
);
|
|
78
|
+
const recentVisitors: UserListItem[] = visitors
|
|
79
|
+
.filter((visitor) => !activeUserNames.has(visitor.userName.toLowerCase()))
|
|
80
|
+
.slice(0, 10) // Limit to 10 recent visitors
|
|
81
|
+
.map((visitor) => ({
|
|
82
|
+
type: "visitor",
|
|
83
|
+
user: {
|
|
84
|
+
name: visitor.userName,
|
|
85
|
+
displayName: visitor.userName,
|
|
86
|
+
},
|
|
87
|
+
visitedAt: visitor.visitedAt,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const allUsers = [...activeUsers, ...recentVisitors];
|
|
91
|
+
const visibleUsers = activeSessions.slice(0, 3); // Only show active users in the avatar stack
|
|
92
|
+
const remainingActiveCount = Math.max(0, activeSessions.length - 3);
|
|
93
|
+
const totalCount = activeSessions.length + recentVisitors.length;
|
|
94
|
+
|
|
95
|
+
const handleAbout = () => {
|
|
96
|
+
editContext?.openDialog(AboutDialog, {});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleLogout = () => {
|
|
100
|
+
window.location.href = "/sitecore/login?action=logout";
|
|
101
|
+
};
|
|
102
|
+
|
|
10
103
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
104
|
+
<Popover>
|
|
105
|
+
<PopoverTrigger asChild>
|
|
106
|
+
<div
|
|
107
|
+
className="flex cursor-pointer items-center gap-2"
|
|
108
|
+
title={`${activeSessions.length} active user${activeSessions.length !== 1 ? "s" : ""}, ${recentVisitors.length} recent visitor${recentVisitors.length !== 1 ? "s" : ""}`}
|
|
109
|
+
>
|
|
110
|
+
{/* Overlapping user avatars */}
|
|
111
|
+
<div className="flex items-center -space-x-2">
|
|
112
|
+
{visibleUsers.map((session, index) => (
|
|
113
|
+
<div
|
|
114
|
+
key={session.sessionId}
|
|
115
|
+
className="relative rounded-full border-2 border-white"
|
|
116
|
+
style={{ zIndex: visibleUsers.length - index }}
|
|
117
|
+
>
|
|
118
|
+
<User session={session} />
|
|
119
|
+
</div>
|
|
120
|
+
))}
|
|
121
|
+
|
|
122
|
+
{/* Show count if more than 3 active users or if there are visitors */}
|
|
123
|
+
{(remainingActiveCount > 0 || recentVisitors.length > 0) && (
|
|
124
|
+
<div
|
|
125
|
+
className="relative flex h-7 w-7 items-center justify-center rounded-full border-2 border-white bg-gray-600 text-xs font-medium text-white"
|
|
126
|
+
style={{ zIndex: 0 }}
|
|
127
|
+
>
|
|
128
|
+
+{remainingActiveCount + recentVisitors.length}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<VerticalDotsIcon />
|
|
134
|
+
</div>
|
|
135
|
+
</PopoverTrigger>
|
|
136
|
+
|
|
137
|
+
<PopoverContent className="w-80 p-2" align="end">
|
|
138
|
+
{isLoadingVisitors && (
|
|
139
|
+
<div className="mb-2 text-xs text-gray-500">
|
|
140
|
+
Loading recent visitors...
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
<div className="max-h-96 space-y-1 overflow-y-auto">
|
|
145
|
+
{/* Active Users Section */}
|
|
146
|
+
{activeSessions.length > 0 && (
|
|
147
|
+
<>
|
|
148
|
+
{activeSessions.map((session) => {
|
|
149
|
+
const userName = session.user.displayName || session.user.name;
|
|
150
|
+
const idxBackslash = userName.indexOf("\\");
|
|
151
|
+
const displayName =
|
|
152
|
+
idxBackslash >= 0
|
|
153
|
+
? userName.substring(idxBackslash + 1)
|
|
154
|
+
: userName;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div
|
|
158
|
+
key={session.sessionId}
|
|
159
|
+
className="flex items-center gap-3 rounded p-2 hover:bg-gray-50"
|
|
160
|
+
>
|
|
161
|
+
<div className="relative">
|
|
162
|
+
<User session={session} />
|
|
163
|
+
<div className="absolute -right-0.5 -bottom-0.5 h-3 w-3 rounded-full border-2 border-white bg-green-500"></div>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="min-w-0 flex-1">
|
|
166
|
+
<div className="truncate text-sm font-medium text-gray-900">
|
|
167
|
+
{displayName}
|
|
168
|
+
</div>
|
|
169
|
+
<div className="truncate text-xs text-gray-500">
|
|
170
|
+
{session.user.email}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
{session.sessionId === editContext?.sessionId && (
|
|
174
|
+
<div className="text-xs font-medium text-blue-600">
|
|
175
|
+
You
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{/* Recent Visitors Section */}
|
|
185
|
+
{recentVisitors.length > 0 && (
|
|
186
|
+
<>
|
|
187
|
+
<div className="mt-3 mb-1 px-2 text-xs font-medium text-gray-600">
|
|
188
|
+
Recent Visitors ({recentVisitors.length})
|
|
189
|
+
</div>
|
|
190
|
+
{recentVisitors.map((visitor, index) => {
|
|
191
|
+
const userName =
|
|
192
|
+
visitor.user?.displayName || visitor.user?.name || "";
|
|
193
|
+
const idxBackslash = userName.indexOf("\\");
|
|
194
|
+
const displayName =
|
|
195
|
+
idxBackslash >= 0
|
|
196
|
+
? userName.substring(idxBackslash + 1)
|
|
197
|
+
: userName;
|
|
198
|
+
|
|
199
|
+
const visitedAt = visitor.visitedAt
|
|
200
|
+
? new Date(visitor.visitedAt)
|
|
201
|
+
: null;
|
|
202
|
+
const timeAgo = visitedAt ? formatTimeAgo(visitedAt) : "";
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
key={`visitor-${index}`}
|
|
207
|
+
className="flex items-center gap-3 rounded p-2 opacity-75 hover:bg-gray-50"
|
|
208
|
+
>
|
|
209
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-300 text-xs font-medium text-gray-600">
|
|
210
|
+
{displayName.charAt(0).toUpperCase()}
|
|
211
|
+
</div>
|
|
212
|
+
<div className="min-w-0 flex-1">
|
|
213
|
+
<div className="truncate text-sm font-medium text-gray-700">
|
|
214
|
+
{displayName}
|
|
215
|
+
</div>
|
|
216
|
+
<div className="truncate text-xs text-gray-500">
|
|
217
|
+
{timeAgo}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{/* Separator before actions */}
|
|
227
|
+
{(activeSessions.length > 0 || recentVisitors.length > 0) && (
|
|
228
|
+
<div className="my-2 border-t border-gray-200"></div>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{/* Action Items */}
|
|
232
|
+
<div className="space-y-1">
|
|
233
|
+
<div
|
|
234
|
+
className="flex cursor-pointer items-center gap-3 rounded p-2 hover:bg-gray-50"
|
|
235
|
+
onClick={handleAbout}
|
|
236
|
+
>
|
|
237
|
+
<div className="flex h-8 w-8 items-center justify-center">
|
|
238
|
+
<i
|
|
239
|
+
className="pi pi-info-circle text-gray-600"
|
|
240
|
+
style={{ fontSize: "1rem" }}
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
<div className="min-w-0 flex-1">
|
|
244
|
+
<div className="text-sm font-medium text-gray-900">About</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
<div
|
|
248
|
+
className="flex cursor-pointer items-center gap-3 rounded p-2 hover:bg-gray-50"
|
|
249
|
+
onClick={handleLogout}
|
|
250
|
+
>
|
|
251
|
+
<div className="flex h-8 w-8 items-center justify-center">
|
|
252
|
+
<i
|
|
253
|
+
className="pi pi-sign-out text-gray-600"
|
|
254
|
+
style={{ fontSize: "1rem" }}
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
<div className="min-w-0 flex-1">
|
|
258
|
+
<div className="text-sm font-medium text-gray-900">Log Out</div>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
{allUsers.length === 0 && !isLoadingVisitors && (
|
|
264
|
+
<div className="p-2 text-sm text-gray-500">No users found</div>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
</PopoverContent>
|
|
268
|
+
</Popover>
|
|
16
269
|
);
|
|
17
270
|
}
|
|
271
|
+
|
|
272
|
+
function formatTimeAgo(date: Date): string {
|
|
273
|
+
const now = new Date();
|
|
274
|
+
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
275
|
+
|
|
276
|
+
if (diffInSeconds < 60) return "Just now";
|
|
277
|
+
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
|
|
278
|
+
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
|
|
279
|
+
if (diffInSeconds < 604800)
|
|
280
|
+
return `${Math.floor(diffInSeconds / 86400)}d ago`;
|
|
281
|
+
|
|
282
|
+
return date.toLocaleDateString();
|
|
283
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useEditContext } from "../client/editContext";
|
|
2
|
+
import {
|
|
3
|
+
DropdownMenu,
|
|
4
|
+
DropdownMenuContent,
|
|
5
|
+
DropdownMenuItem,
|
|
6
|
+
DropdownMenuSeparator,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
} from "../../components/ui/dropdown-menu";
|
|
9
|
+
import { MenuItemGroup } from "../../config/types";
|
|
10
|
+
import { VerticalDotsIcon } from "../ui/Icons";
|
|
11
|
+
|
|
12
|
+
export function ItemActionsMenu({ isMobile }: { isMobile: boolean }) {
|
|
13
|
+
const editContext = useEditContext();
|
|
14
|
+
if (!editContext) return null;
|
|
15
|
+
|
|
16
|
+
const itemGroups: MenuItemGroup[] =
|
|
17
|
+
editContext.configuration.editor.itemActionsMenu?.itemsFactory(
|
|
18
|
+
editContext,
|
|
19
|
+
) || [];
|
|
20
|
+
|
|
21
|
+
if (isMobile) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="text-sm text-gray-200">
|
|
24
|
+
{itemGroups.map((group, groupIndex) => (
|
|
25
|
+
<div
|
|
26
|
+
key={`group-${group.id || groupIndex}`}
|
|
27
|
+
className="mb-3 flex flex-row flex-wrap items-center justify-end gap-3"
|
|
28
|
+
>
|
|
29
|
+
{group.items
|
|
30
|
+
.filter((x) => !x.disabled)
|
|
31
|
+
.map((item) => (
|
|
32
|
+
<div
|
|
33
|
+
key={item.id}
|
|
34
|
+
className="flex items-center gap-1"
|
|
35
|
+
onClick={() => {
|
|
36
|
+
item.command?.({});
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{typeof item.icon === "string" ? (
|
|
40
|
+
<i className={item.icon} style={{ fontSize: "1rem" }} />
|
|
41
|
+
) : (
|
|
42
|
+
<>{item.icon}</>
|
|
43
|
+
)}
|
|
44
|
+
{item.label}
|
|
45
|
+
</div>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<DropdownMenu>
|
|
55
|
+
<DropdownMenuTrigger asChild className="cursor-pointer">
|
|
56
|
+
<div className="p-1">
|
|
57
|
+
<VerticalDotsIcon />
|
|
58
|
+
</div>
|
|
59
|
+
</DropdownMenuTrigger>
|
|
60
|
+
<DropdownMenuContent className="w-64" align="start">
|
|
61
|
+
{itemGroups.map((group, groupIndex) => (
|
|
62
|
+
<div key={`group-${group.id || groupIndex}`}>
|
|
63
|
+
{group.items
|
|
64
|
+
.filter((x) => !x.disabled)
|
|
65
|
+
.map((item) => (
|
|
66
|
+
<DropdownMenuItem
|
|
67
|
+
key={item.id}
|
|
68
|
+
onClick={() => {
|
|
69
|
+
item.command?.({});
|
|
70
|
+
}}
|
|
71
|
+
className="flex items-center gap-2"
|
|
72
|
+
>
|
|
73
|
+
{typeof item.icon === "string" ? (
|
|
74
|
+
<i className={item.icon} style={{ fontSize: "1rem" }} />
|
|
75
|
+
) : (
|
|
76
|
+
<>{item.icon}</>
|
|
77
|
+
)}
|
|
78
|
+
{item.label}
|
|
79
|
+
</DropdownMenuItem>
|
|
80
|
+
))}
|
|
81
|
+
{groupIndex > 0 && groupIndex < itemGroups.length - 1 && (
|
|
82
|
+
<DropdownMenuSeparator />
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
))}
|
|
86
|
+
</DropdownMenuContent>
|
|
87
|
+
</DropdownMenu>
|
|
88
|
+
);
|
|
89
|
+
}
|