@elytracms/studio 0.0.40 → 0.0.41
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/cms/detail-editor.js +67 -54
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/rich-text/component-embed.d.ts +7 -2
- package/dist/components/cms/rich-text/component-embed.js +60 -5
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.js +24 -5
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/embed-inspector-context.d.ts +50 -0
- package/dist/components/cms/rich-text/embed-inspector-context.js +12 -0
- package/dist/components/cms/rich-text/embed-inspector-context.js.map +1 -0
- package/dist/components/cms/rich-text/embed-inspector.d.ts +25 -0
- package/dist/components/cms/rich-text/embed-inspector.js +48 -0
- package/dist/components/cms/rich-text/embed-inspector.js.map +1 -0
- package/dist/components/cms/rich-text/embed-picker.d.ts +0 -2
- package/dist/components/cms/rich-text/embed-picker.js +0 -4
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.d.ts +7 -0
- package/dist/components/cms/rich-text/embed-props.js +9 -0
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +8 -2
- package/dist/components/cms/rich-text/insert.js +32 -3
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/rich-text/slash-command.d.ts +23 -6
- package/dist/components/cms/rich-text/slash-command.js +31 -23
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/version-compare.js +4 -3
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/components/component-preview.js +62 -9
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/lib/i18n/messages.en.js +3 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/package.json +2 -2
|
@@ -36,6 +36,10 @@ import { useDocumentVersionsQuery } from './use-document-versions';
|
|
|
36
36
|
import { discardDraftStatus } from './discard-draft';
|
|
37
37
|
import { IssuesPane } from './issues-pane';
|
|
38
38
|
import { DetailsPane } from './details-pane';
|
|
39
|
+
// 0.0.41: selection-driven embed inspector. Both modules are deliberately TipTap-free
|
|
40
|
+
// so the editor runtime stays behind rich-text/lazy (EC-322) — see their doc comments.
|
|
41
|
+
import { EmbedInspectorProvider } from './rich-text/embed-inspector-context';
|
|
42
|
+
import { EmbedInspector } from './rich-text/embed-inspector';
|
|
39
43
|
import { useSetBreadcrumb } from '../breadcrumb-context';
|
|
40
44
|
import { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
41
45
|
import { usePublishedDocuments } from '@elytracms/studio/lib/cms/published-documents';
|
|
@@ -291,6 +295,15 @@ function EditorBody({ projectId, collection, doc, focusPath, }) {
|
|
|
291
295
|
// `sidebar`-group field can open the Details pane (where it renders). Undefined
|
|
292
296
|
// = uncontrolled (the sidebar defaults to its first pane).
|
|
293
297
|
const [inspectorPaneId, setInspectorPaneId] = useState();
|
|
298
|
+
// 0.0.41: the selected rich-text component embed, if any — a `componentEmbed` node
|
|
299
|
+
// selection swaps the inspector sidebar for the embed's prop form. Fed by the
|
|
300
|
+
// rich-text field through the EmbedInspector context; `clearActiveEmbed` keys on the
|
|
301
|
+
// node id so a fast A→B reselect can't let A's deselect wipe B (selection race).
|
|
302
|
+
const [activeEmbed, setActiveEmbed] = useState(null);
|
|
303
|
+
const embedInspectorApi = useMemo(() => ({
|
|
304
|
+
setActiveEmbed: (payload) => setActiveEmbed(payload),
|
|
305
|
+
clearActiveEmbed: (nodeId) => setActiveEmbed((prev) => (prev?.nodeId === nodeId ? null : prev)),
|
|
306
|
+
}), []);
|
|
294
307
|
// EC-298: the open version comparison. The Compare tab is STICKY — opening it
|
|
295
308
|
// sets the selection and focuses the tab; tabbing to the real fields keeps it
|
|
296
309
|
// open (the selection persists) so an editor can flip between diff and fields;
|
|
@@ -683,60 +696,60 @@ function EditorBody({ projectId, collection, doc, focusPath, }) {
|
|
|
683
696
|
setCompareBase,
|
|
684
697
|
setCompareTarget,
|
|
685
698
|
exitCompare,
|
|
686
|
-
}, children: _jsxs("div", { className: cn('flex h-full min-h-0 flex-col', viewReadOnly && 'bg-info/[0.04]'), children: [_jsxs("div", { className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Row, { gap: "3", children: [_jsxs(Row, { gap: "2", align: "center", className: "min-w-0", children: [_jsx(Heading, { level: 1, size: "h2", className: "truncate", children: singleton ? localizedLabel(collection.form?.label, collection.id) : title || doc.id }), singleton ? null : _jsx(FallbackChip, { fallback: titleFallback, testId: "doc-title-fallback" })] }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "muted", className: "whitespace-nowrap", "data-testid": "autosave-status", children: source.lastError
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
699
|
+
}, children: _jsx(EmbedInspectorProvider, { value: embedInspectorApi, children: _jsxs("div", { className: cn('flex h-full min-h-0 flex-col', viewReadOnly && 'bg-info/[0.04]'), children: [_jsxs("div", { className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Row, { gap: "3", children: [_jsxs(Row, { gap: "2", align: "center", className: "min-w-0", children: [_jsx(Heading, { level: 1, size: "h2", className: "truncate", children: singleton ? localizedLabel(collection.form?.label, collection.id) : title || doc.id }), singleton ? null : _jsx(FallbackChip, { fallback: titleFallback, testId: "doc-title-fallback" })] }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "muted", className: "whitespace-nowrap", "data-testid": "autosave-status", children: source.lastError
|
|
700
|
+
? translate('cms.editor.saveFailed')
|
|
701
|
+
: explicitSave
|
|
702
|
+
? dirty
|
|
703
|
+
? translate('cms.editor.unsavedChanges')
|
|
704
|
+
: translate('cms.editor.saved')
|
|
705
|
+
: dirty
|
|
706
|
+
? translate('cms.editor.saving')
|
|
707
|
+
: translate('cms.editor.saved') }), canPublish ? _jsx(DocStateBadge, { published: published }) : null, routableCollection ? (liveUrl?.href ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", title: translate('cms.editor.openLiveUrl', { path: liveUrl.path }), children: _jsxs("a", { href: liveUrl.href, target: "_blank", rel: "noreferrer", "data-testid": "view-live", children: [_jsx(ExternalLink, {}), " ", translate('cms.editor.viewLive')] }) })) : (_jsxs(Button, { variant: "outline", size: "sm", disabled: true, "data-testid": "view-live-disabled", title: publishedDocs.status === 'loading'
|
|
708
|
+
? translate('cms.editor.loadingLiveUrl')
|
|
709
|
+
: !published
|
|
710
|
+
? translate('cms.editor.publishToViewLive')
|
|
711
|
+
: translate('cms.editor.setFrontendUrlHint'), children: [_jsx(ExternalLink, {}), " ", translate('cms.editor.viewLive')] }))) : null, canPublish ? (_jsxs(Row, { gap: "px", align: "center", children: [_jsx(Button, { size: "sm", onClick: handlePublish, disabled: publishDisabled, title: publishTitle, className: canPublishClose ? 'rounded-r-none' : undefined, "data-testid": "doc-publish", children: publishButtonLabel }), canPublishClose ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "sm", disabled: publishDisabled, "aria-label": translate('cms.editor.publishActions'), "data-testid": "doc-publish-actions", className: "rounded-l-none border-l border-primary-foreground/25 px-1.5", children: _jsx(ChevronDown, { className: "size-3.5" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { onClick: handlePublishAndClose, "data-testid": "doc-publish-and-close", children: [_jsx(Save, {}), " ", translate('cms.editor.publishAndClose')] }) })] })) : null] })) : null, explicitSave ? (_jsxs(Button, { size: "sm", onClick: save, disabled: !dirty, "data-testid": "doc-save", children: [_jsx(Check, {}), " ", translate('common.save')] })) : null, published || !singleton ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": translate('cms.editor.moreActions'), children: _jsx(MoreHorizontal, {}) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [discardStatus !== 'hidden' ? (_jsxs(DropdownMenuItem, { "data-testid": "doc-discard-draft", disabled: discardStatus !== 'ready', title: discardDisabledReason, onClick: () => setDiscardConfirm(true), children: [_jsx(RotateCcw, {}), " ", translate('cms.editor.discardDraft')] })) : null, published ? (_jsxs(DropdownMenuItem, { onClick: () => setConfirmAction('unpublish'), children: [_jsx(EyeOff, {}), " ", translate('cms.editor.unpublish')] })) : null, singleton ? null : (_jsxs(DropdownMenuItem, { "data-testid": "doc-delete", className: "text-destructive", onClick: () => setConfirmAction('delete'), children: [_jsx(Trash2, {}), " ", translate('common.delete')] }))] })] })) : null] }), showLensRow ? (_jsxs(Row, { gap: "3", className: "mt-3", wrap: true, children: [_jsxs(Row, { gap: "1", align: "center", children: [showContentTabs ? tabs.map((tab) => renderContentTab(tab)) : null, showReferences ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": true }), renderContentTab(REFERENCES_TAB)] })) : null, compare ? (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors', activeTab === COMPARE_TAB
|
|
712
|
+
? 'bg-accent text-accent-foreground'
|
|
713
|
+
: 'text-muted-foreground hover:bg-muted'), children: [_jsxs("button", { type: "button", onClick: () => setActiveTab(COMPARE_TAB), "aria-pressed": activeTab === COMPARE_TAB, className: "inline-flex items-center gap-1", children: [_jsx(GitCompareArrows, { className: "size-3", "aria-hidden": true }), " ", translate('cms.version.compare')] }), _jsx("button", { type: "button", onClick: exitCompare, "aria-label": translate('cms.version.closeCompare'), className: "ml-0.5 rounded p-0.5 hover:bg-background/40", children: _jsx(X, { className: "size-3" }) })] })) : null] }), _jsx(Spacer, {}), canLocales ? _jsx(LocaleSwitcher, {}) : null, canPublish ? (_jsxs(Row, { gap: "px", className: "rounded-md bg-muted/60 p-0.5", children: [_jsx(PerspectiveButton, { label: translate('cms.editor.draftState'), active: perspective === 'draft', onClick: () => setPerspective('draft') }), _jsx(PerspectiveButton, { label: translate('cms.editor.publishedState'), active: perspective === 'published', accent: "info", disabled: !published, title: published ? undefined : translate('cms.editor.publishToViewPublished'), onClick: () => setPerspective('published') })] })) : null] })) : null] }), viewReadOnly ? (_jsxs(Row, { gap: "2", className: "shrink-0 border-b border-info/30 bg-info/10 px-4 py-1.5 text-info sm:px-6", "data-testid": "published-view-banner", children: [_jsx(Lock, { className: "size-3.5 shrink-0", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "text-info", children: translate('cms.editor.publishedReadOnlyBanner') }), _jsx(Spacer, {}), _jsx("button", { type: "button", onClick: () => setPerspective('draft'), className: "text-xs font-medium underline-offset-2 hover:underline", children: translate('cms.editor.switchToDraft') })] })) : null, publishedPending ? (_jsx("div", { className: "grid min-h-0 flex-1 place-items-center p-8", children: _jsx(Text, { size: "sm", tone: "muted", children: publishedSnapshot.status === 'loading'
|
|
714
|
+
? translate('cms.editor.loadingPublishedVersion')
|
|
715
|
+
: translate('cms.editor.noPublishedVersion') }) })) : activeTab === COMPARE_TAB && compare ? (_jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: _jsx(VersionCompare, {}) }), _jsx(InspectorSidebar, { panes: trailingPanes, activeId: inspectorPaneId, onActiveChange: setInspectorPaneId, className: "w-[320px] shrink-0 border-l border-border" })] })) : canvasField ? (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(CompositionFieldEditor, { field: canvasField, value: getValue(canvasField), onChange: (v) => setValue(canvasField, v), readOnly: viewReadOnly || source.isFieldReadOnly(canvasField), trailingPanes: trailingPanes }, `${canvasField.name}:${restoreNonce}:${perspective}`) })) : activeTab === REFERENCES_TAB ? (_jsx("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-2xl", children: _jsx(ReferencesPanel, { projectId: projectId, source: { type: 'document', id: documentKey(ref) }, target: { type: 'document', id: documentKey(ref) } }) }) }) })) : (_jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-2xl", children: _jsx(Stack, { gap: "6", children: groups.map((group) => {
|
|
716
|
+
// EC-306: a conditionally/statically hidden group section is skipped
|
|
717
|
+
// entirely (its fields are also exempt from validation).
|
|
718
|
+
if (hiddenGroups.has(group.name))
|
|
719
|
+
return null;
|
|
720
|
+
// EC-273/EC-240: drop a fieldset whose every field is hidden — whether
|
|
721
|
+
// content-conditionally or for this principal — so the form never
|
|
722
|
+
// shows an empty section header.
|
|
723
|
+
const visible = group.fields.filter(isFieldShown);
|
|
724
|
+
if (visible.length === 0)
|
|
725
|
+
return null;
|
|
726
|
+
const section = group.section;
|
|
727
|
+
// EC-241: a section `title` is a LocalizedString ({ en, de } or a plain
|
|
728
|
+
// string) — resolve it against the active UI locale before rendering.
|
|
729
|
+
const sectionTitle = section?.title !== undefined ? localizedLabel(section.title, group.name) : undefined;
|
|
730
|
+
return (_jsx(CollapsibleSection, { collapsible: section?.collapsible, defaultOpen: section?.defaultOpen, title: _jsx(Text, { size: "sm", weight: "semibold", className: sectionTitle || group.name === DEFAULT_FIELD_GROUP ? undefined : 'capitalize', children: sectionTitle ??
|
|
731
|
+
(group.name === DEFAULT_FIELD_GROUP
|
|
732
|
+
? translate('cms.editor.defaultGroup')
|
|
733
|
+
: group.name) }), children: _jsx("div", { className: "grid grid-cols-1 gap-4 p-4 md:grid-cols-12", children: visible.map((field) => (_jsx("div", { className: cn('col-span-1', fieldWidthColSpan(field.form?.width)), children: renderFieldRow(field) }, field.name))) }) }, group.name));
|
|
734
|
+
}) }) }) }), activeEmbed ? (_jsx(EmbedInspector, { payload: activeEmbed, className: "w-[320px] shrink-0 border-l border-border" })) : (_jsx(InspectorSidebar, { panes: trailingPanes, activeId: inspectorPaneId, onActiveChange: setInspectorPaneId, className: "w-[320px] shrink-0 border-l border-border" }))] })), explicitSave ? (_jsx(UnsavedChangesGuard, { when: dirty, onSave: () => {
|
|
735
|
+
save();
|
|
736
|
+
return true;
|
|
737
|
+
}, description: translate('cms.editor.unsavedChangesGuard') })) : null, confirmAction !== null ? (_jsx(ReferenceConfirmDialog, { projectId: projectId, action: confirmAction, targets: confirmTargets, open: true, onOpenChange: (open) => {
|
|
738
|
+
if (!open)
|
|
739
|
+
setConfirmAction(null);
|
|
740
|
+
}, onConfirm: handleConfirmedAction })) : null, _jsx(Dialog, { open: discardConfirm, onOpenChange: (open) => { if (!open)
|
|
741
|
+
setDiscardConfirm(false); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.editor.discardDraftTitle') }), _jsx(DialogDescription, { children: translate('cms.editor.discardDraftDescription') })] }), _jsxs(DialogFooter, { className: "mt-4", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDiscardConfirm(false), disabled: discardBusy, children: translate('common.cancel') }), _jsx(Button, { variant: "destructive", size: "sm", "data-testid": "discard-draft-confirm", disabled: discardBusy, onClick: () => void handleDiscardDraft(), children: discardBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "animate-spin" }), " ", translate('cms.editor.discardingDraft')] })) : (_jsxs(_Fragment, { children: [_jsx(RotateCcw, {}), " ", translate('cms.editor.discardDraftConfirm')] })) })] })] }) }), _jsx(Dialog, { open: publishConfirm, onOpenChange: (open) => { if (!open) {
|
|
742
|
+
setPublishConfirm(false);
|
|
743
|
+
closeAfterPublishRef.current = false;
|
|
744
|
+
} }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.editor.publishUnpublishedRefsTitle') }), _jsx(DialogDescription, { children: translate('cms.editor.publishUnpublishedRefsDescription') })] }), _jsxs(Stack, { gap: "1", children: [referenceIssues.map((issue, i) => (_jsxs(Text, { size: "xs", tone: "muted", children: ["\u2022 ", issue.message] }, `${issue.meta?.id ?? i}`))), schemaIssues.length > 0 ? (_jsx(Text, { size: "xs", tone: "muted", className: "mt-1", children: translate(schemaIssues.length === 1
|
|
745
|
+
? 'cms.editor.publishAlsoHasIssues.one'
|
|
746
|
+
: 'cms.editor.publishAlsoHasIssues.other', { count: schemaIssues.length }) })) : null] }), _jsxs(DialogFooter, { className: "mt-4", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
747
|
+
setPublishConfirm(false);
|
|
748
|
+
closeAfterPublishRef.current = false;
|
|
749
|
+
}, children: translate('common.cancel') }), _jsx(Button, { size: "sm", "data-testid": "publish-anyway", onClick: () => {
|
|
750
|
+
setPublishConfirm(false);
|
|
751
|
+
void doPublish();
|
|
752
|
+
}, children: translate('cms.editor.publishAnyway') })] })] }) })] }) }) }));
|
|
740
753
|
}
|
|
741
754
|
/**
|
|
742
755
|
* EC-300/AD-16 — errors for composition embeds (hard references) whose target isn't
|