@elytracms/studio 0.0.51 → 0.0.53
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/auth.ts +135 -62
- package/convex/invites.ts +77 -0
- package/dist/components/account/account-settings.js +4 -4
- package/dist/components/account/credential-panel.js +1 -1
- package/dist/components/account/self-account-view-source.js +3 -3
- package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
- package/dist/components/ai/ai-chat-panel.js +4 -4
- package/dist/components/ai/ai-cms-panel.js +1 -1
- package/dist/components/ai/proposal-card.js +4 -4
- package/dist/components/ai/translate-document-action.js +1 -1
- package/dist/components/ai/translate-document-dialog.d.ts +1 -1
- package/dist/components/app-views/default-modules.d.ts +3 -3
- package/dist/components/app-views/default-modules.js +20 -20
- package/dist/components/app-views/sidebar.d.ts +1 -1
- package/dist/components/app-views/sidebar.js +2 -2
- package/dist/components/app-views/studio-config.d.ts +1 -1
- package/dist/components/assets/asset-detail.js +9 -9
- package/dist/components/assets/asset-grid.js +7 -7
- package/dist/components/assets/asset-thumb.d.ts +1 -1
- package/dist/components/assets/asset-thumb.js +2 -2
- package/dist/components/assets/assets-collection-view-source.js +3 -3
- package/dist/components/assets/assets-module.js +8 -8
- package/dist/components/assets/assets-store.d.ts +1 -1
- package/dist/components/assets/assets-store.js +1 -1
- package/dist/components/builder/blocks-model.js +1 -1
- package/dist/components/builder/blocks-panel.d.ts +1 -1
- package/dist/components/builder/blocks-panel.js +6 -6
- package/dist/components/builder/canvas.js +11 -11
- package/dist/components/builder/clipboard-actions.js +2 -2
- package/dist/components/builder/composition-field-editor-lazy.d.ts +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +1 -1
- package/dist/components/builder/composition-field-editor.js +9 -9
- package/dist/components/builder/insert-context.d.ts +2 -2
- package/dist/components/builder/insert-context.js +2 -2
- package/dist/components/builder/insert-menu.d.ts +2 -2
- package/dist/components/builder/insert-menu.js +1 -1
- package/dist/components/builder/layout-inspector.js +1 -1
- package/dist/components/builder/page-tree.js +5 -5
- package/dist/components/builder/property-inspector.js +3 -3
- package/dist/components/builder/reuse-context.d.ts +1 -1
- package/dist/components/builder/reuse-context.js +3 -3
- package/dist/components/builder/spacing-handles.js +2 -2
- package/dist/components/cms/asset-picker.js +4 -4
- package/dist/components/cms/bulk-actions.js +1 -1
- package/dist/components/cms/bulk-bar.d.ts +1 -1
- package/dist/components/cms/bulk-bar.js +3 -3
- package/dist/components/cms/cell-edit.js +1 -1
- package/dist/components/cms/cell.js +2 -2
- package/dist/components/cms/cms-index.js +1 -1
- package/dist/components/cms/cms-store.d.ts +2 -2
- package/dist/components/cms/cms-store.js +6 -6
- package/dist/components/cms/collection-table.js +17 -17
- package/dist/components/cms/detail-editor.js +31 -31
- package/dist/components/cms/document-diff-view.js +4 -4
- package/dist/components/cms/editable-cell.d.ts +1 -1
- package/dist/components/cms/editable-cell.js +6 -6
- package/dist/components/cms/field-control.js +15 -15
- package/dist/components/cms/field-renderer.d.ts +2 -2
- package/dist/components/cms/filter-builder.js +3 -3
- package/dist/components/cms/import-export.js +1 -1
- package/dist/components/cms/issues-pane.js +1 -1
- package/dist/components/cms/prop-fields.js +3 -3
- package/dist/components/cms/reference-confirm.js +1 -1
- package/dist/components/cms/relation-picker.js +1 -1
- package/dist/components/cms/rich-text/block-commands.d.ts +1 -1
- package/dist/components/cms/rich-text/component-embed.js +2 -2
- package/dist/components/cms/rich-text/editor.js +8 -8
- package/dist/components/cms/rich-text/embed-inspector.d.ts +1 -1
- package/dist/components/cms/rich-text/embed-inspector.js +2 -2
- package/dist/components/cms/rich-text/embed-picker.js +1 -1
- package/dist/components/cms/rich-text/embed-props.js +1 -1
- package/dist/components/cms/rich-text/extensions.d.ts +1 -1
- package/dist/components/cms/rich-text/extensions.js +4 -4
- package/dist/components/cms/rich-text/index.d.ts +1 -1
- package/dist/components/cms/rich-text/index.js +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +1 -1
- package/dist/components/cms/rich-text/lazy.d.ts +1 -1
- package/dist/components/cms/rich-text/lazy.js +1 -1
- package/dist/components/cms/rich-text/slash-command.js +1 -1
- package/dist/components/cms/rich-text/toolbar.d.ts +1 -1
- package/dist/components/cms/rich-text/toolbar.js +4 -4
- package/dist/components/cms/use-cell-edit.d.ts +1 -1
- package/dist/components/cms/use-cell-edit.js +2 -2
- package/dist/components/cms/use-compare-selection.d.ts +1 -1
- package/dist/components/cms/use-discard-controller.d.ts +1 -1
- package/dist/components/cms/use-discard-controller.js +2 -2
- package/dist/components/cms/use-document-versions.d.ts +1 -1
- package/dist/components/cms/use-document-versions.js +1 -1
- package/dist/components/cms/use-embed-inspector-host.d.ts +1 -1
- package/dist/components/cms/use-published-perspective.d.ts +2 -2
- package/dist/components/cms/use-published-perspective.js +2 -2
- package/dist/components/cms/use-restore-version.js +1 -1
- package/dist/components/cms/version-compare-lazy.js +1 -1
- package/dist/components/cms/version-compare.js +6 -6
- package/dist/components/cms/versions-model.d.ts +1 -1
- package/dist/components/cms/versions-pane.js +5 -5
- package/dist/components/cms/view-bar.js +2 -2
- package/dist/components/collab/collab-context.js +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +2 -2
- package/dist/components/components/component-preview.js +3 -3
- package/dist/components/components/components-index.js +2 -2
- package/dist/components/components/registry-browser.js +2 -2
- package/dist/components/components/transfer-dialog.js +5 -5
- package/dist/components/components/transfer.js +4 -4
- package/dist/components/current-user-menu.js +2 -2
- package/dist/components/delivery-health-banner.js +2 -2
- package/dist/components/operation-error.js +1 -1
- package/dist/components/project-list.js +5 -5
- package/dist/components/project-session.js +1 -1
- package/dist/components/project-switcher.js +3 -3
- package/dist/components/rail-store-status.js +1 -1
- package/dist/components/redirects/redirects-collection-view-source.js +3 -3
- package/dist/components/redirects/redirects-module.js +6 -6
- package/dist/components/section-nav.js +3 -3
- package/dist/components/section-surface.js +1 -1
- package/dist/components/studio-project-screen.js +7 -7
- package/dist/components/studio-shell.js +9 -9
- package/dist/components/use-hydration-phase.js +1 -1
- package/dist/components/users/users-collection-view-source.js +4 -4
- package/dist/components/users/users-module.js +9 -9
- package/dist/components/workspace-projects.js +1 -1
- package/dist/customization.d.ts +9 -9
- package/dist/customization.js +5 -5
- package/dist/lib/ai/anthropic-provider.d.ts +1 -1
- package/dist/lib/ai/apply-content.d.ts +1 -1
- package/dist/lib/ai/apply-content.js +3 -3
- package/dist/lib/ai/chat-state.d.ts +2 -2
- package/dist/lib/ai/config.d.ts +1 -1
- package/dist/lib/ai/create-flow.d.ts +2 -2
- package/dist/lib/ai/create-flow.js +3 -3
- package/dist/lib/ai/fake-provider.d.ts +1 -1
- package/dist/lib/ai/index.d.ts +15 -15
- package/dist/lib/ai/index.js +15 -15
- package/dist/lib/ai/knowledge.d.ts +1 -1
- package/dist/lib/ai/provenance.d.ts +1 -1
- package/dist/lib/ai/route-provider.d.ts +1 -1
- package/dist/lib/ai/route.d.ts +1 -1
- package/dist/lib/ai/shape.d.ts +1 -1
- package/dist/lib/ai/translate-document.d.ts +1 -1
- package/dist/lib/ai/translate-document.js +3 -3
- package/dist/lib/ai-panel.js +1 -1
- package/dist/lib/assets/index.d.ts +4 -4
- package/dist/lib/assets/index.js +4 -4
- package/dist/lib/assets/upload.d.ts +2 -2
- package/dist/lib/assets/upload.js +2 -2
- package/dist/lib/auth/convex-auth-adapter.d.ts +53 -2
- package/dist/lib/auth/convex-auth-adapter.js +149 -68
- package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
- package/dist/lib/auth/dev-auto-login-adapter.d.ts +1 -1
- package/dist/lib/auth/index.d.ts +9 -9
- package/dist/lib/auth/index.js +10 -10
- package/dist/lib/auth/local-auth-adapter.d.ts +1 -1
- package/dist/lib/auth/route-guard.d.ts +1 -1
- package/dist/lib/auth-errors.d.ts +1 -1
- package/dist/lib/clipboard/index.d.ts +3 -3
- package/dist/lib/clipboard/index.js +3 -3
- package/dist/lib/cms/published-documents.d.ts +1 -1
- package/dist/lib/cms-views/index.d.ts +2 -2
- package/dist/lib/cms-views/index.js +2 -2
- package/dist/lib/collection-view/collection-view-source.d.ts +4 -4
- package/dist/lib/data/collection-content.js +1 -1
- package/dist/lib/data/data-store.js +6 -6
- package/dist/lib/data/index.d.ts +4 -4
- package/dist/lib/data/index.js +4 -4
- package/dist/lib/data/sample-capture.js +4 -4
- package/dist/lib/data/sources-store.d.ts +1 -1
- package/dist/lib/diff-view-preference.js +1 -1
- package/dist/lib/dnd/gap-drag.d.ts +1 -1
- package/dist/lib/i18n/i18n.d.ts +1 -1
- package/dist/lib/i18n/i18n.js +1 -1
- package/dist/lib/i18n/index.d.ts +4 -4
- package/dist/lib/i18n/index.js +3 -3
- package/dist/lib/i18n/messages.en.d.ts +1 -1
- package/dist/lib/i18n/messages.en.js +1 -1
- package/dist/lib/locale/binding-fallback.d.ts +1 -1
- package/dist/lib/locale/binding-fallback.js +2 -2
- package/dist/lib/locale/index.d.ts +6 -6
- package/dist/lib/locale/index.js +6 -6
- package/dist/lib/locale/locale-switcher.js +3 -3
- package/dist/lib/locale/use-studio-locale.js +1 -1
- package/dist/lib/operations/asset-repository.d.ts +1 -1
- package/dist/lib/operations/cms-persistence.d.ts +1 -1
- package/dist/lib/operations/cms-persistence.js +1 -1
- package/dist/lib/operations/index.d.ts +6 -6
- package/dist/lib/operations/index.js +6 -6
- package/dist/lib/operations/session-hydration.d.ts +1 -1
- package/dist/lib/operations/session-hydration.js +1 -1
- package/dist/lib/operations/studio-operations.d.ts +1 -1
- package/dist/lib/operations/studio-operations.js +3 -3
- package/dist/lib/persistence/convex-adapter.js +2 -2
- package/dist/lib/persistence/convex-blob-storage.d.ts +1 -1
- package/dist/lib/persistence/convex-blob-storage.js +1 -1
- package/dist/lib/persistence/deployment-readiness.d.ts +2 -2
- package/dist/lib/persistence/index.d.ts +6 -6
- package/dist/lib/persistence/index.js +7 -7
- package/dist/lib/persona/labels.js +1 -1
- package/dist/lib/query/index.d.ts +2 -2
- package/dist/lib/query/index.js +1 -1
- package/dist/lib/rail.js +1 -1
- package/dist/lib/references/impact.d.ts +1 -1
- package/dist/lib/references/impact.js +1 -1
- package/dist/lib/references/index.d.ts +3 -3
- package/dist/lib/references/index.js +3 -3
- package/dist/lib/references/references-store.d.ts +2 -2
- package/dist/lib/references/references-store.js +3 -3
- package/dist/lib/registry/index.js +2 -2
- package/dist/lib/theme.js +1 -1
- package/dist/lib/webhooks/config.d.ts +1 -1
- package/dist/lib/webhooks/config.js +2 -2
- package/dist/lib/webhooks/dispatcher.js +2 -2
- package/dist/lib/webhooks/index.d.ts +6 -6
- package/dist/lib/webhooks/index.js +4 -4
- package/dist/lib/workspace/index.d.ts +7 -7
- package/dist/lib/workspace/index.js +3 -3
- package/dist/lib/workspace/switcher-source.d.ts +1 -1
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.js +1 -1
- package/dist/ui/avatar.js +1 -1
- package/dist/ui/badge.js +1 -1
- package/dist/ui/button.js +1 -1
- package/dist/ui/card.js +1 -1
- package/dist/ui/collapsible-section.js +2 -2
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dropdown-menu.js +1 -1
- package/dist/ui/field.js +1 -1
- package/dist/ui/index.d.ts +19 -19
- package/dist/ui/index.js +19 -19
- package/dist/ui/input.js +1 -1
- package/dist/ui/inspector.js +1 -1
- package/dist/ui/layout.js +1 -1
- package/dist/ui/panel.js +2 -2
- package/dist/ui/popover.js +1 -1
- package/dist/ui/row-item.js +2 -2
- package/dist/ui/segmented-control.js +1 -1
- package/dist/ui/separator.js +1 -1
- package/dist/ui/toolbar.js +2 -2
- package/dist/ui/typography.js +1 -1
- package/package.json +3 -3
- package/dist/components/assets/asset-library.d.ts +0 -14
- package/dist/components/assets/asset-library.js +0 -202
- package/dist/components/assets/asset-library.js.map +0 -1
- package/dist/components/backend-gate.d.ts +0 -19
- package/dist/components/backend-gate.js +0 -64
- package/dist/components/backend-gate.js.map +0 -1
- package/dist/components/builder/composition-scope.d.ts +0 -16
- package/dist/components/builder/composition-scope.js +0 -7
- package/dist/components/builder/composition-scope.js.map +0 -1
- package/dist/components/cms/alias-selector.d.ts +0 -20
- package/dist/components/cms/alias-selector.js +0 -34
- package/dist/components/cms/alias-selector.js.map +0 -1
- package/dist/components/cms/collections-sidebar.d.ts +0 -18
- package/dist/components/cms/collections-sidebar.js +0 -36
- package/dist/components/cms/collections-sidebar.js.map +0 -1
- package/dist/components/cms/new-page-dialog.d.ts +0 -22
- package/dist/components/cms/new-page-dialog.js +0 -42
- package/dist/components/cms/new-page-dialog.js.map +0 -1
- package/dist/components/cms/rich-text/embed-control.d.ts +0 -17
- package/dist/components/cms/rich-text/embed-control.js +0 -62
- package/dist/components/cms/rich-text/embed-control.js.map +0 -1
- package/dist/components/cms/rich-text/image-control.d.ts +0 -16
- package/dist/components/cms/rich-text/image-control.js +0 -36
- package/dist/components/cms/rich-text/image-control.js.map +0 -1
- package/dist/components/components/fixture-runner.d.ts +0 -10
- package/dist/components/components/fixture-runner.js +0 -27
- package/dist/components/components/fixture-runner.js.map +0 -1
- package/dist/components/references/builder-deep-link.d.ts +0 -12
- package/dist/components/references/builder-deep-link.js +0 -53
- package/dist/components/references/builder-deep-link.js.map +0 -1
- package/dist/components/routes/redirect-chain.d.ts +0 -9
- package/dist/components/routes/redirect-chain.js +0 -16
- package/dist/components/routes/redirect-chain.js.map +0 -1
- package/dist/components/routes/redirect-manager.d.ts +0 -13
- package/dist/components/routes/redirect-manager.js +0 -24
- package/dist/components/routes/redirect-manager.js.map +0 -1
- package/dist/components/routes/route-list.d.ts +0 -35
- package/dist/components/routes/route-list.js +0 -69
- package/dist/components/routes/route-list.js.map +0 -1
- package/dist/components/routes/routes-shell.d.ts +0 -14
- package/dist/components/routes/routes-shell.js +0 -68
- package/dist/components/routes/routes-shell.js.map +0 -1
- package/dist/components/sections.d.ts +0 -37
- package/dist/components/sections.js +0 -62
- package/dist/components/sections.js.map +0 -1
- package/dist/components/users/user-search.d.ts +0 -8
- package/dist/components/users/user-search.js +0 -13
- package/dist/components/users/user-search.js.map +0 -1
- package/dist/components/workspace-gate.d.ts +0 -12
- package/dist/components/workspace-gate.js +0 -28
- package/dist/components/workspace-gate.js.map +0 -1
- package/dist/lib/cms-views/views-store.d.ts +0 -70
- package/dist/lib/cms-views/views-store.js +0 -129
- package/dist/lib/cms-views/views-store.js.map +0 -1
- package/dist/lib/operations/graph-commands.d.ts +0 -136
- package/dist/lib/operations/graph-commands.js +0 -85
- package/dist/lib/operations/graph-commands.js.map +0 -1
- package/dist/lib/operations/graph-persistence.d.ts +0 -27
- package/dist/lib/operations/graph-persistence.js +0 -35
- package/dist/lib/operations/graph-persistence.js.map +0 -1
- package/dist/lib/routing/index.d.ts +0 -10
- package/dist/lib/routing/index.js +0 -11
- package/dist/lib/routing/index.js.map +0 -1
- package/dist/lib/routing/routing-analysis.d.ts +0 -26
- package/dist/lib/routing/routing-analysis.js +0 -37
- package/dist/lib/routing/routing-analysis.js.map +0 -1
- package/dist/lib/routing/routing-store.d.ts +0 -50
- package/dist/lib/routing/routing-store.js +0 -72
- package/dist/lib/routing/routing-store.js.map +0 -1
- package/dist/lib/working-graph.d.ts +0 -82
- package/dist/lib/working-graph.js +0 -113
- package/dist/lib/working-graph.js.map +0 -1
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Param-driven builder selection (EC-172). Reference-panel entries deep-link
|
|
3
|
-
* to `/$projectId/builder?page=<pageId>&node=<nodeId>`; this headless
|
|
4
|
-
* component applies those params to the builder store — switching the active
|
|
5
|
-
* page, selecting the node (falling back to the page root when the node has
|
|
6
|
-
* drifted away), and scrolling the canvas wrapper (`data-ec-node-id`) into
|
|
7
|
-
* view. Pure selection state: nothing here mutates the graph.
|
|
8
|
-
*/
|
|
9
|
-
export declare function BuilderDeepLink({ pageId, nodeId }: {
|
|
10
|
-
pageId?: string;
|
|
11
|
-
nodeId?: string;
|
|
12
|
-
}): null;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { getNode } from '@elytracms/core/project-graph';
|
|
3
|
-
import { useBuilder } from '../builder/builder-store';
|
|
4
|
-
/**
|
|
5
|
-
* Param-driven builder selection (EC-172). Reference-panel entries deep-link
|
|
6
|
-
* to `/$projectId/builder?page=<pageId>&node=<nodeId>`; this headless
|
|
7
|
-
* component applies those params to the builder store — switching the active
|
|
8
|
-
* page, selecting the node (falling back to the page root when the node has
|
|
9
|
-
* drifted away), and scrolling the canvas wrapper (`data-ec-node-id`) into
|
|
10
|
-
* view. Pure selection state: nothing here mutates the graph.
|
|
11
|
-
*/
|
|
12
|
-
export function BuilderDeepLink({ pageId, nodeId }) {
|
|
13
|
-
const builder = useBuilder();
|
|
14
|
-
const { dispatch } = builder;
|
|
15
|
-
const graphRef = useRef(builder.present);
|
|
16
|
-
graphRef.current = builder.present;
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (pageId === undefined && nodeId === undefined)
|
|
19
|
-
return;
|
|
20
|
-
const graph = graphRef.current;
|
|
21
|
-
const page = (pageId !== undefined ? graph.pages.find((p) => p.id === pageId) : undefined) ??
|
|
22
|
-
(nodeId !== undefined
|
|
23
|
-
? graph.pages.find((p) => getNode(p.root, nodeId) !== undefined)
|
|
24
|
-
: undefined);
|
|
25
|
-
if (!page)
|
|
26
|
-
return;
|
|
27
|
-
const selectedId = nodeId !== undefined && getNode(page.root, nodeId) ? nodeId : page.root.id;
|
|
28
|
-
dispatch({ type: 'select', id: selectedId, pageId: page.id });
|
|
29
|
-
// The canvas marks every rendered node with `data-ec-node-id`; scroll the
|
|
30
|
-
// selection into view once the page has rendered (bounded retries — the
|
|
31
|
-
// canvas may need a frame or two after a page switch).
|
|
32
|
-
let cancelled = false;
|
|
33
|
-
let attempts = 0;
|
|
34
|
-
const tryScroll = () => {
|
|
35
|
-
if (cancelled)
|
|
36
|
-
return;
|
|
37
|
-
const element = document.querySelector(`[data-ec-node-id="${CSS.escape(selectedId)}"]`);
|
|
38
|
-
if (element) {
|
|
39
|
-
element.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
40
|
-
}
|
|
41
|
-
else if (attempts < 10) {
|
|
42
|
-
attempts += 1;
|
|
43
|
-
window.setTimeout(tryScroll, 60);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
window.setTimeout(tryScroll, 60);
|
|
47
|
-
return () => {
|
|
48
|
-
cancelled = true;
|
|
49
|
-
};
|
|
50
|
-
}, [pageId, nodeId, dispatch]);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=builder-deep-link.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"builder-deep-link.js","sourceRoot":"","sources":["../../../src/components/references/builder-deep-link.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAErD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAwC;IACtF,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAElC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;YAAE,OAAM;QACxD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;QAC9B,MAAM,IAAI,GACR,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7E,CAAC,MAAM,KAAK,SAAS;gBACnB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,SAAS,CAAC;gBAChE,CAAC,CAAC,SAAS,CAAC,CAAA;QAChB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,UAAU,GAAG,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;QAC7F,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;QAE7D,0EAA0E;QAC1E,wEAAwE;QACxE,uDAAuD;QACvD,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,IAAI,QAAQ,GAAG,CAAC,CAAA;QAChB,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,SAAS;gBAAE,OAAM;YACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,qBAAqB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACvF,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAA;YACjE,CAAC;iBAAM,IAAI,QAAQ,GAAG,EAAE,EAAE,CAAC;gBACzB,QAAQ,IAAI,CAAC,CAAA;gBACb,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YAClC,CAAC;QACH,CAAC,CAAA;QACD,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QAChC,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE9B,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { useEffect, useRef } from 'react'\nimport { getNode } from '@elytracms/core/project-graph'\nimport { useBuilder } from '../builder/builder-store'\n\n/**\n * Param-driven builder selection (EC-172). Reference-panel entries deep-link\n * to `/$projectId/builder?page=<pageId>&node=<nodeId>`; this headless\n * component applies those params to the builder store — switching the active\n * page, selecting the node (falling back to the page root when the node has\n * drifted away), and scrolling the canvas wrapper (`data-ec-node-id`) into\n * view. Pure selection state: nothing here mutates the graph.\n */\nexport function BuilderDeepLink({ pageId, nodeId }: { pageId?: string; nodeId?: string }) {\n const builder = useBuilder()\n const { dispatch } = builder\n const graphRef = useRef(builder.present)\n graphRef.current = builder.present\n\n useEffect(() => {\n if (pageId === undefined && nodeId === undefined) return\n const graph = graphRef.current\n const page =\n (pageId !== undefined ? graph.pages.find((p) => p.id === pageId) : undefined) ??\n (nodeId !== undefined\n ? graph.pages.find((p) => getNode(p.root, nodeId) !== undefined)\n : undefined)\n if (!page) return\n const selectedId = nodeId !== undefined && getNode(page.root, nodeId) ? nodeId : page.root.id\n dispatch({ type: 'select', id: selectedId, pageId: page.id })\n\n // The canvas marks every rendered node with `data-ec-node-id`; scroll the\n // selection into view once the page has rendered (bounded retries — the\n // canvas may need a frame or two after a page switch).\n let cancelled = false\n let attempts = 0\n const tryScroll = () => {\n if (cancelled) return\n const element = document.querySelector(`[data-ec-node-id=\"${CSS.escape(selectedId)}\"]`)\n if (element) {\n element.scrollIntoView({ block: 'center', behavior: 'smooth' })\n } else if (attempts < 10) {\n attempts += 1\n window.setTimeout(tryScroll, 60)\n }\n }\n window.setTimeout(tryScroll, 60)\n return () => {\n cancelled = true\n }\n }, [pageId, nodeId, dispatch])\n\n return null\n}\n"]}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { RedirectResolution } from '@elytracms/core/cms-core';
|
|
2
|
-
/**
|
|
3
|
-
* Visualizes a redirect chain from the engine's {@link RedirectResolution}:
|
|
4
|
-
* every hop in order, with the loop entry point highlighted when the chain
|
|
5
|
-
* closes a loop (EC-170).
|
|
6
|
-
*/
|
|
7
|
-
export declare function RedirectChain({ resolution }: {
|
|
8
|
-
resolution: RedirectResolution;
|
|
9
|
-
}): import("react").JSX.Element;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment } from 'react';
|
|
3
|
-
import { ArrowRight, RotateCw } from 'lucide-react';
|
|
4
|
-
import { Badge, Code, Row } from '@elytracms/studio/ui';
|
|
5
|
-
/**
|
|
6
|
-
* Visualizes a redirect chain from the engine's {@link RedirectResolution}:
|
|
7
|
-
* every hop in order, with the loop entry point highlighted when the chain
|
|
8
|
-
* closes a loop (EC-170).
|
|
9
|
-
*/
|
|
10
|
-
export function RedirectChain({ resolution }) {
|
|
11
|
-
return (_jsxs(Row, { gap: "1", wrap: true, "data-testid": "redirect-chain", "data-loop": resolution.loop, children: [resolution.chain.map((hop, index) => {
|
|
12
|
-
const isLoopEntry = resolution.loop && hop === resolution.target;
|
|
13
|
-
return (_jsxs(Fragment, { children: [index > 0 ? _jsx(ArrowRight, { className: "size-3 shrink-0 text-muted-foreground" }) : null, _jsx(Code, { className: isLoopEntry ? 'border-destructive/50 bg-destructive/10 text-destructive' : undefined, children: hop })] }, `${hop}-${index}`));
|
|
14
|
-
}), resolution.loop ? (_jsxs(Badge, { variant: "error", "data-testid": "loop-badge", children: [_jsx(RotateCw, {}), " loop"] })) : null] }));
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=redirect-chain.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirect-chain.js","sourceRoot":"","sources":["../../../src/components/routes/redirect-chain.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEnD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAEvD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,UAAU,EAAsC;IAC9E,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,uBAAa,gBAAgB,eAAY,UAAU,CAAC,IAAI,aACtE,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;gBACnC,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,IAAI,GAAG,KAAK,UAAU,CAAC,MAAM,CAAA;gBAChE,OAAO,CACL,MAAC,QAAQ,eACN,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAC,UAAU,IAAC,SAAS,EAAC,uCAAuC,GAAG,CAAC,CAAC,CAAC,IAAI,EACpF,KAAC,IAAI,IACH,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,0DAA0D,CAAC,CAAC,CAAC,SAAS,YAE9F,GAAG,GACC,KANM,GAAG,GAAG,IAAI,KAAK,EAAE,CAOrB,CACZ,CAAA;YACH,CAAC,CAAC,EACD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CACjB,MAAC,KAAK,IAAC,OAAO,EAAC,OAAO,iBAAa,YAAY,aAC7C,KAAC,QAAQ,KAAG,aACN,CACT,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import { Fragment } from 'react'\nimport { ArrowRight, RotateCw } from 'lucide-react'\nimport type { RedirectResolution } from '@elytracms/core/cms-core'\nimport { Badge, Code, Row } from '@elytracms/studio/ui'\n\n/**\n * Visualizes a redirect chain from the engine's {@link RedirectResolution}:\n * every hop in order, with the loop entry point highlighted when the chain\n * closes a loop (EC-170).\n */\nexport function RedirectChain({ resolution }: { resolution: RedirectResolution }) {\n return (\n <Row gap=\"1\" wrap data-testid=\"redirect-chain\" data-loop={resolution.loop}>\n {resolution.chain.map((hop, index) => {\n const isLoopEntry = resolution.loop && hop === resolution.target\n return (\n <Fragment key={`${hop}-${index}`}>\n {index > 0 ? <ArrowRight className=\"size-3 shrink-0 text-muted-foreground\" /> : null}\n <Code\n className={isLoopEntry ? 'border-destructive/50 bg-destructive/10 text-destructive' : undefined}\n >\n {hop}\n </Code>\n </Fragment>\n )\n })}\n {resolution.loop ? (\n <Badge variant=\"error\" data-testid=\"loop-badge\">\n <RotateCw /> loop\n </Badge>\n ) : null}\n </Row>\n )\n}\n"]}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { RedirectRecord, RedirectResolution } from '@elytracms/core/cms-core';
|
|
2
|
-
export interface RedirectManagerProps {
|
|
3
|
-
redirects: readonly RedirectRecord[];
|
|
4
|
-
/** The live router's chain resolver (engine API, EC-020). */
|
|
5
|
-
followRedirects: (from: string) => RedirectResolution;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* The redirect list (EC-170), read-only (EC-207 "routes as code"): every
|
|
9
|
-
* 301/302 redirect of the project. Each row shows its resolved chain from the
|
|
10
|
-
* live engine; loops render as explicit errors. Redirects are authored in
|
|
11
|
-
* `elytra.config.ts`; this surface only views them.
|
|
12
|
-
*/
|
|
13
|
-
export declare function RedirectManager({ redirects, followRedirects }: RedirectManagerProps): import("react").JSX.Element;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowRight } from 'lucide-react';
|
|
3
|
-
import { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
4
|
-
import { statusCodeOf } from '@elytracms/studio/lib/routing';
|
|
5
|
-
import { RedirectChain } from './redirect-chain';
|
|
6
|
-
/**
|
|
7
|
-
* The redirect list (EC-170), read-only (EC-207 "routes as code"): every
|
|
8
|
-
* 301/302 redirect of the project. Each row shows its resolved chain from the
|
|
9
|
-
* live engine; loops render as explicit errors. Redirects are authored in
|
|
10
|
-
* `elytra.config.ts`; this surface only views them.
|
|
11
|
-
*/
|
|
12
|
-
export function RedirectManager({ redirects, followRedirects }) {
|
|
13
|
-
const sorted = [...redirects].sort((a, b) => a.from.localeCompare(b.from));
|
|
14
|
-
return (_jsxs(Stack, { gap: "none", className: "min-h-0", children: [_jsxs(Row, { gap: "2", className: "h-12 shrink-0 border-b border-border px-4", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Redirects" }), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: redirects.length })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [sorted.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", children: "No redirects defined. Add redirects in elytra.config.ts to forward old URLs to new ones." })) : null, _jsx("ul", { className: "flex flex-col gap-2", "data-testid": "redirect-list", children: sorted.map((redirect) => (_jsx(RedirectRow, { redirect: redirect, resolution: followRedirects(redirect.from) }, redirect.id))) })] })] }));
|
|
15
|
-
}
|
|
16
|
-
function RedirectRow({ redirect, resolution, }) {
|
|
17
|
-
// More hops than `from → to` means this redirect chains onward (or loops).
|
|
18
|
-
const chains = resolution.loop || resolution.chain.length > 2;
|
|
19
|
-
return (_jsxs("li", { "data-testid": `redirect-row-${redirect.id}`, className: 'rounded-md border p-3 ' +
|
|
20
|
-
(resolution.loop ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40'), children: [_jsxs(Row, { gap: "2", wrap: true, children: [_jsx(Code, { children: redirect.from }), _jsx(ArrowRight, { className: "size-3 shrink-0 text-muted-foreground" }), _jsx(Code, { children: redirect.to }), _jsx(Badge, { variant: redirect.permanent ? 'primary' : 'info', "data-testid": `redirect-status-${redirect.id}`, children: statusCodeOf(redirect) }), resolution.loop ? _jsx(Badge, { variant: "error", children: "loop" }) : null, _jsx(Spacer, {})] }), chains ? (_jsxs(Stack, { gap: "1", className: "mt-2", children: [_jsx(Text, { size: "xs", tone: "muted", children: resolution.loop
|
|
21
|
-
? 'This redirect is part of a loop:'
|
|
22
|
-
: `Chains to ${resolution.target}:` }), _jsx(RedirectChain, { resolution: resolution })] })) : null] }));
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=redirect-manager.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirect-manager.js","sourceRoot":"","sources":["../../../src/components/routes/redirect-manager.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAQhD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,SAAS,EAAE,eAAe,EAAwB;IAClF,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAE1E,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,MAAM,EAAC,SAAS,EAAC,SAAS,aACnC,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,2CAA2C,aAChE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,0BAE1B,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,kBAC/B,SAAS,CAAC,MAAM,GACZ,IACH,EAEN,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC3B,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACrB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,yGAErB,CACR,CAAC,CAAC,CAAC,IAAI,EAER,aAAI,SAAS,EAAC,qBAAqB,iBAAa,eAAe,YAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CACxB,KAAC,WAAW,IAEV,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,IAFrC,QAAQ,CAAC,EAAE,CAGhB,CACH,CAAC,GACC,IACC,IACF,CACT,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EACnB,QAAQ,EACR,UAAU,GAKX;IACC,2EAA2E;IAC3E,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7D,OAAO,CACL,6BACe,gBAAgB,QAAQ,CAAC,EAAE,EAAE,EAC1C,SAAS,EACP,wBAAwB;YACxB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC,0BAA0B,CAAC,aAG3F,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,mBACf,KAAC,IAAI,cAAE,QAAQ,CAAC,IAAI,GAAQ,EAC5B,KAAC,UAAU,IAAC,SAAS,EAAC,uCAAuC,GAAG,EAChE,KAAC,IAAI,cAAE,QAAQ,CAAC,EAAE,GAAQ,EAC1B,KAAC,KAAK,IACJ,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,iBACnC,mBAAmB,QAAQ,CAAC,EAAE,EAAE,YAE5C,YAAY,CAAC,QAAQ,CAAC,GACjB,EACP,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,OAAO,EAAC,OAAO,qBAAa,CAAC,CAAC,CAAC,IAAI,EAC7D,KAAC,MAAM,KAAG,IACN,EACL,MAAM,CAAC,CAAC,CAAC,CACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,MAAM,aAC7B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,UAAU,CAAC,IAAI;4BACd,CAAC,CAAC,kCAAkC;4BACpC,CAAC,CAAC,aAAa,UAAU,CAAC,MAAM,GAAG,GAChC,EACP,KAAC,aAAa,IAAC,UAAU,EAAE,UAAU,GAAI,IACnC,CACT,CAAC,CAAC,CAAC,IAAI,IACL,CACN,CAAA;AACH,CAAC","sourcesContent":["import { ArrowRight } from 'lucide-react'\nimport type { RedirectRecord, RedirectResolution } from '@elytracms/core/cms-core'\nimport { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { statusCodeOf } from '@elytracms/studio/lib/routing'\nimport { RedirectChain } from './redirect-chain'\n\nexport interface RedirectManagerProps {\n redirects: readonly RedirectRecord[]\n /** The live router's chain resolver (engine API, EC-020). */\n followRedirects: (from: string) => RedirectResolution\n}\n\n/**\n * The redirect list (EC-170), read-only (EC-207 \"routes as code\"): every\n * 301/302 redirect of the project. Each row shows its resolved chain from the\n * live engine; loops render as explicit errors. Redirects are authored in\n * `elytra.config.ts`; this surface only views them.\n */\nexport function RedirectManager({ redirects, followRedirects }: RedirectManagerProps) {\n const sorted = [...redirects].sort((a, b) => a.from.localeCompare(b.from))\n\n return (\n <Stack gap=\"none\" className=\"min-h-0\">\n <Row gap=\"2\" className=\"h-12 shrink-0 border-b border-border px-4\">\n <Text size=\"sm\" weight=\"semibold\">\n Redirects\n </Text>\n <Text size=\"xs\" tone=\"subtle\" mono>\n {redirects.length}\n </Text>\n </Row>\n\n <Stack gap=\"3\" className=\"p-4\">\n {sorted.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n No redirects defined. Add redirects in elytra.config.ts to forward old URLs to new ones.\n </Text>\n ) : null}\n\n <ul className=\"flex flex-col gap-2\" data-testid=\"redirect-list\">\n {sorted.map((redirect) => (\n <RedirectRow\n key={redirect.id}\n redirect={redirect}\n resolution={followRedirects(redirect.from)}\n />\n ))}\n </ul>\n </Stack>\n </Stack>\n )\n}\n\nfunction RedirectRow({\n redirect,\n resolution,\n}: {\n redirect: RedirectRecord\n /** Engine-resolved chain starting at this redirect's source. */\n resolution: RedirectResolution\n}) {\n // More hops than `from → to` means this redirect chains onward (or loops).\n const chains = resolution.loop || resolution.chain.length > 2\n return (\n <li\n data-testid={`redirect-row-${redirect.id}`}\n className={\n 'rounded-md border p-3 ' +\n (resolution.loop ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40')\n }\n >\n <Row gap=\"2\" wrap>\n <Code>{redirect.from}</Code>\n <ArrowRight className=\"size-3 shrink-0 text-muted-foreground\" />\n <Code>{redirect.to}</Code>\n <Badge\n variant={redirect.permanent ? 'primary' : 'info'}\n data-testid={`redirect-status-${redirect.id}`}\n >\n {statusCodeOf(redirect)}\n </Badge>\n {resolution.loop ? <Badge variant=\"error\">loop</Badge> : null}\n <Spacer />\n </Row>\n {chains ? (\n <Stack gap=\"1\" className=\"mt-2\">\n <Text size=\"xs\" tone=\"muted\">\n {resolution.loop\n ? 'This redirect is part of a loop:'\n : `Chains to ${resolution.target}:`}\n </Text>\n <RedirectChain resolution={resolution} />\n </Stack>\n ) : null}\n </li>\n )\n}\n"]}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { RouteRecord } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { RouteConflict } from '@elytracms/studio/lib/routing';
|
|
3
|
-
/** The slice of a graph page the route list needs for linking/labelling. */
|
|
4
|
-
export interface PageSummary {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
}
|
|
8
|
-
export interface RouteListProps {
|
|
9
|
-
projectId: string;
|
|
10
|
-
routes: readonly RouteRecord[];
|
|
11
|
-
/** Engine-sourced `route-conflict` issues, indexed by route id (both sides). */
|
|
12
|
-
conflicts: ReadonlyMap<string, RouteConflict[]>;
|
|
13
|
-
pages: readonly PageSummary[];
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Every route of the project (EC-170), read-only (EC-207 "routes as code"):
|
|
17
|
-
* static paths, dynamic patterns with `:param` segments rendered distinctly, and
|
|
18
|
-
* per-locale variants — each row linking to its page/template/document. Routes
|
|
19
|
-
* are authored in `elytra.config.ts`; this list only views them. Conflicts from
|
|
20
|
-
* the engine's structured issues render inline with deep links to the
|
|
21
|
-
* counterpart row.
|
|
22
|
-
*/
|
|
23
|
-
export declare function RouteList({ projectId, routes, conflicts, pages }: RouteListProps): import("react").JSX.Element;
|
|
24
|
-
/** A route pattern with `:param` segments rendered distinctly (EC-170). */
|
|
25
|
-
export declare function PatternView({ pattern }: {
|
|
26
|
-
pattern: string;
|
|
27
|
-
}): import("react").JSX.Element;
|
|
28
|
-
/**
|
|
29
|
-
* Inline conflict block: the engine's `route-conflict` message plus a deep
|
|
30
|
-
* link to the counterpart row (same-page anchor; rows highlight on `:target`).
|
|
31
|
-
*/
|
|
32
|
-
export declare function ConflictNotice({ routeId, conflicts, }: {
|
|
33
|
-
routeId: string;
|
|
34
|
-
conflicts: readonly RouteConflict[];
|
|
35
|
-
}): import("react").JSX.Element;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
|
|
3
|
-
import { CircleAlert, Globe } from 'lucide-react';
|
|
4
|
-
import { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
5
|
-
import { patternSegments } from '@elytracms/studio/lib/routing';
|
|
6
|
-
/**
|
|
7
|
-
* Every route of the project (EC-170), read-only (EC-207 "routes as code"):
|
|
8
|
-
* static paths, dynamic patterns with `:param` segments rendered distinctly, and
|
|
9
|
-
* per-locale variants — each row linking to its page/template/document. Routes
|
|
10
|
-
* are authored in `elytra.config.ts`; this list only views them. Conflicts from
|
|
11
|
-
* the engine's structured issues render inline with deep links to the
|
|
12
|
-
* counterpart row.
|
|
13
|
-
*/
|
|
14
|
-
export function RouteList({ projectId, routes, conflicts, pages }) {
|
|
15
|
-
const sorted = [...routes].sort((a, b) => a.pattern === b.pattern
|
|
16
|
-
? (a.locale ?? '').localeCompare(b.locale ?? '')
|
|
17
|
-
: a.pattern.localeCompare(b.pattern));
|
|
18
|
-
return (_jsxs(Stack, { gap: "none", className: "min-h-0", children: [_jsxs(Row, { gap: "2", className: "h-12 shrink-0 border-b border-border px-4", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Routes" }), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: routes.length })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [sorted.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", children: "No routes defined. Add routes in elytra.config.ts to bind URL patterns to pages or templates." })) : null, _jsx("ul", { className: "flex flex-col gap-2", "data-testid": "route-list", children: sorted.map((route) => (_jsx(RouteRow, { projectId: projectId, route: route, conflicts: conflicts.get(route.id) ?? [], pages: pages }, route.id))) })] })] }));
|
|
19
|
-
}
|
|
20
|
-
function RouteRow({ projectId, route, conflicts, pages, }) {
|
|
21
|
-
const hasConflict = conflicts.length > 0;
|
|
22
|
-
return (_jsxs("li", { id: `route-${route.id}`, "data-testid": `route-row-${route.id}`, className: 'rounded-md border p-3 transition-colors target:ring-2 target:ring-ring/60 ' +
|
|
23
|
-
(hasConflict ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40'), children: [_jsxs(Row, { gap: "2", wrap: true, children: [_jsx(PatternView, { pattern: route.pattern }), _jsx(LocaleBadge, { locale: route.locale }), _jsx(Spacer, {}), hasConflict ? (_jsx(Badge, { variant: "error", "data-testid": `route-status-${route.id}`, children: "conflict" })) : (_jsx(Badge, { variant: "success", "data-testid": `route-status-${route.id}`, children: "ok" }))] }), _jsx(Row, { gap: "2", wrap: true, className: "mt-2", children: _jsx(RouteTarget, { projectId: projectId, route: route, pages: pages }) }), hasConflict ? _jsx(ConflictNotice, { routeId: route.id, conflicts: conflicts }) : null] }));
|
|
24
|
-
}
|
|
25
|
-
/** A route pattern with `:param` segments rendered distinctly (EC-170). */
|
|
26
|
-
export function PatternView({ pattern }) {
|
|
27
|
-
const segments = patternSegments(pattern);
|
|
28
|
-
return (_jsxs("span", { className: "inline-flex flex-wrap items-center font-mono text-sm", "data-testid": "pattern", children: [_jsx("span", { className: "text-muted-foreground", children: "/" }), segments.map((segment, index) => (_jsxs("span", { className: "inline-flex items-center", children: [segment.param ? (_jsx("span", { "data-param": segment.text, className: "rounded-sm border border-primary/30 bg-primary/10 px-1 text-primary", children: segment.text })) : (_jsx("span", { className: "text-foreground", children: segment.text })), index < segments.length - 1 ? _jsx("span", { className: "text-muted-foreground", children: "/" }) : null] }, `${segment.text}-${index}`)))] }));
|
|
29
|
-
}
|
|
30
|
-
function LocaleBadge({ locale }) {
|
|
31
|
-
if (!locale) {
|
|
32
|
-
return (_jsxs(Badge, { variant: "outline", children: [_jsx(Globe, {}), " all locales"] }));
|
|
33
|
-
}
|
|
34
|
-
return (_jsxs(Badge, { variant: "info", children: [_jsx(Globe, {}), " ", locale] }));
|
|
35
|
-
}
|
|
36
|
-
/** The page/template/document a route renders, deep-linked where a surface exists. */
|
|
37
|
-
function RouteTarget({ projectId, route, pages, }) {
|
|
38
|
-
// EC-187: every target is a single `document` ref. A page target is the
|
|
39
|
-
// `page` collection; a template target is a `:param` placeholder id;
|
|
40
|
-
// anything else is a concrete linked document.
|
|
41
|
-
if (!route.document) {
|
|
42
|
-
return (_jsx(Badge, { variant: "warning", "data-testid": `route-no-target-${route.id}`, children: "no target" }));
|
|
43
|
-
}
|
|
44
|
-
if (route.document.collection === 'page') {
|
|
45
|
-
const pageId = route.document.id;
|
|
46
|
-
const page = pages.find((candidate) => candidate.id === pageId);
|
|
47
|
-
return (_jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: "page" }), _jsx(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.builder() }, className: "text-xs font-medium text-primary underline-offset-2 hover:underline", children: page?.name ?? pageId }), !page ? _jsx(Badge, { variant: "warning", children: "not in graph" }) : null] }));
|
|
48
|
-
}
|
|
49
|
-
if (route.document.id.startsWith(':')) {
|
|
50
|
-
return (_jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: "template" }), _jsx(Code, { children: route.document.collection })] }));
|
|
51
|
-
}
|
|
52
|
-
return (_jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: "document" }), _jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: {
|
|
53
|
-
projectId,
|
|
54
|
-
_splat: studioPath.cmsDocument(route.document.collection, route.document.id),
|
|
55
|
-
}, className: "text-xs font-medium text-primary underline-offset-2 hover:underline", children: [route.document.collection, "/", route.document.id] })] }));
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Inline conflict block: the engine's `route-conflict` message plus a deep
|
|
59
|
-
* link to the counterpart row (same-page anchor; rows highlight on `:target`).
|
|
60
|
-
*/
|
|
61
|
-
export function ConflictNotice({ routeId, conflicts, }) {
|
|
62
|
-
return (_jsx(Stack, { gap: "1", className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 p-2", children: conflicts.map((conflict, index) => (_jsxs(Row, { gap: "2", align: "start", children: [_jsx(CircleAlert, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }), _jsxs(Stack, { gap: "1", className: "min-w-0", children: [_jsx(Text, { size: "xs", className: "text-destructive", "data-testid": `conflict-${routeId}`,
|
|
63
|
-
// The engine message names both routes by their raw ids
|
|
64
|
-
// (`r-blog-slug-en`) — kept inspectable on hover, never shown as
|
|
65
|
-
// visible copy. The editor sees the patterns on the rows + the link
|
|
66
|
-
// below (EC-192).
|
|
67
|
-
title: conflict.issue.message, children: "Matches the same URLs as another route." }), conflict.otherRouteId ? (_jsx("a", { href: `#route-${conflict.otherRouteId}`, className: "text-xs font-medium text-destructive underline underline-offset-2", "data-testid": `conflict-link-${routeId}`, children: "View conflicting route" })) : null] })] }, `${conflict.otherRouteId}-${index}`))) }));
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=route-list.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route-list.js","sourceRoot":"","sources":["../../../src/components/routes/route-list.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { CircleAlert, Globe } from 'lucide-react'\nimport type { RouteRecord } from '@elytracms/core/cms-core'\nimport { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { patternSegments } from '@elytracms/studio/lib/routing'\nimport type { RouteConflict } from '@elytracms/studio/lib/routing'\n\n/** The slice of a graph page the route list needs for linking/labelling. */\nexport interface PageSummary {\n id: string\n name: string\n}\n\nexport interface RouteListProps {\n projectId: string\n routes: readonly RouteRecord[]\n /** Engine-sourced `route-conflict` issues, indexed by route id (both sides). */\n conflicts: ReadonlyMap<string, RouteConflict[]>\n pages: readonly PageSummary[]\n}\n\n/**\n * Every route of the project (EC-170), read-only (EC-207 \"routes as code\"):\n * static paths, dynamic patterns with `:param` segments rendered distinctly, and\n * per-locale variants — each row linking to its page/template/document. Routes\n * are authored in `elytra.config.ts`; this list only views them. Conflicts from\n * the engine's structured issues render inline with deep links to the\n * counterpart row.\n */\nexport function RouteList({ projectId, routes, conflicts, pages }: RouteListProps) {\n const sorted = [...routes].sort((a, b) =>\n a.pattern === b.pattern\n ? (a.locale ?? '').localeCompare(b.locale ?? '')\n : a.pattern.localeCompare(b.pattern),\n )\n\n return (\n <Stack gap=\"none\" className=\"min-h-0\">\n <Row gap=\"2\" className=\"h-12 shrink-0 border-b border-border px-4\">\n <Text size=\"sm\" weight=\"semibold\">\n Routes\n </Text>\n <Text size=\"xs\" tone=\"subtle\" mono>\n {routes.length}\n </Text>\n </Row>\n\n <Stack gap=\"3\" className=\"p-4\">\n {sorted.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n No routes defined. Add routes in elytra.config.ts to bind URL patterns to pages or\n templates.\n </Text>\n ) : null}\n\n <ul className=\"flex flex-col gap-2\" data-testid=\"route-list\">\n {sorted.map((route) => (\n <RouteRow\n key={route.id}\n projectId={projectId}\n route={route}\n conflicts={conflicts.get(route.id) ?? []}\n pages={pages}\n />\n ))}\n </ul>\n </Stack>\n </Stack>\n )\n}\n\nfunction RouteRow({\n projectId,\n route,\n conflicts,\n pages,\n}: {\n projectId: string\n route: RouteRecord\n conflicts: readonly RouteConflict[]\n pages: readonly PageSummary[]\n}) {\n const hasConflict = conflicts.length > 0\n return (\n <li\n id={`route-${route.id}`}\n data-testid={`route-row-${route.id}`}\n className={\n 'rounded-md border p-3 transition-colors target:ring-2 target:ring-ring/60 ' +\n (hasConflict ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40')\n }\n >\n <Row gap=\"2\" wrap>\n <PatternView pattern={route.pattern} />\n <LocaleBadge locale={route.locale} />\n <Spacer />\n {hasConflict ? (\n <Badge variant=\"error\" data-testid={`route-status-${route.id}`}>\n conflict\n </Badge>\n ) : (\n <Badge variant=\"success\" data-testid={`route-status-${route.id}`}>\n ok\n </Badge>\n )}\n </Row>\n <Row gap=\"2\" wrap className=\"mt-2\">\n <RouteTarget projectId={projectId} route={route} pages={pages} />\n </Row>\n {hasConflict ? <ConflictNotice routeId={route.id} conflicts={conflicts} /> : null}\n </li>\n )\n}\n\n/** A route pattern with `:param` segments rendered distinctly (EC-170). */\nexport function PatternView({ pattern }: { pattern: string }) {\n const segments = patternSegments(pattern)\n return (\n <span className=\"inline-flex flex-wrap items-center font-mono text-sm\" data-testid=\"pattern\">\n <span className=\"text-muted-foreground\">/</span>\n {segments.map((segment, index) => (\n <span key={`${segment.text}-${index}`} className=\"inline-flex items-center\">\n {segment.param ? (\n <span\n data-param={segment.text}\n className=\"rounded-sm border border-primary/30 bg-primary/10 px-1 text-primary\"\n >\n {segment.text}\n </span>\n ) : (\n <span className=\"text-foreground\">{segment.text}</span>\n )}\n {index < segments.length - 1 ? <span className=\"text-muted-foreground\">/</span> : null}\n </span>\n ))}\n </span>\n )\n}\n\nfunction LocaleBadge({ locale }: { locale: string | undefined }) {\n if (!locale) {\n return (\n <Badge variant=\"outline\">\n <Globe /> all locales\n </Badge>\n )\n }\n return (\n <Badge variant=\"info\">\n <Globe /> {locale}\n </Badge>\n )\n}\n\n/** The page/template/document a route renders, deep-linked where a surface exists. */\nfunction RouteTarget({\n projectId,\n route,\n pages,\n}: {\n projectId: string\n route: RouteRecord\n pages: readonly PageSummary[]\n}) {\n // EC-187: every target is a single `document` ref. A page target is the\n // `page` collection; a template target is a `:param` placeholder id;\n // anything else is a concrete linked document.\n if (!route.document) {\n return (\n <Badge variant=\"warning\" data-testid={`route-no-target-${route.id}`}>\n no target\n </Badge>\n )\n }\n if (route.document.collection === 'page') {\n const pageId = route.document.id\n const page = pages.find((candidate) => candidate.id === pageId)\n return (\n <Row gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n page\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.builder() }}\n className=\"text-xs font-medium text-primary underline-offset-2 hover:underline\"\n >\n {page?.name ?? pageId}\n </Link>\n {!page ? <Badge variant=\"warning\">not in graph</Badge> : null}\n </Row>\n )\n }\n if (route.document.id.startsWith(':')) {\n return (\n <Row gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n template\n </Text>\n <Code>{route.document.collection}</Code>\n </Row>\n )\n }\n return (\n <Row gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n document\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{\n projectId,\n _splat: studioPath.cmsDocument(route.document.collection, route.document.id),\n }}\n className=\"text-xs font-medium text-primary underline-offset-2 hover:underline\"\n >\n {route.document.collection}/{route.document.id}\n </Link>\n </Row>\n )\n}\n\n/**\n * Inline conflict block: the engine's `route-conflict` message plus a deep\n * link to the counterpart row (same-page anchor; rows highlight on `:target`).\n */\nexport function ConflictNotice({\n routeId,\n conflicts,\n}: {\n routeId: string\n conflicts: readonly RouteConflict[]\n}) {\n return (\n <Stack gap=\"1\" className=\"mt-2 rounded-md border border-destructive/30 bg-destructive/10 p-2\">\n {conflicts.map((conflict, index) => (\n <Row gap=\"2\" align=\"start\" key={`${conflict.otherRouteId}-${index}`}>\n <CircleAlert className=\"mt-0.5 size-3.5 shrink-0 text-destructive\" />\n <Stack gap=\"1\" className=\"min-w-0\">\n <Text\n size=\"xs\"\n className=\"text-destructive\"\n data-testid={`conflict-${routeId}`}\n // The engine message names both routes by their raw ids\n // (`r-blog-slug-en`) — kept inspectable on hover, never shown as\n // visible copy. The editor sees the patterns on the rows + the link\n // below (EC-192).\n title={conflict.issue.message}\n >\n Matches the same URLs as another route.\n </Text>\n {conflict.otherRouteId ? (\n <a\n href={`#route-${conflict.otherRouteId}`}\n className=\"text-xs font-medium text-destructive underline underline-offset-2\"\n data-testid={`conflict-link-${routeId}`}\n >\n View conflicting route\n </a>\n ) : null}\n </Stack>\n </Row>\n ))}\n </Stack>\n )\n}\n"]}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Project } from '@elytracms/core/studio-core';
|
|
2
|
-
/**
|
|
3
|
-
* The Routes surface (EC-170, made read-only by EC-207 "routes as code"):
|
|
4
|
-
* every route and redirect of the project, with the engine's structured issues
|
|
5
|
-
* (route conflicts, redirect loops) rendered inline. Routes and redirects are
|
|
6
|
-
* authored in `elytra.config.ts` and read from the resolved config project
|
|
7
|
-
* (`resolveDeploymentRoutes`/`resolveDeploymentRedirects`) — NEVER from the
|
|
8
|
-
* backend. This surface only views them; conflict/loop detection comes from the
|
|
9
|
-
* canonical `@elytracms/core/cms-core` router, never from parallel UI-side logic. The
|
|
10
|
-
* page list (CMS `page` collection) is read only to label route targets.
|
|
11
|
-
*/
|
|
12
|
-
export declare function RoutesShell({ project }: {
|
|
13
|
-
project: Project;
|
|
14
|
-
}): import("react").JSX.Element;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import { createRouter } from '@elytracms/core/cms-core';
|
|
4
|
-
import { Badge, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
5
|
-
import { useOperationQuery } from '@elytracms/studio/lib/query';
|
|
6
|
-
import { conflictsByRoute } from '@elytracms/studio/lib/routing';
|
|
7
|
-
import { resolveDeploymentRedirects, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
8
|
-
import { OperationErrorBanner } from '../operation-error';
|
|
9
|
-
import { RouteList } from './route-list';
|
|
10
|
-
import { RedirectManager } from './redirect-manager';
|
|
11
|
-
/** The body-is-a-canvas page collection (EC-187): routable page documents. */
|
|
12
|
-
const PAGE_COLLECTION = 'page';
|
|
13
|
-
/** Page-label freshness: re-read the labels when any document command lands. */
|
|
14
|
-
const PAGE_LABEL_RELOAD_OPS = ['documents'];
|
|
15
|
-
const EMPTY_PAGES = [];
|
|
16
|
-
/**
|
|
17
|
-
* Routable pages come from the `page` CMS collection (EC-192) — the documents an
|
|
18
|
-
* editor actually creates — not the builder's single synthetic working page.
|
|
19
|
-
* `documents.list` returns persisted records wrapping each document; map each to
|
|
20
|
-
* a `{ id, name }` summary for the route target labels.
|
|
21
|
-
*/
|
|
22
|
-
function pageSummaries(records) {
|
|
23
|
-
return records.map(({ document }) => {
|
|
24
|
-
const name = document.values?.['name'];
|
|
25
|
-
return { id: document.id, name: typeof name === 'string' && name.length > 0 ? name : document.id };
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
async function loadPageLabels(operations, projectId) {
|
|
29
|
-
const result = await operations.documents.list({ projectId, collection: PAGE_COLLECTION });
|
|
30
|
-
if (result.ok)
|
|
31
|
-
return { pages: pageSummaries(result.value), error: null };
|
|
32
|
-
return { pages: [], error: result.error };
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* The Routes surface (EC-170, made read-only by EC-207 "routes as code"):
|
|
36
|
-
* every route and redirect of the project, with the engine's structured issues
|
|
37
|
-
* (route conflicts, redirect loops) rendered inline. Routes and redirects are
|
|
38
|
-
* authored in `elytra.config.ts` and read from the resolved config project
|
|
39
|
-
* (`resolveDeploymentRoutes`/`resolveDeploymentRedirects`) — NEVER from the
|
|
40
|
-
* backend. This surface only views them; conflict/loop detection comes from the
|
|
41
|
-
* canonical `@elytracms/core/cms-core` router, never from parallel UI-side logic. The
|
|
42
|
-
* page list (CMS `page` collection) is read only to label route targets.
|
|
43
|
-
*/
|
|
44
|
-
export function RoutesShell({ project }) {
|
|
45
|
-
const projectId = project.id;
|
|
46
|
-
// Routes & redirects are config-sourced (EC-207): resolved synchronously from
|
|
47
|
-
// the active `elytra.config.ts` project, never fetched from the backend.
|
|
48
|
-
const routes = useMemo(() => resolveDeploymentRoutes(), []);
|
|
49
|
-
const redirects = useMemo(() => resolveDeploymentRedirects(), []);
|
|
50
|
-
// Page documents feed the route target labels (read-only display), read through
|
|
51
|
-
// the one operations-backed query cache (EC-308/AD-17) and kept fresh as
|
|
52
|
-
// documents change. A failure is non-fatal — see `loadPageLabels`.
|
|
53
|
-
const pagesQuery = useOperationQuery({
|
|
54
|
-
queryKey: ['routePageLabels', projectId],
|
|
55
|
-
loader: (operations) => loadPageLabels(operations, projectId),
|
|
56
|
-
invalidateOn: PAGE_LABEL_RELOAD_OPS,
|
|
57
|
-
});
|
|
58
|
-
const pages = pagesQuery.data?.pages ?? EMPTY_PAGES;
|
|
59
|
-
const loadError = pagesQuery.data?.error ?? null;
|
|
60
|
-
// The engine: one router over the config-resolved records. Its structured
|
|
61
|
-
// issues are the single source of conflict/loop truth for this surface.
|
|
62
|
-
const router = useMemo(() => createRouter(routes, redirects, {
|
|
63
|
-
defaultLocale: project.settings.defaultLocale,
|
|
64
|
-
}), [routes, redirects, project.settings.defaultLocale]);
|
|
65
|
-
const conflicts = useMemo(() => conflictsByRoute(router.issues), [router]);
|
|
66
|
-
return (_jsxs(Stack, { gap: "none", className: "min-h-full", "data-testid": "routes-shell", children: [_jsxs(Row, { gap: "3", className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Stack, { gap: "1", children: [_jsx(Heading, { level: 2, size: "h3", children: "Routes & redirects" }), _jsxs(Text, { size: "xs", tone: "muted", children: ["Routes & redirects are defined in ", _jsx("code", { children: "elytra.config.ts" }), ". Conflicts and loops are detected by the canonical route engine \u2014 never silently."] })] }), _jsx(Spacer, {}), loadError ? (_jsx(OperationErrorBanner, { error: loadError, onDismiss: () => void pagesQuery.refetch() })) : null, router.issues.length > 0 ? (_jsxs(Badge, { variant: "error", "data-testid": "routing-issue-count", children: [router.issues.length, " issue", router.issues.length === 1 ? '' : 's'] })) : (_jsx(Badge, { variant: "success", children: "no issues" }))] }), _jsxs("div", { className: "grid min-h-0 flex-1 grid-cols-1 xl:grid-cols-[minmax(0,5fr)_minmax(0,4fr)]", children: [_jsx("section", { "aria-label": "Routes", className: "min-w-0 border-b border-border xl:border-b-0 xl:border-r", children: _jsx(RouteList, { projectId: projectId, routes: routes, conflicts: conflicts, pages: pages }) }), _jsx("section", { "aria-label": "Redirects", className: "min-w-0", children: _jsx(RedirectManager, { redirects: redirects, followRedirects: router.followRedirects }) })] })] }));
|
|
67
|
-
}
|
|
68
|
-
//# sourceMappingURL=routes-shell.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"routes-shell.js","sourceRoot":"","sources":["../../../src/components/routes/routes-shell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { createRouter } from '@elytracms/core/cms-core'\nimport type { CmsDocument } from '@elytracms/core/cms-core'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport type { OperationError, OperationsClient } from '@elytracms/studio/lib/operations'\nimport { useOperationQuery } from '@elytracms/studio/lib/query'\nimport { conflictsByRoute } from '@elytracms/studio/lib/routing'\nimport { resolveDeploymentRedirects, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport { OperationErrorBanner } from '../operation-error'\nimport { RouteList } from './route-list'\nimport type { PageSummary } from './route-list'\nimport { RedirectManager } from './redirect-manager'\n\n/** The body-is-a-canvas page collection (EC-187): routable page documents. */\nconst PAGE_COLLECTION = 'page'\n\n/** Page-label freshness: re-read the labels when any document command lands. */\nconst PAGE_LABEL_RELOAD_OPS = ['documents'] as const\nconst EMPTY_PAGES: readonly PageSummary[] = []\n\n/**\n * Routable pages come from the `page` CMS collection (EC-192) — the documents an\n * editor actually creates — not the builder's single synthetic working page.\n * `documents.list` returns persisted records wrapping each document; map each to\n * a `{ id, name }` summary for the route target labels.\n */\nfunction pageSummaries(records: ReadonlyArray<{ document: CmsDocument }>): PageSummary[] {\n return records.map(({ document }) => {\n const name = document.values?.['name']\n return { id: document.id, name: typeof name === 'string' && name.length > 0 ? name : document.id }\n })\n}\n\n/** The page-label load result. A read failure is non-fatal (EC-207): the routes\n * still render; the labels fall back to ids and the structured error surfaces in\n * a dismissible banner. So the failure rides IN the data, not as a thrown query\n * error that would collapse the surface. */\ninterface PageLabelData {\n pages: PageSummary[]\n error: OperationError | null\n}\n\nasync function loadPageLabels(\n operations: OperationsClient,\n projectId: string,\n): Promise<PageLabelData> {\n const result = await operations.documents.list({ projectId, collection: PAGE_COLLECTION })\n if (result.ok) return { pages: pageSummaries(result.value), error: null }\n return { pages: [], error: result.error }\n}\n\n/**\n * The Routes surface (EC-170, made read-only by EC-207 \"routes as code\"):\n * every route and redirect of the project, with the engine's structured issues\n * (route conflicts, redirect loops) rendered inline. Routes and redirects are\n * authored in `elytra.config.ts` and read from the resolved config project\n * (`resolveDeploymentRoutes`/`resolveDeploymentRedirects`) — NEVER from the\n * backend. This surface only views them; conflict/loop detection comes from the\n * canonical `@elytracms/core/cms-core` router, never from parallel UI-side logic. The\n * page list (CMS `page` collection) is read only to label route targets.\n */\nexport function RoutesShell({ project }: { project: Project }) {\n const projectId = project.id\n\n // Routes & redirects are config-sourced (EC-207): resolved synchronously from\n // the active `elytra.config.ts` project, never fetched from the backend.\n const routes = useMemo(() => resolveDeploymentRoutes(), [])\n const redirects = useMemo(() => resolveDeploymentRedirects(), [])\n\n // Page documents feed the route target labels (read-only display), read through\n // the one operations-backed query cache (EC-308/AD-17) and kept fresh as\n // documents change. A failure is non-fatal — see `loadPageLabels`.\n const pagesQuery = useOperationQuery<PageLabelData>({\n queryKey: ['routePageLabels', projectId],\n loader: (operations) => loadPageLabels(operations, projectId),\n invalidateOn: PAGE_LABEL_RELOAD_OPS,\n })\n const pages = pagesQuery.data?.pages ?? EMPTY_PAGES\n const loadError = pagesQuery.data?.error ?? null\n\n // The engine: one router over the config-resolved records. Its structured\n // issues are the single source of conflict/loop truth for this surface.\n const router = useMemo(\n () =>\n createRouter(routes, redirects, {\n defaultLocale: project.settings.defaultLocale,\n }),\n [routes, redirects, project.settings.defaultLocale],\n )\n const conflicts = useMemo(() => conflictsByRoute(router.issues), [router])\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\" data-testid=\"routes-shell\">\n <Row gap=\"3\" className=\"shrink-0 border-b border-border px-4 py-3 sm:px-6\">\n <Stack gap=\"1\">\n <Heading level={2} size=\"h3\">\n Routes & redirects\n </Heading>\n <Text size=\"xs\" tone=\"muted\">\n Routes & redirects are defined in <code>elytra.config.ts</code>. Conflicts and loops\n are detected by the canonical route engine — never silently.\n </Text>\n </Stack>\n <Spacer />\n {loadError ? (\n <OperationErrorBanner error={loadError} onDismiss={() => void pagesQuery.refetch()} />\n ) : null}\n {router.issues.length > 0 ? (\n <Badge variant=\"error\" data-testid=\"routing-issue-count\">\n {router.issues.length} issue{router.issues.length === 1 ? '' : 's'}\n </Badge>\n ) : (\n <Badge variant=\"success\">no issues</Badge>\n )}\n </Row>\n\n <div className=\"grid min-h-0 flex-1 grid-cols-1 xl:grid-cols-[minmax(0,5fr)_minmax(0,4fr)]\">\n <section\n aria-label=\"Routes\"\n className=\"min-w-0 border-b border-border xl:border-b-0 xl:border-r\"\n >\n <RouteList projectId={projectId} routes={routes} conflicts={conflicts} pages={pages} />\n </section>\n <section aria-label=\"Redirects\" className=\"min-w-0\">\n <RedirectManager redirects={redirects} followRedirects={router.followRedirects} />\n </section>\n </div>\n </Stack>\n )\n}\n"]}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { LucideIcon } from 'lucide-react';
|
|
2
|
-
import type { Project, SectionAvailability, SectionAvailabilityMap, SectionId } from '@elytracms/core/studio-core';
|
|
3
|
-
/**
|
|
4
|
-
* App-level navigation ids: the studio-core sections plus surfaces registered
|
|
5
|
-
* by the app itself. `routes` (EC-170) is the first app-registered surface —
|
|
6
|
-
* it lives next to the CMS in the rail and rides on the CMS availability,
|
|
7
|
-
* since routes/redirects are CMS routing concerns (`@elytracms/core/cms-core`).
|
|
8
|
-
*/
|
|
9
|
-
export type StudioNavId = SectionId | 'routes';
|
|
10
|
-
export interface SectionNavItem {
|
|
11
|
-
id: StudioNavId;
|
|
12
|
-
label: string;
|
|
13
|
-
description: string;
|
|
14
|
-
icon: LucideIcon;
|
|
15
|
-
}
|
|
16
|
-
/** Navigation items in canonical order (brief §8.1 + EC-170). */
|
|
17
|
-
export declare const SECTION_NAV_ITEMS: readonly SectionNavItem[];
|
|
18
|
-
export declare function sectionNavItem(id: StudioNavId): SectionNavItem;
|
|
19
|
-
/**
|
|
20
|
-
* Availability for a nav id. App-registered surfaces map onto the section
|
|
21
|
-
* whose data they manage: Routes follows the CMS availability.
|
|
22
|
-
*/
|
|
23
|
-
export declare function navAvailabilityOf(map: SectionAvailabilityMap, id: StudioNavId): SectionAvailability;
|
|
24
|
-
/**
|
|
25
|
-
* The section a project should open to: the first non-`unavailable` section in
|
|
26
|
-
* canonical order. Settings is always available, so this never returns null.
|
|
27
|
-
*/
|
|
28
|
-
export declare function firstAvailableSection(project: Project): SectionId;
|
|
29
|
-
/**
|
|
30
|
-
* Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`
|
|
31
|
-
* (incl. the app-registered Routes surface, EC-170). Returns null for the bare
|
|
32
|
-
* project URL or an unknown segment. The section is the SECOND path segment —
|
|
33
|
-
* directly after the projectId; there is no `/p` prefix (EC-220). The single
|
|
34
|
-
* source of truth for active-section detection: the shell breadcrumb and the
|
|
35
|
-
* nav rail both read it, so the segment index lives in exactly one place.
|
|
36
|
-
*/
|
|
37
|
-
export declare function activeSectionFromPath(pathname: string): StudioNavId | null;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react';
|
|
2
|
-
import { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core';
|
|
3
|
-
const ICONS = {
|
|
4
|
-
components: Blocks,
|
|
5
|
-
cms: Database,
|
|
6
|
-
routes: Signpost,
|
|
7
|
-
assets: Image,
|
|
8
|
-
extensions: Puzzle,
|
|
9
|
-
settings: Settings,
|
|
10
|
-
};
|
|
11
|
-
/** Descriptor for the app-registered Routes surface (EC-170). */
|
|
12
|
-
const ROUTES_DESCRIPTOR = {
|
|
13
|
-
id: 'routes',
|
|
14
|
-
label: 'Routes',
|
|
15
|
-
description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',
|
|
16
|
-
};
|
|
17
|
-
/** Canonical nav order: studio-core order with Routes slotted after CMS. */
|
|
18
|
-
const NAV_IDS = SECTION_IDS.flatMap((id) => id === 'cms' ? [id, 'routes'] : [id]);
|
|
19
|
-
function descriptorOf(id) {
|
|
20
|
-
return id === 'routes' ? ROUTES_DESCRIPTOR : SECTIONS[id];
|
|
21
|
-
}
|
|
22
|
-
/** Navigation items in canonical order (brief §8.1 + EC-170). */
|
|
23
|
-
export const SECTION_NAV_ITEMS = NAV_IDS.map((id) => ({
|
|
24
|
-
id,
|
|
25
|
-
label: descriptorOf(id).label,
|
|
26
|
-
description: descriptorOf(id).description,
|
|
27
|
-
icon: ICONS[id],
|
|
28
|
-
}));
|
|
29
|
-
export function sectionNavItem(id) {
|
|
30
|
-
return SECTION_NAV_ITEMS.find((item) => item.id === id);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Availability for a nav id. App-registered surfaces map onto the section
|
|
34
|
-
* whose data they manage: Routes follows the CMS availability.
|
|
35
|
-
*/
|
|
36
|
-
export function navAvailabilityOf(map, id) {
|
|
37
|
-
return availabilityOf(map, id === 'routes' ? 'cms' : id);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* The section a project should open to: the first non-`unavailable` section in
|
|
41
|
-
* canonical order. Settings is always available, so this never returns null.
|
|
42
|
-
*/
|
|
43
|
-
export function firstAvailableSection(project) {
|
|
44
|
-
const found = SECTION_IDS.find((id) => availabilityOf(project.settings.sectionAvailability, id) !== 'unavailable');
|
|
45
|
-
return found ?? 'settings';
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`
|
|
49
|
-
* (incl. the app-registered Routes surface, EC-170). Returns null for the bare
|
|
50
|
-
* project URL or an unknown segment. The section is the SECOND path segment —
|
|
51
|
-
* directly after the projectId; there is no `/p` prefix (EC-220). The single
|
|
52
|
-
* source of truth for active-section detection: the shell breadcrumb and the
|
|
53
|
-
* nav rail both read it, so the segment index lives in exactly one place.
|
|
54
|
-
*/
|
|
55
|
-
export function activeSectionFromPath(pathname) {
|
|
56
|
-
const segment = pathname.split('/')[2] ?? '';
|
|
57
|
-
if (segment === 'routes')
|
|
58
|
-
return 'routes';
|
|
59
|
-
const parsed = sectionIdSchema.safeParse(segment);
|
|
60
|
-
return parsed.success ? parsed.data : null;
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=sections.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/components/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAElF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAuBpG,MAAM,KAAK,GAAoC;IAC7C,UAAU,EAAE,MAAM;IAClB,GAAG,EAAE,QAAQ;IACb,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,QAAQ;CACnB,CAAA;AAED,iEAAiE;AACjE,MAAM,iBAAiB,GAAG;IACxB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,yEAAyE;CAC9E,CAAA;AAEV,4EAA4E;AAC5E,MAAM,OAAO,GAA2B,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CACjE,EAAE,KAAK,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,QAAQ,CAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAChD,CAAA;AAED,SAAS,YAAY,CAAC,EAAe;IACnC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAA8B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/E,EAAE;IACF,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK;IAC7B,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW;IACzC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;CAChB,CAAC,CAAC,CAAA;AAEH,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAE,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAA2B,EAC3B,EAAe;IAEf,OAAO,cAAc,CAAC,GAAG,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAC5B,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,EAAE,EAAE,CAAC,KAAK,aAAa,CACnF,CAAA;IACD,OAAO,KAAK,IAAI,UAAU,CAAA;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5C,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IACzC,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACjD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5C,CAAC","sourcesContent":["import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core'\nimport type {\n Project,\n SectionAvailability,\n SectionAvailabilityMap,\n SectionId,\n} from '@elytracms/core/studio-core'\n\n/**\n * App-level navigation ids: the studio-core sections plus surfaces registered\n * by the app itself. `routes` (EC-170) is the first app-registered surface —\n * it lives next to the CMS in the rail and rides on the CMS availability,\n * since routes/redirects are CMS routing concerns (`@elytracms/core/cms-core`).\n */\nexport type StudioNavId = SectionId | 'routes'\n\nexport interface SectionNavItem {\n id: StudioNavId\n label: string\n description: string\n icon: LucideIcon\n}\n\nconst ICONS: Record<StudioNavId, LucideIcon> = {\n components: Blocks,\n cms: Database,\n routes: Signpost,\n assets: Image,\n extensions: Puzzle,\n settings: Settings,\n}\n\n/** Descriptor for the app-registered Routes surface (EC-170). */\nconst ROUTES_DESCRIPTOR = {\n id: 'routes',\n label: 'Routes',\n description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',\n} as const\n\n/** Canonical nav order: studio-core order with Routes slotted after CMS. */\nconst NAV_IDS: readonly StudioNavId[] = SECTION_IDS.flatMap((id) =>\n id === 'cms' ? ([id, 'routes'] as const) : [id],\n)\n\nfunction descriptorOf(id: StudioNavId): { label: string; description: string } {\n return id === 'routes' ? ROUTES_DESCRIPTOR : SECTIONS[id]\n}\n\n/** Navigation items in canonical order (brief §8.1 + EC-170). */\nexport const SECTION_NAV_ITEMS: readonly SectionNavItem[] = NAV_IDS.map((id) => ({\n id,\n label: descriptorOf(id).label,\n description: descriptorOf(id).description,\n icon: ICONS[id],\n}))\n\nexport function sectionNavItem(id: StudioNavId): SectionNavItem {\n return SECTION_NAV_ITEMS.find((item) => item.id === id)!\n}\n\n/**\n * Availability for a nav id. App-registered surfaces map onto the section\n * whose data they manage: Routes follows the CMS availability.\n */\nexport function navAvailabilityOf(\n map: SectionAvailabilityMap,\n id: StudioNavId,\n): SectionAvailability {\n return availabilityOf(map, id === 'routes' ? 'cms' : id)\n}\n\n/**\n * The section a project should open to: the first non-`unavailable` section in\n * canonical order. Settings is always available, so this never returns null.\n */\nexport function firstAvailableSection(project: Project): SectionId {\n const found = SECTION_IDS.find(\n (id) => availabilityOf(project.settings.sectionAvailability, id) !== 'unavailable',\n )\n return found ?? 'settings'\n}\n\n/**\n * Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`\n * (incl. the app-registered Routes surface, EC-170). Returns null for the bare\n * project URL or an unknown segment. The section is the SECOND path segment —\n * directly after the projectId; there is no `/p` prefix (EC-220). The single\n * source of truth for active-section detection: the shell breadcrumb and the\n * nav rail both read it, so the segment index lives in exactly one place.\n */\nexport function activeSectionFromPath(pathname: string): StudioNavId | null {\n const segment = pathname.split('/')[2] ?? ''\n if (segment === 'routes') return 'routes'\n const parsed = sectionIdSchema.safeParse(segment)\n return parsed.success ? parsed.data : null\n}\n"]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { UserRecord } from '@elytracms/core/persistence';
|
|
2
|
-
/**
|
|
3
|
-
* Free-text filter over the studio users roster (EC-240, 240b) — matches a query
|
|
4
|
-
* against a user's name, email, id, or any of their roles, case-insensitively.
|
|
5
|
-
* Pure + deterministic (mirrors `asset-search.ts`), so the roster's search state
|
|
6
|
-
* is reproducible from fixtures.
|
|
7
|
-
*/
|
|
8
|
-
export declare function filterUsers(users: readonly UserRecord[], query: string): readonly UserRecord[];
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Free-text filter over the studio users roster (EC-240, 240b) — matches a query
|
|
3
|
-
* against a user's name, email, id, or any of their roles, case-insensitively.
|
|
4
|
-
* Pure + deterministic (mirrors `asset-search.ts`), so the roster's search state
|
|
5
|
-
* is reproducible from fixtures.
|
|
6
|
-
*/
|
|
7
|
-
export function filterUsers(users, query) {
|
|
8
|
-
const q = query.trim().toLowerCase();
|
|
9
|
-
if (!q)
|
|
10
|
-
return users;
|
|
11
|
-
return users.filter((user) => [user.name ?? '', user.email, user.id, ...user.roles].some((field) => field.toLowerCase().includes(q)));
|
|
12
|
-
}
|
|
13
|
-
//# sourceMappingURL=user-search.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"user-search.js","sourceRoot":"","sources":["../../../src/components/users/user-search.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAA4B,EAAE,KAAa;IACrE,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IACpB,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3B,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACnE,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAChC,CACF,CAAA;AACH,CAAC","sourcesContent":["import type { UserRecord } from '@elytracms/core/persistence'\n\n/**\n * Free-text filter over the studio users roster (EC-240, 240b) — matches a query\n * against a user's name, email, id, or any of their roles, case-insensitively.\n * Pure + deterministic (mirrors `asset-search.ts`), so the roster's search state\n * is reproducible from fixtures.\n */\nexport function filterUsers(users: readonly UserRecord[], query: string): readonly UserRecord[] {\n const q = query.trim().toLowerCase()\n if (!q) return users\n return users.filter((user) =>\n [user.name ?? '', user.email, user.id, ...user.roles].some((field) =>\n field.toLowerCase().includes(q),\n ),\n )\n}\n"]}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* Workspace-config boot gate (EC-154) — sibling of the EC-153 `BackendGate`.
|
|
4
|
-
* The workspace config is static boot input (bundled `elytra.config.ts` or
|
|
5
|
-
* env-injected JSON), so an *invalid* config is a deterministic boot-error
|
|
6
|
-
* state rendered before auth and the studio store: structured, file-located
|
|
7
|
-
* issues — never a silent fall-back to unconfigured behavior. No config and a
|
|
8
|
-
* valid config render children untouched.
|
|
9
|
-
*/
|
|
10
|
-
export declare function WorkspaceGate({ children }: {
|
|
11
|
-
children: ReactNode;
|
|
12
|
-
}): import("react").JSX.Element;
|