@elytracms/studio 0.0.26 → 0.0.28
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/convex/assets.ts +19 -10
- package/convex/authority.ts +60 -0
- package/convex/cliTokens.ts +4 -4
- package/convex/cms.ts +12 -12
- package/convex/content.ts +10 -3
- package/convex/delivery.ts +22 -4
- package/convex/guard.ts +28 -11
- package/convex/members.ts +40 -7
- package/convex/redirects.ts +67 -0
- package/convex/references.ts +11 -11
- package/convex/schema.ts +30 -1
- package/convex/sync.ts +17 -10
- package/convex/users.ts +63 -0
- package/dist/components/app-views/default-modules.d.ts +33 -14
- package/dist/components/app-views/default-modules.js +59 -20
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +32 -0
- package/dist/components/app-views/module.js +16 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.d.ts +15 -1
- package/dist/components/app-views/sidebar.js +10 -0
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/cms/asset-picker.js +4 -4
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/cell.js +16 -13
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +11 -26
- package/dist/components/cms/cms-store.js +25 -160
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.d.ts +10 -0
- package/dist/components/cms/collection-table.js +72 -72
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.d.ts +14 -0
- package/dist/components/cms/detail-editor.js +224 -72
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +26 -0
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/editable-cell.js +12 -7
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.d.ts +2 -0
- package/dist/components/cms/field-control.js +40 -30
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/filter-builder.d.ts +18 -0
- package/dist/components/cms/filter-builder.js +47 -6
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/import-export.js +5 -3
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/reference-confirm.js +8 -9
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/relation-picker.js +13 -10
- package/dist/components/cms/relation-picker.js.map +1 -1
- package/dist/components/cms/use-cell-edit.js +13 -7
- package/dist/components/cms/use-cell-edit.js.map +1 -1
- package/dist/components/cms/use-document-versions.d.ts +16 -0
- package/dist/components/cms/use-document-versions.js +31 -0
- package/dist/components/cms/use-document-versions.js.map +1 -0
- package/dist/components/cms/version-compare-text.d.ts +30 -0
- package/dist/components/cms/version-compare-text.js +113 -0
- package/dist/components/cms/version-compare-text.js.map +1 -0
- package/dist/components/cms/version-compare.d.ts +12 -0
- package/dist/components/cms/version-compare.js +271 -0
- package/dist/components/cms/version-compare.js.map +1 -0
- package/dist/components/cms/versions-pane.d.ts +3 -0
- package/dist/components/cms/versions-pane.js +47 -38
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.d.ts +21 -6
- package/dist/components/cms/view-bar.js +42 -57
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.d.ts +15 -0
- package/dist/components/collection-view/cms-collection-view-source.js +158 -0
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -0
- package/dist/components/collection-view/collection-view-context.d.ts +10 -0
- package/dist/components/collection-view/collection-view-context.js +26 -0
- package/dist/components/collection-view/collection-view-context.js.map +1 -0
- package/dist/components/current-user-menu.js +8 -1
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +18 -0
- package/dist/components/redirects/redirects-collection-view-source.js +114 -0
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -0
- package/dist/components/redirects/redirects-module.d.ts +18 -0
- package/dist/components/redirects/redirects-module.js +43 -0
- package/dist/components/redirects/redirects-module.js.map +1 -0
- package/dist/components/redirects/redirects-schema.d.ts +10 -0
- package/dist/components/redirects/redirects-schema.js +42 -0
- package/dist/components/redirects/redirects-schema.js.map +1 -0
- package/dist/components/redirects/redirects-store.d.ts +46 -0
- package/dist/components/redirects/redirects-store.js +124 -0
- package/dist/components/redirects/redirects-store.js.map +1 -0
- package/dist/components/references/references-panel.js +2 -2
- package/dist/components/references/references-panel.js.map +1 -1
- package/dist/components/section-nav.js +14 -5
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/users/user-search.d.ts +8 -0
- package/dist/components/users/user-search.js +13 -0
- package/dist/components/users/user-search.js.map +1 -0
- package/dist/components/users/users-collection-view-source.d.ts +28 -0
- package/dist/components/users/users-collection-view-source.js +157 -0
- package/dist/components/users/users-collection-view-source.js.map +1 -0
- package/dist/components/users/users-module.d.ts +23 -0
- package/dist/components/users/users-module.js +61 -0
- package/dist/components/users/users-module.js.map +1 -0
- package/dist/components/users/users-schema.d.ts +19 -0
- package/dist/components/users/users-schema.js +38 -0
- package/dist/components/users/users-schema.js.map +1 -0
- package/dist/components/users/users-store.d.ts +40 -0
- package/dist/components/users/users-store.js +87 -0
- package/dist/components/users/users-store.js.map +1 -0
- package/dist/lib/cms-views/index.d.ts +4 -5
- package/dist/lib/cms-views/index.js +4 -5
- package/dist/lib/cms-views/index.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +48 -1
- package/dist/lib/cms-views/view-model.js +77 -1
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +251 -0
- package/dist/lib/collection-view/collection-view-source.js +10 -0
- package/dist/lib/collection-view/collection-view-source.js.map +1 -0
- package/dist/lib/operations/session-hydration.d.ts +7 -0
- package/dist/lib/operations/session-hydration.js +7 -1
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +5 -1
- package/dist/lib/persistence/convex-adapter.js +148 -1
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +1 -8
- package/dist/lib/workspace/index.js +3 -9
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/router/match.d.ts +4 -0
- package/dist/router/match.js +8 -0
- package/dist/router/match.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/popover.d.ts +13 -0
- package/dist/ui/popover.js +17 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/ui/row-item.d.ts +16 -0
- package/dist/ui/row-item.js +18 -0
- package/dist/ui/row-item.js.map +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-compare.js","sourceRoot":"","sources":["../../../src/components/cms/version-compare.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { ArrowRight, ChevronDown, ChevronLeft, ChevronRight, Image as ImageIcon, RotateCcw } from 'lucide-react'\nimport { diffDocuments } from '@elytracms/core/content'\nimport type { BlockChange, FieldChange, PropChange } from '@elytracms/core/content'\nimport { isAssetNode, readAssetId, readDocumentRef } from '@elytracms/core/cms-core'\nimport type { CmsDocument, FieldDef, RestoreScope } from '@elytracms/core/cms-core'\nimport {\n Badge,\n Button,\n Code,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { useOperationCache } from '@elytracms/studio/lib/query'\nimport { localeChipLabel } from '@elytracms/studio/lib/locale'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { toRefArray } from './cell'\nimport { richTextToText, wordDiff } from './version-compare-text'\nimport type { WordToken } from './version-compare-text'\nimport { useDocumentEditor } from './document-editor-context'\nimport { useDocumentVersionsQuery } from './use-document-versions'\nimport { formatVersionTimestamp } from './versions-model'\n\n/**\n * EC-298 — the version compare view. Renders as the sticky **Compare** content tab\n * (the Versions sidebar pane stays on as the navigator).\n *\n * A calm change-list grouped BY FIELD (a localized field's per-locale variants sit\n * together): before | after across ONE continuous central divider. Text reads as a\n * prose diff (changed words lit in place — rich text via its markdown projection so\n * the content, not just \"changed\", is visible); assets as thumbnails; relations as\n * titles. Each field/locale (and each block, at property level) restores back to the\n * base version with a single ↩.\n */\nexport function VersionCompare() {\n const source = useCollectionView()\n const {\n collection,\n docRef,\n draft,\n dirty,\n viewReadOnly,\n save,\n replaceDraft,\n compare,\n setCompareBase,\n setCompareTarget,\n } = useDocumentEditor()\n const { versionsQuery, versionKey } = useDocumentVersionsQuery(docRef)\n const cache = useOperationCache()\n const [busy, setBusy] = useState(false)\n\n const view = versionsQuery.data ?? null\n const versions = useMemo(() => view?.versions ?? [], [view])\n\n const baseRecord = compare ? versions.find((v) => v.version === compare.base) : undefined\n const targetSnapshot: CmsDocument | undefined =\n compare?.target === 'draft'\n ? draft\n : versions.find((v) => v.version === compare?.target)?.snapshot\n\n const diff = useMemo(\n () =>\n baseRecord && targetSnapshot\n ? diffDocuments(collection, baseRecord.snapshot, targetSnapshot)\n : { changes: [], unchangedCount: 0 },\n [collection, baseRecord, targetSnapshot],\n )\n\n const fieldOrder = useMemo(\n () => new Map(collection.fields.map((f, i) => [f.name, i])),\n [collection],\n )\n const localeRank = useMemo(\n () => new Map(source.localeConfig.locales.map((l, i) => [l, i])),\n [source.localeConfig],\n )\n const groups = useMemo(\n () => groupByField(diff.changes, fieldOrder, localeRank),\n [diff.changes, fieldOrder, localeRank],\n )\n\n const restore = useCallback(\n async (scope: RestoreScope) => {\n if (!compare) return\n setBusy(true)\n try {\n if (dirty) save()\n const restored = await source.versioning?.restore(docRef, compare.base, scope)\n if (restored) replaceDraft(restored)\n await cache.invalidate(versionKey)\n } finally {\n setBusy(false)\n }\n },\n [compare, dirty, save, source, docRef, replaceDraft, cache, versionKey],\n )\n\n if (!compare) return null\n if (view === null) return <CompareNotice>Loading version history…</CompareNotice>\n if (!baseRecord || !targetSnapshot)\n return <CompareNotice>That version is no longer available. Pick another to compare.</CompareNotice>\n\n const canRestore = compare.target === 'draft' && !viewReadOnly\n const targetLabel = compare.target === 'draft' ? 'Current draft' : `v${compare.target}`\n\n // Step the base through history, skipping the target so the sides never collapse.\n const baseIndex = versions.findIndex((v) => v.version === compare.base)\n const nextBaseFor = (dir: -1 | 1): number | undefined => {\n let i = baseIndex + dir\n while (i >= 0 && i < versions.length && versions[i]!.version === compare.target) i += dir\n return i >= 0 && i < versions.length ? versions[i]!.version : undefined\n }\n const newerBase = nextBaseFor(-1) // versions are newest-first\n const olderBase = nextBaseFor(1)\n\n const baseOptions = versions\n .filter((v) => v.version !== compare.target)\n .map((v) => ({ value: v.version, label: `v${v.version}`, meta: formatVersionTimestamp(v.createdAt) }))\n const targetOptions = [\n { value: 'draft' as const, label: 'Current draft', meta: dirty ? 'unsaved edits' : undefined },\n ...versions\n .filter((v) => v.version !== compare.base)\n .map((v) => ({ value: v.version, label: `v${v.version}`, meta: formatVersionTimestamp(v.createdAt) })),\n ]\n\n return (\n <Stack gap=\"3\" className=\"mx-auto w-full max-w-5xl p-4 sm:p-6\">\n {/* The two sides — both re-pickable, so any pair can be compared; the\n Versions pane also sets the base on click. Stepper walks the base. */}\n <Row gap=\"2\" align=\"center\" wrap className=\"rounded-md border border-border/70 bg-muted/30 px-3 py-2\">\n <VersionMenu\n caption=\"Base\"\n current={`v${compare.base}`}\n meta={formatVersionTimestamp(baseRecord.createdAt)}\n options={baseOptions}\n onSelect={(v) => setCompareBase(v as number)}\n />\n <ArrowRight className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <VersionMenu\n caption=\"Compare with\"\n current={targetLabel}\n options={targetOptions}\n onSelect={(v) => setCompareTarget(v as number | 'draft')}\n />\n <Spacer />\n <Row gap=\"1\">\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n onClick={() => newerBase !== undefined && setCompareBase(newerBase)}\n disabled={newerBase === undefined}\n title=\"Compare a newer version\"\n aria-label=\"Compare a newer version\"\n >\n <ChevronLeft />\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n onClick={() => olderBase !== undefined && setCompareBase(olderBase)}\n disabled={olderBase === undefined}\n title=\"Compare an older version\"\n aria-label=\"Compare an older version\"\n >\n <ChevronRight />\n </Button>\n </Row>\n {canRestore ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void restore({ kind: 'all' })}\n disabled={busy || diff.changes.length === 0}\n title={`Restore the whole document to v${compare.base}`}\n >\n <RotateCcw /> Restore version\n </Button>\n ) : null}\n </Row>\n\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {diff.changes.length === 0\n ? 'No differences'\n : `${countLabel(diff.changes.length, 'change', 'changes')} · ${diff.unchangedCount} unchanged`}\n {!canRestore && diff.changes.length > 0\n ? ' · comparing two past versions — set “Compare with” to Current draft to restore'\n : ''}\n </Text>\n\n {diff.changes.length === 0 ? (\n <CompareNotice>These two versions are identical.</CompareNotice>\n ) : (\n <div className=\"relative\">\n {/* One continuous 2px centre divider behind the before | after columns. */}\n <div\n className=\"pointer-events-none absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-border\"\n aria-hidden\n />\n <TwoCol\n className=\"px-0.5 pb-1\"\n before={\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n Before · v{compare.base}\n </Text>\n }\n after={\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n After · {targetLabel}\n </Text>\n }\n />\n {groups.map((group) => (\n <FieldGroup key={group.field} group={group} canRestore={canRestore} busy={busy} onRestore={restore} />\n ))}\n </div>\n )}\n </Stack>\n )\n}\n\n/** A before | after grid; the continuous centre divider lives behind it (in the\n * parent), so the gap is where the two columns meet. */\nfunction TwoCol({ before, after, className }: { before: ReactNode; after: ReactNode; className?: string }) {\n return (\n <div className={cn('grid grid-cols-2 gap-x-6', className)}>\n <div className=\"min-w-0\">{before}</div>\n <div className=\"min-w-0\">{after}</div>\n </div>\n )\n}\n\nfunction VersionMenu({\n caption,\n current,\n meta,\n options,\n onSelect,\n}: {\n caption: string\n current: string\n meta?: string\n options: { value: number | 'draft'; label: string; meta?: string }[]\n onSelect: (value: number | 'draft') => void\n}) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"gap-1.5\">\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n {caption}\n </Text>\n <Text size=\"sm\" weight=\"medium\" mono>\n {current}\n </Text>\n {meta ? (\n <Text size=\"xs\" tone=\"muted\" className=\"hidden sm:inline\">\n {meta}\n </Text>\n ) : null}\n <ChevronDown className=\"size-3.5 text-muted-foreground\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"max-h-72 overflow-auto\">\n {options.map((o) => (\n <DropdownMenuItem key={String(o.value)} onClick={() => onSelect(o.value)} className=\"gap-2\">\n <Text size=\"sm\" weight=\"medium\" mono={o.value !== 'draft'}>\n {o.label}\n </Text>\n {o.meta ? (\n <Text size=\"xs\" tone=\"muted\">\n {o.meta}\n </Text>\n ) : null}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\ninterface FieldGroupData {\n field: string\n label: string\n type: FieldDef['type']\n entries: FieldChange[]\n}\n\nfunction FieldGroup({\n group,\n canRestore,\n busy,\n onRestore,\n}: {\n group: FieldGroupData\n canRestore: boolean\n busy: boolean\n onRestore: (scope: RestoreScope) => void\n}) {\n // A plain (non-localized) field is a single entry — label + value inline. A\n // localized field groups its locale variants under one label.\n const single = group.entries.length === 1 && group.entries[0]!.locale === undefined\n if (single) {\n const entry = group.entries[0]!\n return (\n <div className=\"border-t border-border/30 py-3\">\n <Row gap=\"2\" align=\"center\" className=\"mb-1.5\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {group.label}\n </Text>\n <KindTag kind={entry.kind} />\n <Spacer />\n {canRestore ? (\n <RestoreIcon busy={busy} title={`Restore ${group.label}`} onClick={() => onRestore(fieldScope(entry))} />\n ) : null}\n </Row>\n <EntryBody change={entry} canRestore={canRestore} busy={busy} onRestore={onRestore} />\n </div>\n )\n }\n return (\n <div className=\"border-t border-border/30 py-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"mb-2 block\">\n {group.label}\n </Text>\n <Stack gap=\"3\">\n {group.entries.map((entry) => (\n <div key={entry.locale ?? '__base__'}>\n <Row gap=\"2\" align=\"center\" className=\"mb-1\">\n {entry.locale ? (\n <Badge variant=\"outline\" className=\"px-1 py-0 text-[0.6rem] uppercase tracking-wide text-muted-foreground\">\n {localeChipLabel(entry.locale)}\n </Badge>\n ) : null}\n <KindTag kind={entry.kind} />\n <Spacer />\n {canRestore ? (\n <RestoreIcon\n busy={busy}\n title={`Restore ${group.label}${entry.locale ? ` (${localeChipLabel(entry.locale)})` : ''}`}\n onClick={() => onRestore(fieldScope(entry))}\n />\n ) : null}\n </Row>\n <EntryBody change={entry} canRestore={canRestore} busy={busy} onRestore={onRestore} />\n </div>\n ))}\n </Stack>\n </div>\n )\n}\n\nfunction EntryBody({\n change,\n canRestore,\n busy,\n onRestore,\n}: {\n change: FieldChange\n canRestore: boolean\n busy: boolean\n onRestore: (scope: RestoreScope) => void\n}) {\n const localeScope = change.locale !== undefined ? { locale: change.locale } : {}\n if (change.blocks) {\n return (\n <Stack gap=\"3\" className=\"mt-1\">\n {change.blocks.map((block) => (\n <BlockSection\n key={block.blockId}\n block={block}\n canRestore={canRestore}\n busy={busy}\n onRestore={() =>\n onRestore({ kind: 'block', field: change.field, blockId: block.blockId, ...localeScope })\n }\n />\n ))}\n </Stack>\n )\n }\n if (change.type === 'richText') {\n const before = richTextToText(change.before)\n const after = richTextToText(change.after)\n if (before === after)\n return <MutedNote>{before ? 'Text unchanged — formatting or whitespace differs.' : 'Content changed.'}</MutedNote>\n return <WordDiffCols before={before} after={after} />\n }\n if (change.kind === 'changed' && typeof change.before === 'string' && typeof change.after === 'string') {\n return <WordDiffCols before={change.before} after={change.after} />\n }\n return (\n <TwoCol\n before={<SideValue value={change.before} other={change.after} typeHint={change.type} />}\n after={<SideValue value={change.after} other={change.before} typeHint={change.type} />}\n />\n )\n}\n\nfunction BlockSection({\n block,\n canRestore,\n busy,\n onRestore,\n}: {\n block: BlockChange\n canRestore: boolean\n busy: boolean\n onRestore: () => void\n}) {\n const name = block.componentId.split('.').pop() ?? block.componentId\n return (\n <div>\n <Row gap=\"2\" align=\"center\" className=\"mb-1\">\n <Text size=\"xs\" weight=\"medium\" className=\"min-w-0 truncate\">\n {name}\n </Text>\n <Code tone=\"muted\">{block.componentId}</Code>\n <KindTag kind={block.kind} />\n <Spacer />\n {canRestore ? <RestoreIcon busy={busy} title=\"Restore this block\" onClick={onRestore} /> : null}\n </Row>\n {block.props && block.props.length > 0 ? (\n <Stack gap=\"2\">\n {block.props.map((prop) => (\n <PropRow key={prop.name} prop={prop} />\n ))}\n </Stack>\n ) : null}\n {block.slotsChanged ? <MutedNote>Nested content changed.</MutedNote> : null}\n {block.kind !== 'changed' && !block.props?.length ? (\n <MutedNote>{block.kind === 'added' ? 'Block added.' : 'Block removed.'}</MutedNote>\n ) : null}\n </div>\n )\n}\n\nfunction PropRow({ prop }: { prop: PropChange }) {\n const useWordDiff =\n prop.kind === 'changed' && typeof prop.before === 'string' && typeof prop.after === 'string'\n return (\n <div>\n <Text size=\"xs\" tone=\"subtle\" className=\"mb-0.5 block uppercase tracking-wide\">\n {prettyName(prop.name)}\n </Text>\n {useWordDiff ? (\n <WordDiffCols before={String(prop.before)} after={String(prop.after)} />\n ) : (\n <TwoCol\n before={<SideValue value={prop.before} other={prop.after} />}\n after={<SideValue value={prop.after} other={prop.before} />}\n />\n )}\n </div>\n )\n}\n\n/** One side of a non-text change — the value, or an \"empty\" placeholder. */\nfunction SideValue({ value, other, typeHint }: { value: unknown; other?: unknown; typeHint?: FieldDef['type'] }) {\n if (value === undefined || value === null || value === '') {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"italic\">\n empty\n </Text>\n )\n }\n return <ValueView value={value} other={other} typeHint={typeHint} />\n}\n\n/**\n * Read-only value display, dispatching on the field type where known and on the\n * self-describing value shape otherwise (block props carry no schema) — assets as\n * thumbnails, relations as titles, everything else as calm text.\n */\nfunction ValueView({ value, other, typeHint }: { value: unknown; other?: unknown; typeHint?: FieldDef['type'] }) {\n if (typeHint === 'asset' || isAssetNode(value)) return <AssetValue value={value} />\n if (typeHint === 'relation' || readDocumentRef(value)) return <RelationValue value={value} other={other} />\n if (typeHint === 'richText')\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {richTextToText(value) || 'rich text'}\n </Text>\n )\n if (typeHint === 'boolean' || typeof value === 'boolean')\n return (\n <Text size=\"sm\" mono>\n {value === true ? 'Yes' : 'No'}\n </Text>\n )\n if (typeHint === 'date')\n return (\n <Text size=\"sm\" mono>\n {String(value).slice(0, 10)}\n </Text>\n )\n if (typeof value === 'object')\n return (\n <Text size=\"sm\" tone=\"muted\">\n {Array.isArray(value) ? countLabel(value.length, 'item', 'items') : 'object'}\n </Text>\n )\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {String(value)}\n </Text>\n )\n}\n\nfunction RelationValue({ value, other }: { value: unknown; other?: unknown }) {\n const source = useCollectionView()\n const refs = toRefArray(value)\n if (refs.length === 0)\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {String(value)}\n </Text>\n )\n const otherRefs = other !== undefined ? toRefArray(other) : []\n // Disambiguate a same-titled but different target (the \"Ada → Ada\" trap): show\n // the id only when the titles would otherwise read identically.\n const label = refs\n .map((ref) => {\n const title = source.titleOf(ref)\n const collision = otherRefs.some((o) => o.id !== ref.id && source.titleOf(o) === title)\n return collision ? `${title} · ${ref.id}` : title\n })\n .join(', ')\n return <Text size=\"sm\">{label}</Text>\n}\n\nfunction AssetValue({ value }: { value: unknown }) {\n const ids = (Array.isArray(value) ? value : [value])\n .map(readAssetId)\n .filter((id): id is string => id !== undefined)\n if (ids.length === 0)\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {String(value)}\n </Text>\n )\n return (\n <div className=\"flex flex-wrap gap-2\">\n {ids.map((id) => (\n <AssetThumb key={id} id={id} />\n ))}\n </div>\n )\n}\n\n/** An asset as a thumbnail (image with a resolvable URL) or a labelled badge —\n * the visual diff for a cover-image change, not two identical filenames. */\nfunction AssetThumb({ id }: { id: string }) {\n const source = useCollectionView()\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const record = source.resolveAsset?.(id)\n const label = record?.title ?? record?.filename ?? source.titleOf({ collection: assetCollectionId, id })\n const isImage = record?.contentType?.startsWith('image/') ?? false\n const url = record?.url\n if (isImage && url && url.startsWith('http')) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-2\">\n <img\n src={url}\n alt={record?.alt ?? label}\n loading=\"lazy\"\n className=\"size-12 shrink-0 rounded border border-border bg-muted object-cover\"\n />\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n </span>\n )\n }\n return (\n <Badge variant=\"outline\">\n <ImageIcon className=\"size-3\" /> {label}\n </Badge>\n )\n}\n\n// --- Prose word diff --------------------------------------------------------------\n\n/** Prose-diff across the central divider, both sides from one LCS. */\nfunction WordDiffCols({ before, after }: { before: string; after: string }) {\n const wd = useMemo(() => wordDiff(before, after), [before, after])\n return (\n <TwoCol\n before={<WordTokens tokens={wd.before} side=\"before\" />}\n after={<WordTokens tokens={wd.after} side=\"after\" />}\n />\n )\n}\n\nfunction WordTokens({ tokens, side }: { tokens: WordToken[]; side: 'before' | 'after' }) {\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words leading-relaxed\">\n {tokens.map((token, i) =>\n token.changed ? (\n <mark\n key={i}\n className={cn(\n 'rounded-sm px-0.5',\n side === 'before'\n ? 'bg-destructive/15 text-destructive line-through decoration-destructive/40'\n : 'bg-success/20 text-foreground',\n )}\n >\n {token.text}\n </mark>\n ) : (\n <span key={i} className=\"text-muted-foreground\">\n {token.text}\n </span>\n ),\n )}\n </Text>\n )\n}\n\n// --- Small shared bits ------------------------------------------------------------\n\n/** A quiet \"· new\" / \"· removed\" tag; nothing for a plain change (implicit). */\nfunction KindTag({ kind }: { kind: FieldChange['kind'] }) {\n if (kind === 'changed') return null\n return (\n <Text size=\"xs\" tone=\"subtle\">\n · {kind === 'added' ? 'new' : 'removed'}\n </Text>\n )\n}\n\nfunction RestoreIcon({ busy, title, onClick }: { busy: boolean; title: string; onClick: () => void }) {\n return (\n <Button variant=\"ghost\" size=\"icon-sm\" disabled={busy} title={title} aria-label={title} onClick={onClick}>\n <RotateCcw />\n </Button>\n )\n}\n\nfunction MutedNote({ children }: { children: ReactNode }) {\n return (\n <Text size=\"sm\" tone=\"muted\" className=\"italic\">\n {children}\n </Text>\n )\n}\n\nfunction CompareNotice({ children }: { children: ReactNode }) {\n return (\n <div className=\"mx-auto w-full max-w-5xl p-4 sm:p-6\">\n <div className=\"rounded-md border border-dashed border-border/70 px-4 py-8 text-center\">\n <Text size=\"sm\" tone=\"muted\">\n {children}\n </Text>\n </div>\n </div>\n )\n}\n\nfunction fieldScope(change: FieldChange): RestoreScope {\n return { kind: 'field', field: change.field, ...(change.locale !== undefined ? { locale: change.locale } : {}) }\n}\n\n/**\n * Group changes BY FIELD (a localized field's per-locale variants together),\n * fields in schema order, locales in project order (base bucket first).\n */\nfunction groupByField(\n changes: FieldChange[],\n fieldOrder: Map<string, number>,\n localeRank: Map<string, number>,\n): FieldGroupData[] {\n const map = new Map<string, FieldGroupData>()\n for (const change of changes) {\n let group = map.get(change.field)\n if (!group) {\n group = { field: change.field, label: change.label, type: change.type, entries: [] }\n map.set(change.field, group)\n }\n group.entries.push(change)\n }\n const groups = [...map.values()]\n groups.sort(\n (a, b) => (fieldOrder.get(a.field) ?? Number.MAX_SAFE_INTEGER) - (fieldOrder.get(b.field) ?? Number.MAX_SAFE_INTEGER),\n )\n for (const group of groups) {\n group.entries.sort((a, b) => localeCompare(a.locale, b.locale, localeRank))\n }\n return groups\n}\n\nfunction localeCompare(a: string | undefined, b: string | undefined, rank: Map<string, number>): number {\n if (a === b) return 0\n if (a === undefined) return -1 // base bucket first\n if (b === undefined) return 1\n return (rank.get(a) ?? 99) - (rank.get(b) ?? 99)\n}\n\nfunction countLabel(n: number, one: string, many: string): string {\n return `${n} ${n === 1 ? one : many}`\n}\n\n/** camelCase / snake prop name → a readable label. */\nfunction prettyName(name: string): string {\n const spaced = name.replace(/[_-]+/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n"]}
|
|
@@ -10,5 +10,8 @@
|
|
|
10
10
|
* itself undoable) and the open editor adopts it via `replaceDraft`. Unpublish
|
|
11
11
|
* deliberately stays in the header — it routes through the EC-173
|
|
12
12
|
* reference-aware confirmation, which this pane does not duplicate.
|
|
13
|
+
*
|
|
14
|
+
* EC-298: each row also opens the compare view (⤢) and, once comparing, doubles
|
|
15
|
+
* as the navigator — clicking a row sets it as the base of the diff.
|
|
13
16
|
*/
|
|
14
17
|
export declare function VersionsPane(): import("react").JSX.Element;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback,
|
|
3
|
-
import { Eye, RotateCcw } from 'lucide-react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { Eye, GitCompareArrows, RotateCcw } from 'lucide-react';
|
|
4
4
|
import { Badge, Button, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { documentsSemanticEqual } from '@elytracms/core/content';
|
|
6
|
+
import { useOperationCache } from '@elytracms/studio/lib/query';
|
|
7
|
+
import { cn } from '@elytracms/studio/lib/utils';
|
|
8
|
+
import { useCollectionView } from '../collection-view/collection-view-context';
|
|
7
9
|
import { useDocumentEditor } from './document-editor-context';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
* may change the history, so re-read on the whole `documents` domain. */
|
|
11
|
-
const VERSION_RELOAD_OPS = ['documents'];
|
|
10
|
+
import { useDocumentVersionsQuery } from './use-document-versions';
|
|
11
|
+
import { formatVersionTimestamp } from './versions-model';
|
|
12
12
|
/**
|
|
13
13
|
* EC-224 — the document-level **Versions** inspector pane. Re-homes the EC-180
|
|
14
14
|
* VersionsPanel at the document level (the Sanity model): the continuous draft
|
|
@@ -21,37 +21,31 @@ const VERSION_RELOAD_OPS = ['documents'];
|
|
|
21
21
|
* itself undoable) and the open editor adopts it via `replaceDraft`. Unpublish
|
|
22
22
|
* deliberately stays in the header — it routes through the EC-173
|
|
23
23
|
* reference-aware confirmation, which this pane does not duplicate.
|
|
24
|
+
*
|
|
25
|
+
* EC-298: each row also opens the compare view (⤢) and, once comparing, doubles
|
|
26
|
+
* as the navigator — clicking a row sets it as the base of the diff.
|
|
24
27
|
*/
|
|
25
28
|
export function VersionsPane() {
|
|
26
|
-
const
|
|
27
|
-
const { docRef, draft, dirty, hasErrors, viewReadOnly, save, replaceDraft } = useDocumentEditor();
|
|
29
|
+
const source = useCollectionView();
|
|
30
|
+
const { collection, docRef, draft, dirty, hasErrors, viewReadOnly, save, replaceDraft, compare, compareActive, startCompare, setCompareBase, } = useDocumentEditor();
|
|
28
31
|
const [busy, setBusy] = useState(false);
|
|
29
|
-
const published =
|
|
32
|
+
const published = source.publishing?.isPublished(docRef) ?? false;
|
|
30
33
|
const cache = useOperationCache();
|
|
31
|
-
// The recorded version history, read through the one
|
|
32
|
-
//
|
|
33
|
-
// is authoritative regardless of which surface
|
|
34
|
-
// the
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
// state (principle 12).
|
|
39
|
-
const versionKey = useMemo(() => ['documentVersions', cms.projectId ?? '∅', docRef.collection, docRef.id], [cms.projectId, docRef.collection, docRef.id]);
|
|
40
|
-
const versionsQuery = useOperationQuery({
|
|
41
|
-
queryKey: versionKey,
|
|
42
|
-
loader: async () => {
|
|
43
|
-
await cms.flushPersistence();
|
|
44
|
-
return cms.loadDocumentVersions(docRef);
|
|
45
|
-
},
|
|
46
|
-
invalidateOn: VERSION_RELOAD_OPS,
|
|
47
|
-
});
|
|
34
|
+
// The recorded version history, read through the one shared query (EC-308/AD-17,
|
|
35
|
+
// deduped with the compare view). The flush-before-read ordering lives in the
|
|
36
|
+
// loader, so the listed history is authoritative regardless of which surface
|
|
37
|
+
// triggered the change; `invalidateOn: documents` refreshes it on any
|
|
38
|
+
// publish/restore/save from anywhere. Editor draft/dirty/busy stays local — it
|
|
39
|
+
// is not server state (principle 12).
|
|
40
|
+
const { versionsQuery, versionKey } = useDocumentVersionsQuery(docRef);
|
|
48
41
|
const view = versionsQuery.data ?? null;
|
|
49
42
|
const liveSnapshot = view?.publishedVersion !== undefined
|
|
50
43
|
? view.versions.find((v) => v.version === view.publishedVersion)?.snapshot
|
|
51
44
|
: undefined;
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
45
|
+
// SEMANTIC equality (EC-298) — the same engine the compare view uses, so the two
|
|
46
|
+
// never contradict: a doc that only differs by EC-277 envelope drift reads as
|
|
47
|
+
// "up to date", not "unpublished changes".
|
|
48
|
+
const liveMatchesDraft = liveSnapshot !== undefined && documentsSemanticEqual(collection, liveSnapshot, draft);
|
|
55
49
|
const canPublish = !liveMatchesDraft;
|
|
56
50
|
const handlePublish = useCallback(async () => {
|
|
57
51
|
setBusy(true);
|
|
@@ -62,13 +56,13 @@ export function VersionsPane() {
|
|
|
62
56
|
// version list so the new version shows.
|
|
63
57
|
if (dirty)
|
|
64
58
|
save();
|
|
65
|
-
await
|
|
59
|
+
await source.publishing?.publish(docRef);
|
|
66
60
|
await cache.invalidate(versionKey);
|
|
67
61
|
}
|
|
68
62
|
finally {
|
|
69
63
|
setBusy(false);
|
|
70
64
|
}
|
|
71
|
-
}, [
|
|
65
|
+
}, [source, docRef, dirty, save, cache, versionKey]);
|
|
72
66
|
const handleRestore = useCallback(async (version) => {
|
|
73
67
|
setBusy(true);
|
|
74
68
|
try {
|
|
@@ -77,7 +71,7 @@ export function VersionsPane() {
|
|
|
77
71
|
// wouldn't be undoable (restore snapshots the persisted draft).
|
|
78
72
|
if (dirty)
|
|
79
73
|
save();
|
|
80
|
-
const restored = await
|
|
74
|
+
const restored = await source.versioning?.restore(docRef, version);
|
|
81
75
|
if (restored)
|
|
82
76
|
replaceDraft(restored);
|
|
83
77
|
await cache.invalidate(versionKey);
|
|
@@ -85,14 +79,29 @@ export function VersionsPane() {
|
|
|
85
79
|
finally {
|
|
86
80
|
setBusy(false);
|
|
87
81
|
}
|
|
88
|
-
}, [
|
|
82
|
+
}, [source, docRef, dirty, save, replaceDraft, cache, versionKey]);
|
|
89
83
|
return (_jsxs(Stack, { gap: "3", className: "p-3", children: [canPublish ? (_jsxs(Row, { gap: "2", align: "center", className: "rounded-md border border-primary/40 bg-primary/5 px-2.5 py-2", children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Draft" }), _jsx(Text, { size: "xs", tone: "muted", children: published ? 'Unpublished changes' : 'Not published yet' })] }), _jsx(Spacer, {}), _jsxs(Button, { size: "sm", onClick: handlePublish, disabled: busy || hasErrors || viewReadOnly, title: viewReadOnly
|
|
90
84
|
? 'Switch to Draft to publish'
|
|
91
85
|
: hasErrors
|
|
92
86
|
? 'Fix the document’s validation issues to publish'
|
|
93
|
-
: undefined, children: [_jsx(Eye, {}), " Publish"] })] })) : (_jsx(Text, { size: "xs", tone: "muted", className: "px-0.5", children: "The live version is up to date \u2014 nothing to publish." })), _jsx(Stack, { gap: "1", children: view === null ? (_jsx(Text, { size: "xs", tone: "muted", className: "px-0.5", children: "Loading\u2026" })) : view.versions.length === 0 ? (_jsx(Text, { size: "xs", tone: "muted", className: "px-0.5", children: "No published versions yet." })) : (view.versions.map((entry) => {
|
|
87
|
+
: undefined, children: [_jsx(Eye, {}), " Publish"] })] })) : (_jsx(Text, { size: "xs", tone: "muted", className: "px-0.5", children: "The live version is up to date \u2014 nothing to publish." })), compareActive && compare ? (_jsxs(Text, { size: "xs", tone: "muted", className: "px-0.5", children: ["Comparing ", _jsxs("span", { className: "font-medium text-foreground", children: ["v", compare.base] }), " \u2192", ' ', compare.target === 'draft' ? 'Current draft' : `v${compare.target}`, ". Click a version to set it as the base."] })) : null, _jsx(Stack, { gap: "1", children: view === null ? (_jsx(Text, { size: "xs", tone: "muted", className: "px-0.5", children: "Loading\u2026" })) : view.versions.length === 0 ? (_jsx(Text, { size: "xs", tone: "muted", className: "px-0.5", children: "No published versions yet." })) : (view.versions.map((entry) => {
|
|
94
88
|
const isLive = entry.version === view.publishedVersion;
|
|
95
|
-
|
|
89
|
+
const isBase = compareActive && compare?.base === entry.version;
|
|
90
|
+
const isTarget = compareActive && compare?.target === entry.version;
|
|
91
|
+
const selectable = compareActive && !isTarget;
|
|
92
|
+
return (_jsxs(Row, { gap: "2", align: "center", ...(selectable
|
|
93
|
+
? {
|
|
94
|
+
role: 'button',
|
|
95
|
+
tabIndex: 0,
|
|
96
|
+
onClick: () => setCompareBase(entry.version),
|
|
97
|
+
onKeyDown: (e) => {
|
|
98
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
setCompareBase(entry.version);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
: {}), className: cn('rounded-md border px-2.5 py-1.5', isBase ? 'border-primary bg-primary/5' : 'border-border/60', selectable && !isBase && 'cursor-pointer hover:bg-muted/60'), children: [_jsxs(Code, { tone: isLive ? 'primary' : 'muted', children: ["v", entry.version] }), isLive ? _jsx(Badge, { variant: "success", children: "Published" }) : null, isBase ? (_jsx(Badge, { variant: "outline", children: "Base" })) : isTarget ? (_jsx(Badge, { variant: "outline", children: "Compared" })) : null, _jsx(Text, { size: "xs", tone: "subtle", mono: true, className: "truncate", children: formatVersionTimestamp(entry.createdAt) }), _jsx(Spacer, {}), compareActive ? null : (_jsxs(_Fragment, { children: [_jsx(Button, { size: "icon-sm", variant: "ghost", onClick: () => startCompare(entry.version), title: "Compare this version with the current draft", "aria-label": `Compare v${entry.version}`, children: _jsx(GitCompareArrows, {}) }), isLive ? null : (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => void handleRestore(entry.version), disabled: busy || viewReadOnly, title: viewReadOnly ? 'Switch to Draft to restore' : undefined, children: [_jsx(RotateCcw, {}), " Restore"] }))] }))] }, entry.version));
|
|
96
105
|
})) })] }));
|
|
97
106
|
}
|
|
98
107
|
//# sourceMappingURL=versions-pane.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versions-pane.js","sourceRoot":"","sources":["../../../src/components/cms/versions-pane.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport { Eye, RotateCcw } from 'lucide-react'\nimport { Badge, Button, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { useCms } from './cms-store'\nimport type { DocumentVersionsView } from './cms-store'\nimport { useDocumentEditor } from './document-editor-context'\nimport { formatVersionTimestamp, snapshotMatchesDraft } from './versions-model'\n\n/** Recorded-version freshness: any document command (publish/restore/upsert)\n * may change the history, so re-read on the whole `documents` domain. */\nconst VERSION_RELOAD_OPS = ['documents'] as const\n\n/**\n * EC-224 — the document-level **Versions** inspector pane. Re-homes the EC-180\n * VersionsPanel at the document level (the Sanity model): the continuous draft\n * on top with **Publish**, then the recorded version history (the live pin\n * badged), each older version **restorable** into the draft.\n *\n * Publish routes through the same pointer op as the header control\n * (`cms.publishDocument`); restore loads a recorded snapshot as the new working\n * draft (the operation snapshots the current draft first, so a rollback is\n * itself undoable) and the open editor adopts it via `replaceDraft`. Unpublish\n * deliberately stays in the header — it routes through the EC-173\n * reference-aware confirmation, which this pane does not duplicate.\n */\nexport function VersionsPane() {\n const cms = useCms()\n const { docRef, draft, dirty, hasErrors, viewReadOnly, save, replaceDraft } = useDocumentEditor()\n const [busy, setBusy] = useState(false)\n const published = cms.isPublished(docRef)\n const cache = useOperationCache()\n\n // The recorded version history, read through the one operations-backed query\n // cache (EC-308/AD-17). The flush-before-read ordering (so the listed history\n // is authoritative regardless of which surface triggered the change) lives IN\n // the loader, so every fetch — initial, invalidated, or a manual refetch —\n // is correctly sequenced. `invalidateOn: documents` refreshes it whenever a\n // publish/restore/save lands from anywhere (replacing the old `published`-flip\n // reload). The editor draft/dirty/busy state stays local — it is not server\n // state (principle 12).\n const versionKey = useMemo(\n () => ['documentVersions', cms.projectId ?? '∅', docRef.collection, docRef.id],\n [cms.projectId, docRef.collection, docRef.id],\n )\n const versionsQuery = useOperationQuery<DocumentVersionsView>({\n queryKey: versionKey,\n loader: async () => {\n await cms.flushPersistence()\n return cms.loadDocumentVersions(docRef)\n },\n invalidateOn: VERSION_RELOAD_OPS,\n })\n const view = versionsQuery.data ?? null\n\n const liveSnapshot =\n view?.publishedVersion !== undefined\n ? view.versions.find((v) => v.version === view.publishedVersion)?.snapshot\n : undefined\n // Value-equality (key-order-insensitive) so a just-published, unchanged doc\n // doesn't read as \"unpublished changes\" — see snapshotMatchesDraft.\n const liveMatchesDraft = liveSnapshot !== undefined && snapshotMatchesDraft(liveSnapshot, draft)\n const canPublish = !liveMatchesDraft\n\n const handlePublish = useCallback(async () => {\n setBusy(true)\n try {\n // Publish snapshots the SAVED draft — capture any unsaved edits first so\n // the published version is exactly what the editor shows. Await the\n // publish (it flushes + reconciles the pin, EC-225) before re-reading the\n // version list so the new version shows.\n if (dirty) save()\n await cms.publishDocument(docRef)\n await cache.invalidate(versionKey)\n } finally {\n setBusy(false)\n }\n }, [cms, docRef, dirty, save, cache, versionKey])\n\n const handleRestore = useCallback(\n async (version: number) => {\n setBusy(true)\n try {\n // Capture any unsaved buffer edits FIRST so the operation's pre-restore\n // snapshot records them — otherwise they'd be lost and the rollback\n // wouldn't be undoable (restore snapshots the persisted draft).\n if (dirty) save()\n const restored = await cms.restoreDocumentVersion(docRef, version)\n if (restored) replaceDraft(restored)\n await cache.invalidate(versionKey)\n } finally {\n setBusy(false)\n }\n },\n [cms, docRef, dirty, save, replaceDraft, cache, versionKey],\n )\n\n return (\n <Stack gap=\"3\" className=\"p-3\">\n {canPublish ? (\n <Row gap=\"2\" align=\"center\" className=\"rounded-md border border-primary/40 bg-primary/5 px-2.5 py-2\">\n <Stack gap=\"none\" className=\"min-w-0\">\n <Text size=\"sm\" weight=\"medium\">\n Draft\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {published ? 'Unpublished changes' : 'Not published yet'}\n </Text>\n </Stack>\n <Spacer />\n <Button\n size=\"sm\"\n onClick={handlePublish}\n disabled={busy || hasErrors || viewReadOnly}\n title={\n viewReadOnly\n ? 'Switch to Draft to publish'\n : hasErrors\n ? 'Fix the document’s validation issues to publish'\n : undefined\n }\n >\n <Eye /> Publish\n </Button>\n </Row>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n The live version is up to date — nothing to publish.\n </Text>\n )}\n\n <Stack gap=\"1\">\n {view === null ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n Loading…\n </Text>\n ) : view.versions.length === 0 ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n No published versions yet.\n </Text>\n ) : (\n view.versions.map((entry) => {\n const isLive = entry.version === view.publishedVersion\n return (\n <Row\n key={entry.version}\n gap=\"2\"\n align=\"center\"\n className=\"rounded-md border border-border/60 px-2.5 py-1.5\"\n >\n <Code tone={isLive ? 'primary' : 'muted'}>v{entry.version}</Code>\n {isLive ? <Badge variant=\"success\">Published</Badge> : null}\n <Text size=\"xs\" tone=\"subtle\" mono className=\"truncate\">\n {formatVersionTimestamp(entry.createdAt)}\n </Text>\n <Spacer />\n {isLive ? null : (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleRestore(entry.version)}\n disabled={busy || viewReadOnly}\n title={viewReadOnly ? 'Switch to Draft to restore' : undefined}\n >\n <RotateCcw /> Restore\n </Button>\n )}\n </Row>\n )\n })\n )}\n </Stack>\n </Stack>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"versions-pane.js","sourceRoot":"","sources":["../../../src/components/cms/versions-pane.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE7C,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAEzD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,EACJ,UAAU,EACV,MAAM,EACN,KAAK,EACL,KAAK,EACL,SAAS,EACT,YAAY,EACZ,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,aAAa,EACb,YAAY,EACZ,cAAc,GACf,GAAG,iBAAiB,EAAE,CAAA;IACvB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,KAAK,CAAA;IACjE,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAA;IAEjC,iFAAiF;IACjF,8EAA8E;IAC9E,6EAA6E;IAC7E,sEAAsE;IACtE,+EAA+E;IAC/E,sCAAsC;IACtC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAA;IACtE,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,IAAI,IAAI,CAAA;IAEvC,MAAM,YAAY,GAChB,IAAI,EAAE,gBAAgB,KAAK,SAAS;QAClC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,gBAAgB,CAAC,EAAE,QAAQ;QAC1E,CAAC,CAAC,SAAS,CAAA;IACf,iFAAiF;IACjF,8EAA8E;IAC9E,2CAA2C;IAC3C,MAAM,gBAAgB,GACpB,YAAY,KAAK,SAAS,IAAI,sBAAsB,CAAC,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,CAAA;IACvF,MAAM,UAAU,GAAG,CAAC,gBAAgB,CAAA;IAEpC,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,IAAI,CAAC;YACH,yEAAyE;YACzE,oEAAoE;YACpE,0EAA0E;YAC1E,yCAAyC;YACzC,IAAI,KAAK;gBAAE,IAAI,EAAE,CAAA;YACjB,MAAM,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;YACxC,MAAM,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;QACpC,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAA;IAEpD,MAAM,aAAa,GAAG,WAAW,CAC/B,KAAK,EAAE,OAAe,EAAE,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,IAAI,CAAC;YACH,wEAAwE;YACxE,oEAAoE;YACpE,gEAAgE;YAChE,IAAI,KAAK;gBAAE,IAAI,EAAE,CAAA;YACjB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YAClE,IAAI,QAAQ;gBAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;YACpC,MAAM,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;QACpC,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;IACH,CAAC,EACD,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAC/D,CAAA;IAED,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC3B,UAAU,CAAC,CAAC,CAAC,CACZ,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,8DAA8D,aAClG,MAAC,KAAK,IAAC,GAAG,EAAC,MAAM,EAAC,SAAS,EAAC,SAAS,aACnC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,sBAExB,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,mBAAmB,GACnD,IACD,EACR,KAAC,MAAM,KAAG,EACV,MAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,IAAI,IAAI,SAAS,IAAI,YAAY,EAC3C,KAAK,EACH,YAAY;4BACV,CAAC,CAAC,4BAA4B;4BAC9B,CAAC,CAAC,SAAS;gCACT,CAAC,CAAC,iDAAiD;gCACnD,CAAC,CAAC,SAAS,aAGjB,KAAC,GAAG,KAAG,gBACA,IACL,CACP,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,QAAQ,0EAExC,CACR,EAEA,aAAa,IAAI,OAAO,CAAC,CAAC,CAAC,CAC1B,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,QAAQ,2BACnC,gBAAM,SAAS,EAAC,6BAA6B,kBAAG,OAAO,CAAC,IAAI,IAAQ,aAAG,GAAG,EACnF,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,gDAC/D,CACR,CAAC,CAAC,CAAC,IAAI,EAER,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,YACX,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CACf,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,QAAQ,8BAExC,CACR,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAC/B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,QAAQ,2CAExC,CACR,CAAC,CAAC,CAAC,CACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,gBAAgB,CAAA;oBACtD,MAAM,MAAM,GAAG,aAAa,IAAI,OAAO,EAAE,IAAI,KAAK,KAAK,CAAC,OAAO,CAAA;oBAC/D,MAAM,QAAQ,GAAG,aAAa,IAAI,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC,OAAO,CAAA;oBACnE,MAAM,UAAU,GAAG,aAAa,IAAI,CAAC,QAAQ,CAAA;oBAC7C,OAAO,CACL,MAAC,GAAG,IAEF,GAAG,EAAC,GAAG,EACP,KAAK,EAAC,QAAQ,KACV,CAAC,UAAU;4BACb,CAAC,CAAC;gCACE,IAAI,EAAE,QAAQ;gCACd,QAAQ,EAAE,CAAC;gCACX,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC;gCAC5C,SAAS,EAAE,CAAC,CAAgB,EAAE,EAAE;oCAC9B,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;wCACvC,CAAC,CAAC,cAAc,EAAE,CAAA;wCAClB,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;oCAC/B,CAAC;gCACH,CAAC;6BACF;4BACH,CAAC,CAAC,EAAE,CAAC,EACP,SAAS,EAAE,EAAE,CACX,iCAAiC,EACjC,MAAM,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,kBAAkB,EAC3D,UAAU,IAAI,CAAC,MAAM,IAAI,kCAAkC,CAC5D,aAED,MAAC,IAAI,IAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,kBAAI,KAAK,CAAC,OAAO,IAAQ,EAChE,MAAM,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,0BAAkB,CAAC,CAAC,CAAC,IAAI,EAC1D,MAAM,CAAC,CAAC,CAAC,CACR,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,qBAAa,CACtC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CACb,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,yBAAiB,CAC1C,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,QAAC,SAAS,EAAC,UAAU,YACpD,sBAAsB,CAAC,KAAK,CAAC,SAAS,CAAC,GACnC,EACP,KAAC,MAAM,KAAG,EACT,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACtB,8BACE,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,EACf,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,EAC1C,KAAK,EAAC,6CAA6C,gBACvC,YAAY,KAAK,CAAC,OAAO,EAAE,YAEvC,KAAC,gBAAgB,KAAG,GACb,EACR,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACf,MAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,SAAS,EACjB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,EAChD,QAAQ,EAAE,IAAI,IAAI,YAAY,EAC9B,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,SAAS,aAE9D,KAAC,SAAS,KAAG,gBACN,CACV,IACA,CACJ,KAxDI,KAAK,CAAC,OAAO,CAyDd,CACP,CAAA;gBACH,CAAC,CAAC,CACH,GACK,IACF,CACT,CAAA;AACH,CAAC","sourcesContent":["import { useCallback, useState } from 'react'\nimport type { KeyboardEvent } from 'react'\nimport { Eye, GitCompareArrows, RotateCcw } from 'lucide-react'\nimport { Badge, Button, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { documentsSemanticEqual } from '@elytracms/core/content'\nimport { useOperationCache } from '@elytracms/studio/lib/query'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useDocumentEditor } from './document-editor-context'\nimport { useDocumentVersionsQuery } from './use-document-versions'\nimport { formatVersionTimestamp } from './versions-model'\n\n/**\n * EC-224 — the document-level **Versions** inspector pane. Re-homes the EC-180\n * VersionsPanel at the document level (the Sanity model): the continuous draft\n * on top with **Publish**, then the recorded version history (the live pin\n * badged), each older version **restorable** into the draft.\n *\n * Publish routes through the same pointer op as the header control\n * (`cms.publishDocument`); restore loads a recorded snapshot as the new working\n * draft (the operation snapshots the current draft first, so a rollback is\n * itself undoable) and the open editor adopts it via `replaceDraft`. Unpublish\n * deliberately stays in the header — it routes through the EC-173\n * reference-aware confirmation, which this pane does not duplicate.\n *\n * EC-298: each row also opens the compare view (⤢) and, once comparing, doubles\n * as the navigator — clicking a row sets it as the base of the diff.\n */\nexport function VersionsPane() {\n const source = useCollectionView()\n const {\n collection,\n docRef,\n draft,\n dirty,\n hasErrors,\n viewReadOnly,\n save,\n replaceDraft,\n compare,\n compareActive,\n startCompare,\n setCompareBase,\n } = useDocumentEditor()\n const [busy, setBusy] = useState(false)\n const published = source.publishing?.isPublished(docRef) ?? false\n const cache = useOperationCache()\n\n // The recorded version history, read through the one shared query (EC-308/AD-17,\n // deduped with the compare view). The flush-before-read ordering lives in the\n // loader, so the listed history is authoritative regardless of which surface\n // triggered the change; `invalidateOn: documents` refreshes it on any\n // publish/restore/save from anywhere. Editor draft/dirty/busy stays local — it\n // is not server state (principle 12).\n const { versionsQuery, versionKey } = useDocumentVersionsQuery(docRef)\n const view = versionsQuery.data ?? null\n\n const liveSnapshot =\n view?.publishedVersion !== undefined\n ? view.versions.find((v) => v.version === view.publishedVersion)?.snapshot\n : undefined\n // SEMANTIC equality (EC-298) — the same engine the compare view uses, so the two\n // never contradict: a doc that only differs by EC-277 envelope drift reads as\n // \"up to date\", not \"unpublished changes\".\n const liveMatchesDraft =\n liveSnapshot !== undefined && documentsSemanticEqual(collection, liveSnapshot, draft)\n const canPublish = !liveMatchesDraft\n\n const handlePublish = useCallback(async () => {\n setBusy(true)\n try {\n // Publish snapshots the SAVED draft — capture any unsaved edits first so\n // the published version is exactly what the editor shows. Await the\n // publish (it flushes + reconciles the pin, EC-225) before re-reading the\n // version list so the new version shows.\n if (dirty) save()\n await source.publishing?.publish(docRef)\n await cache.invalidate(versionKey)\n } finally {\n setBusy(false)\n }\n }, [source, docRef, dirty, save, cache, versionKey])\n\n const handleRestore = useCallback(\n async (version: number) => {\n setBusy(true)\n try {\n // Capture any unsaved buffer edits FIRST so the operation's pre-restore\n // snapshot records them — otherwise they'd be lost and the rollback\n // wouldn't be undoable (restore snapshots the persisted draft).\n if (dirty) save()\n const restored = await source.versioning?.restore(docRef, version)\n if (restored) replaceDraft(restored)\n await cache.invalidate(versionKey)\n } finally {\n setBusy(false)\n }\n },\n [source, docRef, dirty, save, replaceDraft, cache, versionKey],\n )\n\n return (\n <Stack gap=\"3\" className=\"p-3\">\n {canPublish ? (\n <Row gap=\"2\" align=\"center\" className=\"rounded-md border border-primary/40 bg-primary/5 px-2.5 py-2\">\n <Stack gap=\"none\" className=\"min-w-0\">\n <Text size=\"sm\" weight=\"medium\">\n Draft\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {published ? 'Unpublished changes' : 'Not published yet'}\n </Text>\n </Stack>\n <Spacer />\n <Button\n size=\"sm\"\n onClick={handlePublish}\n disabled={busy || hasErrors || viewReadOnly}\n title={\n viewReadOnly\n ? 'Switch to Draft to publish'\n : hasErrors\n ? 'Fix the document’s validation issues to publish'\n : undefined\n }\n >\n <Eye /> Publish\n </Button>\n </Row>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n The live version is up to date — nothing to publish.\n </Text>\n )}\n\n {compareActive && compare ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n Comparing <span className=\"font-medium text-foreground\">v{compare.base}</span> →{' '}\n {compare.target === 'draft' ? 'Current draft' : `v${compare.target}`}. Click a version to set it as the base.\n </Text>\n ) : null}\n\n <Stack gap=\"1\">\n {view === null ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n Loading…\n </Text>\n ) : view.versions.length === 0 ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n No published versions yet.\n </Text>\n ) : (\n view.versions.map((entry) => {\n const isLive = entry.version === view.publishedVersion\n const isBase = compareActive && compare?.base === entry.version\n const isTarget = compareActive && compare?.target === entry.version\n const selectable = compareActive && !isTarget\n return (\n <Row\n key={entry.version}\n gap=\"2\"\n align=\"center\"\n {...(selectable\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: () => setCompareBase(entry.version),\n onKeyDown: (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n setCompareBase(entry.version)\n }\n },\n }\n : {})}\n className={cn(\n 'rounded-md border px-2.5 py-1.5',\n isBase ? 'border-primary bg-primary/5' : 'border-border/60',\n selectable && !isBase && 'cursor-pointer hover:bg-muted/60',\n )}\n >\n <Code tone={isLive ? 'primary' : 'muted'}>v{entry.version}</Code>\n {isLive ? <Badge variant=\"success\">Published</Badge> : null}\n {isBase ? (\n <Badge variant=\"outline\">Base</Badge>\n ) : isTarget ? (\n <Badge variant=\"outline\">Compared</Badge>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\" mono className=\"truncate\">\n {formatVersionTimestamp(entry.createdAt)}\n </Text>\n <Spacer />\n {compareActive ? null : (\n <>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => startCompare(entry.version)}\n title=\"Compare this version with the current draft\"\n aria-label={`Compare v${entry.version}`}\n >\n <GitCompareArrows />\n </Button>\n {isLive ? null : (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleRestore(entry.version)}\n disabled={busy || viewReadOnly}\n title={viewReadOnly ? 'Switch to Draft to restore' : undefined}\n >\n <RotateCcw /> Restore\n </Button>\n )}\n </>\n )}\n </Row>\n )\n })\n )}\n </Stack>\n </Stack>\n )\n}\n"]}
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import type { CollectionDef } from '@elytracms/core/cms-core';
|
|
2
2
|
/**
|
|
3
|
-
* The
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* The view selector of the collection table (EC-169) — a segmented control that
|
|
4
|
+
* switches between the built-in "All documents" default and the project's
|
|
5
|
+
* code-defined views (EC-246b: views are config-as-code, so this only *picks* a
|
|
6
|
+
* lens; it never creates, edits, or deletes one at runtime). Renders nothing
|
|
7
|
+
* when the default view is the only one — no lonely single-tab chrome. Shares
|
|
8
|
+
* the segmented-control language of the locale switcher for a consistent header.
|
|
9
|
+
*
|
|
10
|
+
* AD-19: reads the named-views capability off the portable {@link useCollectionView}
|
|
11
|
+
* source, so a rail without config-declared views simply shows no selector.
|
|
8
12
|
*/
|
|
9
|
-
export declare function
|
|
13
|
+
export declare function ViewTabs(): import("react").JSX.Element | null;
|
|
14
|
+
/**
|
|
15
|
+
* Display options for the collection table (EC-246a) — one far-right control,
|
|
16
|
+
* aligned with where per-row actions live, folding together how the list is
|
|
17
|
+
* *shown*: the page hierarchy layout (flat vs tree, for hierarchical collections)
|
|
18
|
+
* and visible/ordered columns. Column order is a property of all columns (visible
|
|
19
|
+
* or not); the first visible column is the title/link and can't be hidden.
|
|
20
|
+
*/
|
|
21
|
+
export declare function DisplayMenu({ collection, hierarchical, treeMode, onToggleTree, }: {
|
|
10
22
|
collection: CollectionDef;
|
|
23
|
+
hierarchical: boolean;
|
|
24
|
+
treeMode: boolean;
|
|
25
|
+
onToggleTree: (tree: boolean) => void;
|
|
11
26
|
}): import("react").JSX.Element;
|
|
@@ -1,75 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, Input, Row, Spacer, Text, } from '@elytracms/studio/ui';
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowDown, ArrowUp, Check, List, ListTree, SlidersHorizontal } from 'lucide-react';
|
|
3
|
+
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Row, Text, } from '@elytracms/studio/ui';
|
|
5
4
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
6
|
-
import {
|
|
7
|
-
import { fieldLabel
|
|
5
|
+
import { moveColumn, resolveColumns, toggleColumn } from '@elytracms/studio/lib/cms-views';
|
|
6
|
+
import { fieldLabel } from './cms-store';
|
|
7
|
+
import { useCollectionView } from '../collection-view/collection-view-context';
|
|
8
8
|
/**
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* The view selector of the collection table (EC-169) — a segmented control that
|
|
10
|
+
* switches between the built-in "All documents" default and the project's
|
|
11
|
+
* code-defined views (EC-246b: views are config-as-code, so this only *picks* a
|
|
12
|
+
* lens; it never creates, edits, or deletes one at runtime). Renders nothing
|
|
13
|
+
* when the default view is the only one — no lonely single-tab chrome. Shares
|
|
14
|
+
* the segmented-control language of the locale switcher for a consistent header.
|
|
15
|
+
*
|
|
16
|
+
* AD-19: reads the named-views capability off the portable {@link useCollectionView}
|
|
17
|
+
* source, so a rail without config-declared views simply shows no selector.
|
|
14
18
|
*/
|
|
15
|
-
export function
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
export function ViewTabs() {
|
|
20
|
+
const source = useCollectionView();
|
|
21
|
+
const views = source.views?.list() ?? [];
|
|
22
|
+
// A single (default) view needs no selector — the breadcrumb names the collection.
|
|
23
|
+
if (views.length <= 1)
|
|
24
|
+
return null;
|
|
25
|
+
const activeId = source.views?.activeId();
|
|
20
26
|
const active = views.find((view) => view.id === activeId) ?? views[0];
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async function run(task) {
|
|
25
|
-
setBusy(true);
|
|
26
|
-
try {
|
|
27
|
-
await task();
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
setBusy(false);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return (_jsxs(Row, { gap: "1", wrap: true, className: "mt-3", "data-testid": "view-bar", children: [views.map((view) => (_jsxs("button", { type: "button", "aria-pressed": view.id === active.id, "data-testid": `view-chip-${view.id}`, onClick: () => cms.setActiveView(collectionId, view.id), className: cn('rounded-full border px-2.5 py-0.5 text-xs transition-colors', view.id === active.id
|
|
34
|
-
? 'border-primary/30 bg-primary/10 text-primary'
|
|
35
|
-
: 'border-border text-muted-foreground hover:bg-muted'), children: [view.name, view.id === active.id && dirty ? ' *' : ''] }, view.id))), active.id !== ALL_VIEW_ID ? (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": `Delete view ${active.name}`, "data-testid": "view-delete", disabled: busy, onClick: () => void run(() => cms.deleteView(collectionId, active.id)), title: "Delete this view (documents are never touched)", children: _jsx(Trash2, {}) })) : null, dirty ? (_jsxs(Row, { gap: "1", "data-testid": "view-dirty-actions", children: [_jsx(Badge, { variant: "warning", "data-testid": "view-dirty-badge", children: "Unsaved changes" }), active.id !== ALL_VIEW_ID ? (_jsxs(Button, { variant: "outline", size: "sm", "data-testid": "view-save", disabled: busy, onClick: () => void run(() => cms.saveActiveView(collectionId)), children: [_jsx(Save, {}), " Save"] })) : null, _jsx(Button, { variant: "outline", size: "sm", "data-testid": "view-save-as", disabled: busy, onClick: () => setSaveAsOpen(true), children: "Save as\u2026" }), _jsxs(Button, { variant: "ghost", size: "sm", "data-testid": "view-reset", disabled: busy, onClick: () => cms.resetTableState(collectionId), children: [_jsx(RotateCcw, {}), " Reset"] })] })) : null, _jsx(Spacer, {}), _jsx(ColumnsMenu, { collection: collection }), _jsx(SaveViewAsDialog, { open: saveAsOpen, onOpenChange: setSaveAsOpen, onSave: (name) => run(async () => {
|
|
36
|
-
const ok = await cms.saveViewAs(collectionId, name);
|
|
37
|
-
if (ok)
|
|
38
|
-
setSaveAsOpen(false);
|
|
39
|
-
return ok;
|
|
40
|
-
}) })] }));
|
|
41
|
-
}
|
|
42
|
-
/** Name prompt for "save as" — a named view is created from the working state. */
|
|
43
|
-
function SaveViewAsDialog({ open, onOpenChange, onSave, }) {
|
|
44
|
-
const [name, setName] = useState('');
|
|
45
|
-
function submit(event) {
|
|
46
|
-
event.preventDefault();
|
|
47
|
-
if (name.trim() === '')
|
|
48
|
-
return;
|
|
49
|
-
void onSave(name).then(() => setName(''));
|
|
50
|
-
}
|
|
51
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsx(DialogContent, { "data-testid": "save-view-dialog", children: _jsxs("form", { onSubmit: submit, className: "grid gap-4", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Save view" }), _jsx(DialogDescription, { children: "Saves the current filters, sort, and columns as a named view, shared with every editor on this project." })] }), _jsx(Input, { autoFocus: true, value: name, onChange: (event) => setName(event.target.value), placeholder: "View name", "aria-label": "View name", "data-testid": "save-view-name" }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { type: "submit", disabled: name.trim() === '', "data-testid": "save-view-confirm", children: "Save view" })] })] }) }) }));
|
|
27
|
+
return (_jsx(Row, { gap: "px", className: "rounded-md bg-muted/50 p-0.5", "data-testid": "view-bar", children: views.map((view) => (_jsx("button", { type: "button", "aria-pressed": view.id === active.id, "data-testid": `view-chip-${view.id}`, onClick: () => source.views?.setActive(view.id), className: cn('rounded-[5px] px-2.5 py-1 text-xs font-medium transition-colors', view.id === active.id
|
|
28
|
+
? 'bg-background text-foreground shadow-sm'
|
|
29
|
+
: 'text-muted-foreground hover:text-foreground'), children: view.name }, view.id))) }));
|
|
52
30
|
}
|
|
53
31
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* and
|
|
32
|
+
* Display options for the collection table (EC-246a) — one far-right control,
|
|
33
|
+
* aligned with where per-row actions live, folding together how the list is
|
|
34
|
+
* *shown*: the page hierarchy layout (flat vs tree, for hierarchical collections)
|
|
35
|
+
* and visible/ordered columns. Column order is a property of all columns (visible
|
|
36
|
+
* or not); the first visible column is the title/link and can't be hidden.
|
|
58
37
|
*/
|
|
59
|
-
function
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const state = cms.tableStateOf(collectionId);
|
|
38
|
+
export function DisplayMenu({ collection, hierarchical, treeMode, onToggleTree, }) {
|
|
39
|
+
const source = useCollectionView();
|
|
40
|
+
const state = source.viewState();
|
|
63
41
|
const columns = resolveColumns(collection, state);
|
|
64
42
|
const titleField = columns.find((column) => column.visible)?.field;
|
|
65
|
-
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children:
|
|
43
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": "Display options", title: "Display: layout and columns", "data-testid": "display-menu", children: _jsx(SlidersHorizontal, {}) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-64", children: [hierarchical ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuLabel, { children: "Layout" }), _jsx(LayoutOption, { label: "Flat list", icon: _jsx(List, {}), active: !treeMode, onSelect: () => onToggleTree(false) }), _jsx(LayoutOption, { label: "Page hierarchy", icon: _jsx(ListTree, {}), active: treeMode, onSelect: () => onToggleTree(true) }), _jsx(DropdownMenuSeparator, {})] })) : null, _jsx(DropdownMenuLabel, { children: "Columns \u00B7 first visible is the title" }), columns.map((column, index) => {
|
|
66
44
|
const field = collection.fields.find((f) => f.name === column.field);
|
|
67
45
|
if (!field)
|
|
68
46
|
return null;
|
|
69
47
|
const isTitle = column.field === titleField;
|
|
70
|
-
return (_jsxs(DropdownMenuItem, { onSelect: (event) => event.preventDefault(), "data-testid": `column-row-${column.field}`, children: [_jsx("input", { type: "checkbox", className: "size-3.5 accent-primary", checked: column.visible, disabled: isTitle, onChange: () =>
|
|
48
|
+
return (_jsxs(DropdownMenuItem, { onSelect: (event) => event.preventDefault(), "data-testid": `column-row-${column.field}`, children: [_jsx("input", { type: "checkbox", className: "size-3.5 accent-primary", checked: column.visible, disabled: isTitle, onChange: () => source.setViewState((prev) => toggleColumn(collection, prev, column.field)), "aria-label": `Toggle column ${fieldLabel(field)}`, "data-testid": `column-toggle-${column.field}` }), _jsxs(Text, { as: "span", size: "sm", className: "min-w-0 flex-1 truncate", children: [fieldLabel(field), isTitle ? (_jsxs(Text, { as: "span", size: "xs", tone: "subtle", children: [' ', "(title)"] })) : null] }), _jsxs(Row, { gap: "1", children: [_jsx(ColumnMoveButton, { label: `Move column ${fieldLabel(field)} up`, icon: _jsx(ArrowUp, {}), disabled: index <= 0, onMove: () => source.setViewState((prev) => moveColumn(collection, prev, column.field, -1)) }), _jsx(ColumnMoveButton, { label: `Move column ${fieldLabel(field)} down`, icon: _jsx(ArrowDown, {}), disabled: index >= columns.length - 1, onMove: () => source.setViewState((prev) => moveColumn(collection, prev, column.field, 1)) })] })] }, column.field));
|
|
71
49
|
})] })] }));
|
|
72
50
|
}
|
|
51
|
+
/** One layout choice (flat vs tree) — a checkmark marks the active mode. */
|
|
52
|
+
function LayoutOption({ label, icon, active, onSelect, }) {
|
|
53
|
+
return (_jsxs(DropdownMenuItem, { "data-testid": `layout-${active ? 'active' : 'option'}`, onSelect: (event) => {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
onSelect();
|
|
56
|
+
}, children: [icon, _jsx(Text, { as: "span", size: "sm", className: "min-w-0 flex-1 truncate", children: label }), active ? _jsx(Check, { className: "size-3.5 text-primary" }) : null] }));
|
|
57
|
+
}
|
|
73
58
|
function ColumnMoveButton({ label, icon, disabled, onMove, }) {
|
|
74
59
|
return (_jsx(Button, { variant: "ghost", size: "icon-sm", className: "size-5 [&_svg]:size-3", "aria-label": label, disabled: disabled, onClick: (event) => {
|
|
75
60
|
event.preventDefault();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-bar.js","sourceRoot":"","sources":["../../../src/components/cms/view-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport type { FormEvent, ReactNode } from 'react'\nimport { ArrowDown, ArrowUp, Columns3, RotateCcw, Save, Trash2 } from 'lucide-react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n Input,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ALL_VIEW_ID, moveColumn, resolveColumns, toggleColumn } from '@elytracms/studio/lib/cms-views'\nimport { fieldLabel, useCms } from './cms-store'\n\n/**\n * The saved-view bar of the collection table (EC-169): switch between the\n * unnamed \"All documents\" default and the project's named views, see when the\n * working state diverged (\"unsaved changes\" with save / save-as / reset), and\n * configure visible/ordered columns. View mutations go through the store's\n * operations-backed actions; deleting a view is view-only by construction.\n */\nexport function ViewBar({ collection }: { collection: CollectionDef }) {\n const cms = useCms()\n const collectionId = collection.id\n const views = cms.viewsOf(collectionId)\n const activeId = cms.activeViewId(collectionId)\n const active = views.find((view) => view.id === activeId) ?? views[0]!\n const dirty = cms.isViewDirty(collectionId)\n const [saveAsOpen, setSaveAsOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n\n async function run(task: () => Promise<boolean>) {\n setBusy(true)\n try {\n await task()\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Row gap=\"1\" wrap className=\"mt-3\" data-testid=\"view-bar\">\n {views.map((view) => (\n <button\n key={view.id}\n type=\"button\"\n aria-pressed={view.id === active.id}\n data-testid={`view-chip-${view.id}`}\n onClick={() => cms.setActiveView(collectionId, view.id)}\n className={cn(\n 'rounded-full border px-2.5 py-0.5 text-xs transition-colors',\n view.id === active.id\n ? 'border-primary/30 bg-primary/10 text-primary'\n : 'border-border text-muted-foreground hover:bg-muted',\n )}\n >\n {view.name}\n {view.id === active.id && dirty ? ' *' : ''}\n </button>\n ))}\n\n {active.id !== ALL_VIEW_ID ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete view ${active.name}`}\n data-testid=\"view-delete\"\n disabled={busy}\n onClick={() => void run(() => cms.deleteView(collectionId, active.id))}\n title=\"Delete this view (documents are never touched)\"\n >\n <Trash2 />\n </Button>\n ) : null}\n\n {dirty ? (\n <Row gap=\"1\" data-testid=\"view-dirty-actions\">\n <Badge variant=\"warning\" data-testid=\"view-dirty-badge\">\n Unsaved changes\n </Badge>\n {active.id !== ALL_VIEW_ID ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid=\"view-save\"\n disabled={busy}\n onClick={() => void run(() => cms.saveActiveView(collectionId))}\n >\n <Save /> Save\n </Button>\n ) : null}\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid=\"view-save-as\"\n disabled={busy}\n onClick={() => setSaveAsOpen(true)}\n >\n Save as…\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-testid=\"view-reset\"\n disabled={busy}\n onClick={() => cms.resetTableState(collectionId)}\n >\n <RotateCcw /> Reset\n </Button>\n </Row>\n ) : null}\n\n <Spacer />\n <ColumnsMenu collection={collection} />\n\n <SaveViewAsDialog\n open={saveAsOpen}\n onOpenChange={setSaveAsOpen}\n onSave={(name) =>\n run(async () => {\n const ok = await cms.saveViewAs(collectionId, name)\n if (ok) setSaveAsOpen(false)\n return ok\n })\n }\n />\n </Row>\n )\n}\n\n/** Name prompt for \"save as\" — a named view is created from the working state. */\nfunction SaveViewAsDialog({\n open,\n onOpenChange,\n onSave,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n onSave: (name: string) => Promise<void>\n}) {\n const [name, setName] = useState('')\n\n function submit(event: FormEvent) {\n event.preventDefault()\n if (name.trim() === '') return\n void onSave(name).then(() => setName(''))\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent data-testid=\"save-view-dialog\">\n <form onSubmit={submit} className=\"grid gap-4\">\n <DialogHeader>\n <DialogTitle>Save view</DialogTitle>\n <DialogDescription>\n Saves the current filters, sort, and columns as a named view, shared with every\n editor on this project.\n </DialogDescription>\n </DialogHeader>\n <Input\n autoFocus\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder=\"View name\"\n aria-label=\"View name\"\n data-testid=\"save-view-name\"\n />\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={name.trim() === ''} data-testid=\"save-view-confirm\">\n Save view\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\n/**\n * Visible/ordered column configuration — edits the working view state. Order is\n * a property of all columns (visible or not); a checkbox toggles visibility\n * without moving the column (EC-213). The first visible column is the title/link\n * and can't be hidden (the table always needs a title column).\n */\nfunction ColumnsMenu({ collection }: { collection: CollectionDef }) {\n const cms = useCms()\n const collectionId = collection.id\n const state = cms.tableStateOf(collectionId)\n const columns = resolveColumns(collection, state)\n const titleField = columns.find((column) => column.visible)?.field\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"columns-menu\">\n <Columns3 /> Columns\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"w-64\">\n <DropdownMenuLabel>Columns · first visible is the title</DropdownMenuLabel>\n {columns.map((column, index) => {\n const field = collection.fields.find((f) => f.name === column.field)\n if (!field) return null\n const isTitle = column.field === titleField\n return (\n <DropdownMenuItem\n key={column.field}\n onSelect={(event) => event.preventDefault()}\n data-testid={`column-row-${column.field}`}\n >\n <input\n type=\"checkbox\"\n className=\"size-3.5 accent-primary\"\n checked={column.visible}\n disabled={isTitle}\n onChange={() =>\n cms.setTableState(collectionId, (prev) =>\n toggleColumn(collection, prev, column.field),\n )\n }\n aria-label={`Toggle column ${fieldLabel(field)}`}\n data-testid={`column-toggle-${column.field}`}\n />\n <Text as=\"span\" size=\"sm\" className=\"min-w-0 flex-1 truncate\">\n {fieldLabel(field)}\n {isTitle ? (\n <Text as=\"span\" size=\"xs\" tone=\"subtle\">\n {' '}\n (title)\n </Text>\n ) : null}\n </Text>\n <Row gap=\"1\">\n <ColumnMoveButton\n label={`Move column ${fieldLabel(field)} up`}\n icon={<ArrowUp />}\n disabled={index <= 0}\n onMove={() =>\n cms.setTableState(collectionId, (prev) =>\n moveColumn(collection, prev, column.field, -1),\n )\n }\n />\n <ColumnMoveButton\n label={`Move column ${fieldLabel(field)} down`}\n icon={<ArrowDown />}\n disabled={index >= columns.length - 1}\n onMove={() =>\n cms.setTableState(collectionId, (prev) =>\n moveColumn(collection, prev, column.field, 1),\n )\n }\n />\n </Row>\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction ColumnMoveButton({\n label,\n icon,\n disabled,\n onMove,\n}: {\n label: string\n icon: ReactNode\n disabled: boolean\n onMove: () => void\n}) {\n return (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"size-5 [&_svg]:size-3\"\n aria-label={label}\n disabled={disabled}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n onMove()\n }}\n >\n {icon}\n </Button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"view-bar.js","sourceRoot":"","sources":["../../../src/components/cms/view-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport { ArrowDown, ArrowUp, Check, List, ListTree, SlidersHorizontal } from 'lucide-react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { moveColumn, resolveColumns, toggleColumn } from '@elytracms/studio/lib/cms-views'\nimport { fieldLabel } from './cms-store'\nimport { useCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * The view selector of the collection table (EC-169) — a segmented control that\n * switches between the built-in \"All documents\" default and the project's\n * code-defined views (EC-246b: views are config-as-code, so this only *picks* a\n * lens; it never creates, edits, or deletes one at runtime). Renders nothing\n * when the default view is the only one — no lonely single-tab chrome. Shares\n * the segmented-control language of the locale switcher for a consistent header.\n *\n * AD-19: reads the named-views capability off the portable {@link useCollectionView}\n * source, so a rail without config-declared views simply shows no selector.\n */\nexport function ViewTabs() {\n const source = useCollectionView()\n const views = source.views?.list() ?? []\n\n // A single (default) view needs no selector — the breadcrumb names the collection.\n if (views.length <= 1) return null\n\n const activeId = source.views?.activeId()\n const active = views.find((view) => view.id === activeId) ?? views[0]!\n\n return (\n <Row gap=\"px\" className=\"rounded-md bg-muted/50 p-0.5\" data-testid=\"view-bar\">\n {views.map((view) => (\n <button\n key={view.id}\n type=\"button\"\n aria-pressed={view.id === active.id}\n data-testid={`view-chip-${view.id}`}\n onClick={() => source.views?.setActive(view.id)}\n className={cn(\n 'rounded-[5px] px-2.5 py-1 text-xs font-medium transition-colors',\n view.id === active.id\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {view.name}\n </button>\n ))}\n </Row>\n )\n}\n\n/**\n * Display options for the collection table (EC-246a) — one far-right control,\n * aligned with where per-row actions live, folding together how the list is\n * *shown*: the page hierarchy layout (flat vs tree, for hierarchical collections)\n * and visible/ordered columns. Column order is a property of all columns (visible\n * or not); the first visible column is the title/link and can't be hidden.\n */\nexport function DisplayMenu({\n collection,\n hierarchical,\n treeMode,\n onToggleTree,\n}: {\n collection: CollectionDef\n hierarchical: boolean\n treeMode: boolean\n onToggleTree: (tree: boolean) => void\n}) {\n const source = useCollectionView()\n const state = source.viewState()\n const columns = resolveColumns(collection, state)\n const titleField = columns.find((column) => column.visible)?.field\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Display options\"\n title=\"Display: layout and columns\"\n data-testid=\"display-menu\"\n >\n <SlidersHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-64\">\n {hierarchical ? (\n <>\n <DropdownMenuLabel>Layout</DropdownMenuLabel>\n <LayoutOption\n label=\"Flat list\"\n icon={<List />}\n active={!treeMode}\n onSelect={() => onToggleTree(false)}\n />\n <LayoutOption\n label=\"Page hierarchy\"\n icon={<ListTree />}\n active={treeMode}\n onSelect={() => onToggleTree(true)}\n />\n <DropdownMenuSeparator />\n </>\n ) : null}\n <DropdownMenuLabel>Columns · first visible is the title</DropdownMenuLabel>\n {columns.map((column, index) => {\n const field = collection.fields.find((f) => f.name === column.field)\n if (!field) return null\n const isTitle = column.field === titleField\n return (\n <DropdownMenuItem\n key={column.field}\n onSelect={(event) => event.preventDefault()}\n data-testid={`column-row-${column.field}`}\n >\n <input\n type=\"checkbox\"\n className=\"size-3.5 accent-primary\"\n checked={column.visible}\n disabled={isTitle}\n onChange={() =>\n source.setViewState((prev) => toggleColumn(collection, prev, column.field))\n }\n aria-label={`Toggle column ${fieldLabel(field)}`}\n data-testid={`column-toggle-${column.field}`}\n />\n <Text as=\"span\" size=\"sm\" className=\"min-w-0 flex-1 truncate\">\n {fieldLabel(field)}\n {isTitle ? (\n <Text as=\"span\" size=\"xs\" tone=\"subtle\">\n {' '}\n (title)\n </Text>\n ) : null}\n </Text>\n <Row gap=\"1\">\n <ColumnMoveButton\n label={`Move column ${fieldLabel(field)} up`}\n icon={<ArrowUp />}\n disabled={index <= 0}\n onMove={() =>\n source.setViewState((prev) => moveColumn(collection, prev, column.field, -1))\n }\n />\n <ColumnMoveButton\n label={`Move column ${fieldLabel(field)} down`}\n icon={<ArrowDown />}\n disabled={index >= columns.length - 1}\n onMove={() =>\n source.setViewState((prev) => moveColumn(collection, prev, column.field, 1))\n }\n />\n </Row>\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** One layout choice (flat vs tree) — a checkmark marks the active mode. */\nfunction LayoutOption({\n label,\n icon,\n active,\n onSelect,\n}: {\n label: string\n icon: ReactNode\n active: boolean\n onSelect: () => void\n}) {\n return (\n <DropdownMenuItem\n data-testid={`layout-${active ? 'active' : 'option'}`}\n onSelect={(event) => {\n event.preventDefault()\n onSelect()\n }}\n >\n {icon}\n <Text as=\"span\" size=\"sm\" className=\"min-w-0 flex-1 truncate\">\n {label}\n </Text>\n {active ? <Check className=\"size-3.5 text-primary\" /> : null}\n </DropdownMenuItem>\n )\n}\n\nfunction ColumnMoveButton({\n label,\n icon,\n disabled,\n onMove,\n}: {\n label: string\n icon: ReactNode\n disabled: boolean\n onMove: () => void\n}) {\n return (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"size-5 [&_svg]:size-3\"\n aria-label={label}\n disabled={disabled}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n onMove()\n }}\n >\n {icon}\n </Button>\n )\n}\n"]}
|