@scalar/api-client 3.5.1 → 3.6.0
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/CHANGELOG.md +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/style.css +3916 -4768
- package/dist/styles/tailwind.config.css +20 -0
- package/dist/styles/utilities.css +45 -0
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.js +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.js.map +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.script.js +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.script.js.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.d.ts +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.d.ts.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/app/App.vue.d.ts +15 -31
- package/dist/v2/features/app/App.vue.d.ts.map +1 -1
- package/dist/v2/features/app/App.vue.js.map +1 -1
- package/dist/v2/features/app/App.vue.script.js +107 -28
- package/dist/v2/features/app/App.vue.script.js.map +1 -1
- package/dist/v2/features/app/app-state.d.ts +10 -14
- package/dist/v2/features/app/app-state.d.ts.map +1 -1
- package/dist/v2/features/app/app-state.js +53 -21
- package/dist/v2/features/app/app-state.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.script.js +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/AppHeaderActions.vue.d.ts +32 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.js +7 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.js.map +1 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.script.js +170 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts +2 -3
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.js +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.js.map +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.script.js +1 -2
- package/dist/v2/features/app/components/AppSidebar.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts +1 -2
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js +3 -34
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.d.ts +1 -1
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/PublishDocumentModal.vue.d.ts +77 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.js +7 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.js.map +1 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.script.js +209 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/SyncConflictResolutionEditor.vue.d.ts.map +1 -0
- package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.js +2 -2
- package/dist/v2/features/app/components/SyncConflictResolutionEditor.vue.js.map +1 -0
- package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.script.js +1 -1
- package/dist/v2/features/{collection/components/SyncConflictResolutionEditor.vue.js.map → app/components/SyncConflictResolutionEditor.vue.script.js.map} +1 -1
- package/dist/v2/features/app/helpers/check-version-conflict.d.ts +8 -5
- package/dist/v2/features/app/helpers/check-version-conflict.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/check-version-conflict.js +10 -7
- package/dist/v2/features/app/helpers/check-version-conflict.js.map +1 -1
- package/dist/v2/features/app/helpers/create-api-client-app.d.ts +8 -5
- package/dist/v2/features/app/helpers/create-api-client-app.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/create-api-client-app.js +2 -2
- package/dist/v2/features/app/helpers/create-api-client-app.js.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.d.ts +1 -10
- package/dist/v2/features/app/helpers/load-registry-document.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.js +6 -5
- package/dist/v2/features/app/helpers/load-registry-document.js.map +1 -1
- package/dist/v2/features/app/helpers/registry-error-messages.d.ts +23 -0
- package/dist/v2/features/app/helpers/registry-error-messages.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/registry-error-messages.js +63 -0
- package/dist/v2/features/app/helpers/registry-error-messages.js.map +1 -0
- package/dist/v2/features/app/hooks/use-active-document-version.d.ts +2 -1
- package/dist/v2/features/app/hooks/use-active-document-version.d.ts.map +1 -1
- package/dist/v2/features/app/hooks/use-active-document-version.js.map +1 -1
- package/dist/v2/features/app/hooks/use-document-sync.d.ts +126 -0
- package/dist/v2/features/app/hooks/use-document-sync.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-document-sync.js +448 -0
- package/dist/v2/features/app/hooks/use-document-sync.js.map +1 -0
- package/dist/v2/features/app/hooks/use-network-status.d.ts +29 -0
- package/dist/v2/features/app/hooks/use-network-status.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-network-status.js +58 -0
- package/dist/v2/features/app/hooks/use-network-status.js.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts +1 -25
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts.map +1 -1
- package/dist/v2/features/app/hooks/use-sidebar-documents.js.map +1 -1
- package/dist/v2/features/app/index.d.ts +1 -1
- package/dist/v2/features/app/index.d.ts.map +1 -1
- package/dist/v2/features/collection/DocumentCollection.vue.d.ts.map +1 -1
- package/dist/v2/features/collection/DocumentCollection.vue.js.map +1 -1
- package/dist/v2/features/collection/DocumentCollection.vue.script.js +43 -277
- package/dist/v2/features/collection/DocumentCollection.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js +25 -9
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js.map +1 -1
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.d.ts.map +1 -1
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.js +5 -5
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.js.map +1 -1
- package/dist/v2/types/configuration.d.ts +273 -7
- package/dist/v2/types/configuration.d.ts.map +1 -1
- package/dist/vue-styles.css +1389 -0
- package/package.json +21 -15
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.js +0 -7
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.js.map +0 -1
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.script.js +0 -51
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.script.js.map +0 -1
- package/dist/v2/features/collection/components/SyncConflictResolutionEditor.vue.d.ts.map +0 -1
- package/dist/v2/features/collection/components/SyncConflictResolutionEditor.vue.script.js.map +0 -1
- /package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.d.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentBreadcrumb.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/DocumentBreadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarCombobox,\n useModal,\n type ScalarComboboxOption,\n type ScalarComboboxOptionGroup,\n} from '@scalar/components'\nimport { ScalarIconCaretDown } from '@scalar/icons'\nimport { useToasts } from '@scalar/use-toasts'\nimport { getWorkspaceId } from '@scalar/workspace-store/persistence'\nimport { computed, ref } from 'vue'\n\nimport {\n DEFAULT_TEAM_WORKSPACE_SLUG,\n TEAM_WORKSPACES_ENABLED,\n type AppState,\n} from '@/v2/features/app/app-state'\nimport type { VersionStatus } from '@/v2/features/app/helpers/compute-version-status'\nimport { createDraftRegistryDocument } from '@/v2/features/app/helpers/create-draft-registry-document'\nimport { loadRegistryDocument } from '@/v2/features/app/helpers/load-registry-document'\nimport { VERSION_STATUS_PRESENTATION } from '@/v2/features/app/helpers/version-status-presentation'\nimport { useActiveDocumentVersion } from '@/v2/features/app/hooks/use-active-document-version'\nimport type { RegistryDocumentsState } from '@/v2/features/app/hooks/use-sidebar-documents'\nimport { useVersionConflictCheck } from '@/v2/features/app/hooks/use-version-conflict-check'\nimport { safeRun } from '@/v2/helpers/safe-run'\nimport type { ImportDocumentFromRegistry } from '@/v2/types/configuration'\n\nimport CreateVersionModal from './CreateVersionModal.vue'\n\nconst {\n app,\n registryDocuments = { status: 'success', documents: [] },\n fetchRegistryDocument,\n} = defineProps<{\n /** The app state used to read the active document and emit navigation events. */\n app: AppState\n /**\n * The list of all available registry documents. Used to merge advertised\n * versions with loaded workspace documents so the picker can offer every\n * known version of the active document.\n */\n registryDocuments?: RegistryDocumentsState\n /**\n * Fetcher used to import a version from the registry when the user picks\n * one that has not been loaded into the local workspace store yet.\n */\n fetchRegistryDocument?: ImportDocumentFromRegistry\n}>()\n\nconst emit = defineEmits<{\n /**\n * Emitted when the user clicks the \"+\" affordance inside the workspace\n * picker dropdown. The parent owns the create-workspace modal so the\n * breadcrumb stays free of workspace lifecycle concerns.\n */\n (event: 'createWorkspace'): void\n}>()\n\nconst { toast } = useToasts()\n\n/**\n * Resolve the active document group, its versions, and the currently\n * selected version once. The right-side sync indicator consumes the same\n * composable so the two surfaces always agree on what \"active\" means.\n */\nconst { activeRegistryMeta, activeItem, versions, activeVersion } =\n useActiveDocumentVersion({\n app,\n registryDocuments: () => registryDocuments,\n })\n\n/** Workspace label rendered as the first segment of the breadcrumb. */\nconst workspaceTitle = computed(\n () => app.workspace.activeWorkspace.value?.label ?? '',\n)\n\n/** Identifier for the active workspace - used as the combobox selection. */\nconst activeWorkspaceId = computed(\n () => app.workspace.activeWorkspace.value?.id,\n)\n\n/**\n * Workspaces grouped by team, mapped into the shape `ScalarCombobox`\n * expects. We always render this as a grouped combobox - even when there\n * is only one group - so the dropdown layout stays consistent and the\n * group label clearly separates `Team Workspaces` from `Local Workspaces`.\n */\nconst workspaceGroups = computed<ScalarComboboxOptionGroup[]>(() =>\n app.workspace.workspaceGroups.value.map((group) => ({\n // `WorkspaceGroup.label` is optional, but the combobox requires a\n // string. Falling back to an empty string lets the option group still\n // render its rows; the `group` slot below renders nothing for empty\n // labels so we do not surface an awkward blank header.\n label: group.label ?? '',\n options: group.options.map((option) => ({\n id: option.id,\n label: option.label,\n })),\n })),\n)\n\n/**\n * Currently selected option object. The combobox compares by reference, so\n * we have to look the option up inside `workspaceGroups` rather than build\n * a fresh `{ id, label }` here.\n */\nconst selectedWorkspaceOption = computed<ScalarComboboxOption | undefined>(\n () => {\n const id = activeWorkspaceId.value\n if (!id) {\n return undefined\n }\n for (const group of workspaceGroups.value) {\n const match = group.options.find((option) => option.id === id)\n if (match) {\n return match\n }\n }\n return undefined\n },\n)\n\n/** Title rendered for the document segment of the breadcrumb. */\nconst documentTitle = computed(() => {\n const item = activeItem.value\n if (item) {\n return item.title\n }\n const doc = app.store.value?.workspace.activeDocument\n return doc?.info?.title ?? ''\n})\n\n/**\n * Options passed to the combobox. We extend the base option shape with the\n * extra metadata the row template needs:\n * - `isLatest` toggles the \"Latest\" badge on the most recent version.\n * - `status` drives the row icon (synced / push / pull / conflict).\n *\n * `id` must match `SidebarDocumentVersion.key` so emitted updates can\n * resolve back to the underlying version.\n */\ntype VersionOption = ScalarComboboxOption & {\n isLatest: boolean\n status: VersionStatus\n}\n\nconst versionOptions = computed<VersionOption[]>(() =>\n versions.value.map((v) => ({\n id: v.key,\n label: v.version,\n // `isLatest` is precomputed by the sidebar layer and tracks the latest\n // *registry-advertised* version, not just the first row in the list —\n // drafts surface ahead of registry rows but never claim the badge.\n isLatest: v.isLatest,\n status: v.status,\n })),\n)\n\n/**\n * The combobox compares `modelValue` to options by reference, so we must\n * return the exact option object from `versionOptions` rather than a freshly\n * constructed one — otherwise the active row would never render as selected.\n */\nconst selectedOption = computed<VersionOption | undefined>(() => {\n const active = activeVersion.value\n if (!active) {\n return undefined\n }\n return versionOptions.value.find((option) => option.id === active.key)\n})\n\n/**\n * True when the current route actually resolves to a document. The workspace\n * store's `activeDocument` getter falls back to the first document in the\n * workspace even while the user is on a settings or environment page, so we\n * key off `activeEntities.documentSlug` instead — it is only populated when\n * the route actually carries a `:documentSlug` segment.\n */\nconst hasActiveDocument = computed(() =>\n Boolean(app.activeEntities.documentSlug.value),\n)\n\n/**\n * True only for registry-backed documents, which are the only ones that can\n * advertise multiple versions and therefore the only ones that get a picker.\n */\nconst hasVersionPicker = computed(() =>\n Boolean(activeItem.value && activeRegistryMeta.value),\n)\n\n/** Hide the entire breadcrumb when there is nothing meaningful to show. */\nconst isVisible = computed(() =>\n Boolean(workspaceTitle.value || hasActiveDocument.value),\n)\n\n/** Guards against double-firing the loader when the user clicks repeatedly. */\nconst isLoading = ref(false)\n\n// Run the three-way conflict check for every loaded version of the active\n// document group. We deliberately do not check versions of *other*\n// documents — the breadcrumb only renders the picker for the active one —\n// but every row inside that picker carries a status icon, so we want a\n// fresh result for each of them up front rather than on demand.\nuseVersionConflictCheck({\n store: () => app.store.value,\n fetcher: () => fetchRegistryDocument,\n registry: () => activeItem.value?.registry,\n versions,\n})\n\nconst navigateToDocument = (documentSlug: string) => {\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug,\n })\n}\n\n/**\n * Routes to the get-started page for a workspace, identified by the\n * combobox option id. We resolve the underlying `WorkspaceOption` from\n * `workspaceList` so the team-slug and slug pair is sourced from the same\n * place the picker built its options - that sidesteps any ambiguity if a\n * teamSlug or slug were ever to contain a slash.\n *\n * When team workspaces are enabled and the active team has no real\n * workspace yet, the picker may surface a synthetic placeholder option\n * (id: `getWorkspaceId(teamSlug, DEFAULT_TEAM_WORKSPACE_SLUG)`). We route\n * that through the normal navigation flow so the route handler can create\n * the workspace on demand.\n */\nconst navigateToWorkspaceGetStarted = (workspaceId: string) => {\n const emitNavigation = (teamSlug: string, slug: string) => {\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'get-started',\n teamSlug,\n workspaceSlug: slug,\n })\n }\n\n const workspace = app.workspace.workspaceList.value?.find(\n (w) => w.id === workspaceId,\n )\n if (workspace) {\n emitNavigation(workspace.teamSlug, workspace.slug)\n return\n }\n\n if (!TEAM_WORKSPACES_ENABLED) {\n return\n }\n\n const activeTeamSlug = app.activeEntities.teamSlug?.value\n if (\n activeTeamSlug &&\n activeTeamSlug !== 'local' &&\n workspaceId === getWorkspaceId(activeTeamSlug, DEFAULT_TEAM_WORKSPACE_SLUG)\n ) {\n emitNavigation(activeTeamSlug, DEFAULT_TEAM_WORKSPACE_SLUG)\n }\n}\n\n/**\n * Click handler for the workspace label when it renders as a plain link\n * (i.e. the user is NOT yet on the get-started page). Navigating there\n * is what flips the same segment into a dropdown on the next render.\n */\nconst handleWorkspaceLinkClick = () => {\n const id = activeWorkspaceId.value\n if (!id) {\n return\n }\n navigateToWorkspaceGetStarted(id)\n}\n\n/**\n * Click handler for the document title segment. Routes back to the\n * document overview - useful when the user is deep inside an operation\n * page and wants to jump back to the top-level doc view without losing\n * the workspace / document context the breadcrumb already encodes.\n */\nconst handleDocumentTitleClick = () => {\n const documentSlug = app.activeEntities.documentSlug.value\n if (!documentSlug) {\n return\n }\n navigateToDocument(documentSlug)\n}\n\n/**\n * Selecting a workspace from the combobox routes to that workspace's\n * get-started page. We deliberately go to get-started rather than the\n * environment overview because the workspace switch is the natural moment\n * to surface onboarding guidance - the user has effectively arrived at a\n * fresh workspace and may not yet have any documents loaded.\n */\nconst handleWorkspaceSelect = (option: ScalarComboboxOption | undefined) => {\n if (!option) {\n return\n }\n if (option.id === activeWorkspaceId.value) {\n return\n }\n navigateToWorkspaceGetStarted(option.id)\n}\n\nconst handleVersionSelect = async (option: VersionOption | undefined) => {\n if (!option) {\n return\n }\n const version = versions.value.find((v) => v.key === option.id)\n if (!version || version.key === activeVersion.value?.key) {\n return\n }\n\n // Already imported into the workspace store — just route to it. The sidebar\n // active-version selection follows from `activeDocumentSlug` so switching\n // the route is enough to keep both surfaces in sync.\n if (version.documentName) {\n navigateToDocument(version.documentName)\n return\n }\n\n // Capture `app.store.value` into a local so the closure passed to\n // `safeRun` keeps the non-nullable type without needing an assertion,\n // and so a later store swap cannot redirect the in-flight load.\n const registry = activeItem.value?.registry\n const workspaceStore = app.store.value\n if (!registry || !fetchRegistryDocument || !workspaceStore) {\n toast('Cannot load this version without a registry fetcher.', 'error')\n return\n }\n\n if (isLoading.value) {\n return\n }\n\n isLoading.value = true\n\n // The loader's helpers (fetcher, coercion, slug generation) can throw on\n // network failures or unexpected payloads. `safeRun` swallows the\n // exception and surfaces it as an `{ ok: false, error }` result so a\n // single rejection cannot leave the picker permanently disabled.\n const outcome = await safeRun(() =>\n loadRegistryDocument({\n fetcher: fetchRegistryDocument,\n workspaceStore,\n namespace: registry.namespace,\n slug: registry.slug,\n version: version.version,\n // Forward the registry-advertised hash from the picker row. Storing it\n // on the document lets us later detect when the registry has moved on\n // and surface upstream changes.\n commitHash: version.registryCommitHash,\n }),\n )\n\n isLoading.value = false\n\n if (!outcome.ok) {\n toast(outcome.error, 'error')\n return\n }\n\n const result = outcome.data\n if (!result.ok) {\n toast(result.error, 'error')\n return\n }\n\n navigateToDocument(result.documentName)\n}\n\n/** Modal lifecycle for the create-new-version flow. */\nconst createVersionModal = useModal()\n\n/**\n * Versions already loaded into the workspace store for the active group.\n * Used to keep the modal from accepting duplicates that would silently\n * collide with an existing local document. Versions advertised only by the\n * registry are intentionally NOT included - submitting one of those is the\n * conflict-resolution path the create-draft flow opts into.\n */\nconst loadedVersionStrings = computed(() =>\n versions.value.filter((v) => Boolean(v.documentName)).map((v) => v.version),\n)\n\nconst handleCreateVersion = async (version: string) => {\n const registry = activeItem.value?.registry\n const seedDocumentName = app.activeEntities.documentSlug.value\n const store = app.store.value\n\n if (!registry || !seedDocumentName || !store) {\n toast(\n 'Cannot create a new version without an active registry document.',\n 'error',\n )\n return\n }\n\n if (isLoading.value) {\n return\n }\n\n isLoading.value = true\n\n const result = await createDraftRegistryDocument({\n workspaceStore: store,\n namespace: registry.namespace,\n slug: registry.slug,\n version,\n seedDocumentName,\n })\n\n isLoading.value = false\n\n if (!result.ok) {\n toast(result.error, 'error')\n return\n }\n\n navigateToDocument(result.documentName)\n}\n</script>\n\n<template>\n <nav\n v-if=\"isVisible\"\n aria-label=\"Document breadcrumb\"\n class=\"flex min-w-0 items-center gap-2 text-sm font-medium\">\n <!--\n The breadcrumb leads with a vertical bar to visually divide it from\n the menu trigger to its left. The menu trigger itself surfaces the\n active scope (\"Team\" / \"Local\") so it doubles as the leading\n breadcrumb segment - the bar separates that scope label from the\n workspace selector.\n -->\n <span\n aria-hidden=\"true\"\n class=\"bg-border h-4 w-px shrink-0\" />\n\n <!--\n Workspace segment. Two-step affordance keyed off whether a document\n is active on the route:\n 1. While viewing a document: a plain link that navigates to the\n workspace's get-started page. A single click cannot accidentally\n switch workspaces while the user is mid-task.\n 2. On any workspace-level page (no active document, e.g. the\n get-started or environment pages): the same label expands into\n a `ScalarCombobox` so switching workspaces is one extra click\n from the workspace home, which is the natural moment for that\n decision.\n -->\n <template v-if=\"workspaceTitle\">\n <ScalarCombobox\n v-if=\"!hasActiveDocument\"\n class=\"workspace-picker w-72\"\n :modelValue=\"selectedWorkspaceOption\"\n :options=\"workspaceGroups\"\n placeholder=\"Search workspaces\"\n @add=\"emit('createWorkspace')\"\n @update:modelValue=\"handleWorkspaceSelect\">\n <button\n aria-label=\"Workspace\"\n class=\"hover:bg-b-2 flex min-w-0 items-center gap-1 rounded px-1.5 py-0.5\"\n type=\"button\">\n <span class=\"truncate\">{{ workspaceTitle }}</span>\n <ScalarIconCaretDown\n class=\"text-c-3 size-3 shrink-0\"\n weight=\"bold\" />\n </button>\n <template #add>\n <span class=\"text-c-1\">Create local workspace</span>\n </template>\n </ScalarCombobox>\n <button\n v-else\n aria-label=\"Open workspace home\"\n class=\"hover:bg-b-2 truncate rounded px-1.5 py-0.5\"\n type=\"button\"\n @click=\"handleWorkspaceLinkClick\">\n {{ workspaceTitle }}\n </button>\n </template>\n\n <template v-if=\"hasActiveDocument\">\n <!--\n Forward slashes separate the inner breadcrumb segments because\n they read as a path-like hierarchy (\"Workspace / Document /\n Version\"), in contrast to the leading bar that separates the\n breadcrumb from the menu trigger.\n -->\n <span\n aria-hidden=\"true\"\n class=\"text-c-3\">\n /\n </span>\n <button\n class=\"hover:bg-b-2 truncate rounded px-1.5 py-0.5\"\n type=\"button\"\n @click=\"handleDocumentTitleClick\">\n {{ documentTitle }}\n </button>\n <template v-if=\"hasVersionPicker\">\n <span\n aria-hidden=\"true\"\n class=\"text-c-3\">\n /\n </span>\n <ScalarCombobox\n class=\"version-picker w-64\"\n :modelValue=\"selectedOption\"\n :options=\"versionOptions\"\n placeholder=\"Search versions\"\n @add=\"createVersionModal.show()\"\n @update:modelValue=\"handleVersionSelect\">\n <button\n aria-label=\"Document version\"\n class=\"hover:bg-b-2 flex items-center gap-1 rounded px-1.5 py-0.5 disabled:opacity-50\"\n :disabled=\"isLoading\"\n type=\"button\">\n <span>{{ activeVersion?.version ?? '' }}</span>\n <ScalarIconCaretDown\n class=\"text-c-3 size-3\"\n weight=\"bold\" />\n </button>\n <template #option=\"{ option, selected }\">\n <component\n :is=\"VERSION_STATUS_PRESENTATION[option.status].icon\"\n :aria-label=\"VERSION_STATUS_PRESENTATION[option.status].label\"\n class=\"size-4 shrink-0\"\n :class=\"VERSION_STATUS_PRESENTATION[option.status].class\"\n :title=\"VERSION_STATUS_PRESENTATION[option.status].label\" />\n <span\n class=\"text-c-1 min-w-0 flex-1 truncate\"\n :class=\"{ 'font-medium': selected }\">\n {{ option.label }}\n </span>\n <span\n v-if=\"option.isLatest\"\n class=\"text-c-3 ml-2 shrink-0 text-xs\">\n Latest\n </span>\n </template>\n <!--\n The combobox's built-in `add` slot renders a `+` icon row below\n the version list. Wiring it up here keeps the create-draft\n affordance discoverable inside the same surface where the user\n picks versions, instead of as a separate button next to it.\n -->\n <template #add>\n <span class=\"text-c-1 font-medium\">New Version</span>\n </template>\n </ScalarCombobox>\n <span\n v-if=\"isLoading\"\n class=\"text-c-3 ml-1 text-xs\">\n Loading…\n </span>\n </template>\n </template>\n <CreateVersionModal\n :existingVersions=\"loadedVersionStrings\"\n :sourceVersion=\"activeVersion?.version\"\n :state=\"createVersionModal\"\n @create=\"handleCreateVersion\" />\n </nav>\n</template>\n\n<style scoped>\n/*\n * The combobox option only marks the *active* (hovered / keyboard-focused)\n * row with a background. We also want a persistent highlight on the\n * currently *selected* version so the user can spot it at a glance, even\n * after moving the cursor or arrowing to another row.\n */\n.version-picker :deep([role='option'][aria-selected='true']) {\n background-color: var(--scalar-background-2);\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DocumentBreadcrumb.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/DocumentBreadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarCombobox,\n useModal,\n type ScalarComboboxOption,\n type ScalarComboboxOptionGroup,\n} from '@scalar/components'\nimport { ScalarIconCaretDown } from '@scalar/icons'\nimport { useToasts } from '@scalar/use-toasts'\nimport { computed, ref } from 'vue'\n\nimport { type AppState } from '@/v2/features/app/app-state'\nimport type { VersionStatus } from '@/v2/features/app/helpers/compute-version-status'\nimport { createDraftRegistryDocument } from '@/v2/features/app/helpers/create-draft-registry-document'\nimport { loadRegistryDocument } from '@/v2/features/app/helpers/load-registry-document'\nimport { VERSION_STATUS_PRESENTATION } from '@/v2/features/app/helpers/version-status-presentation'\nimport { useActiveDocumentVersion } from '@/v2/features/app/hooks/use-active-document-version'\nimport { useVersionConflictCheck } from '@/v2/features/app/hooks/use-version-conflict-check'\nimport { safeRun } from '@/v2/helpers/safe-run'\nimport type {\n ImportDocumentFromRegistry,\n RegistryDocumentsState,\n} from '@/v2/types/configuration'\n\nimport CreateVersionModal from './CreateVersionModal.vue'\n\nconst {\n app,\n registryDocuments = { status: 'success', documents: [] },\n fetchRegistryDocument,\n} = defineProps<{\n /** The app state used to read the active document and emit navigation events. */\n app: AppState\n /**\n * The list of all available registry documents. Used to merge advertised\n * versions with loaded workspace documents so the picker can offer every\n * known version of the active document.\n */\n registryDocuments?: RegistryDocumentsState\n /**\n * Fetcher used to import a version from the registry when the user picks\n * one that has not been loaded into the local workspace store yet.\n */\n fetchRegistryDocument?: ImportDocumentFromRegistry\n}>()\n\nconst emit = defineEmits<{\n /**\n * Emitted when the user clicks the \"+\" affordance inside the workspace\n * picker dropdown. The parent owns the create-workspace modal so the\n * breadcrumb stays free of workspace lifecycle concerns.\n */\n (event: 'createWorkspace'): void\n}>()\n\nconst { toast } = useToasts()\n\n/**\n * Resolve the active document group, its versions, and the currently\n * selected version once. The right-side sync indicator consumes the same\n * composable so the two surfaces always agree on what \"active\" means.\n */\nconst { activeRegistryMeta, activeItem, versions, activeVersion } =\n useActiveDocumentVersion({\n app,\n registryDocuments: () => registryDocuments,\n })\n\n/** Workspace label rendered as the first segment of the breadcrumb. */\nconst workspaceTitle = computed(\n () => app.workspace.activeWorkspace.value?.label ?? '',\n)\n\n/** Identifier for the active workspace - used as the combobox selection. */\nconst activeWorkspaceId = computed(\n () => app.workspace.activeWorkspace.value?.id,\n)\n\n/**\n * Workspaces grouped by team, mapped into the shape `ScalarCombobox`\n * expects. We always render this as a grouped combobox - even when there\n * is only one group - so the dropdown layout stays consistent and the\n * group label clearly separates `Team Workspaces` from `Local Workspaces`.\n */\nconst workspaceGroups = computed<ScalarComboboxOptionGroup[]>(() =>\n app.workspace.workspaceGroups.value.map((group) => ({\n // `WorkspaceGroup.label` is optional, but the combobox requires a\n // string. Falling back to an empty string lets the option group still\n // render its rows; the `group` slot below renders nothing for empty\n // labels so we do not surface an awkward blank header.\n label: group.label ?? '',\n options: group.options.map((option) => ({\n id: option.id,\n label: option.label,\n })),\n })),\n)\n\n/**\n * Currently selected option object. The combobox compares by reference, so\n * we have to look the option up inside `workspaceGroups` rather than build\n * a fresh `{ id, label }` here.\n */\nconst selectedWorkspaceOption = computed<ScalarComboboxOption | undefined>(\n () => {\n const id = activeWorkspaceId.value\n if (!id) {\n return undefined\n }\n for (const group of workspaceGroups.value) {\n const match = group.options.find((option) => option.id === id)\n if (match) {\n return match\n }\n }\n return undefined\n },\n)\n\n/** Title rendered for the document segment of the breadcrumb. */\nconst documentTitle = computed(() => {\n const item = activeItem.value\n if (item) {\n return item.title\n }\n const doc = app.store.value?.workspace.activeDocument\n return doc?.info?.title ?? ''\n})\n\n/**\n * Options passed to the combobox. We extend the base option shape with the\n * extra metadata the row template needs:\n * - `isLatest` toggles the \"Latest\" badge on the most recent version.\n * - `status` drives the row icon (synced / push / pull / conflict).\n *\n * `id` must match `SidebarDocumentVersion.key` so emitted updates can\n * resolve back to the underlying version.\n */\ntype VersionOption = ScalarComboboxOption & {\n isLatest: boolean\n status: VersionStatus\n}\n\nconst versionOptions = computed<VersionOption[]>(() =>\n versions.value.map((v) => ({\n id: v.key,\n label: v.version,\n // `isLatest` is precomputed by the sidebar layer and tracks the latest\n // *registry-advertised* version, not just the first row in the list —\n // drafts surface ahead of registry rows but never claim the badge.\n isLatest: v.isLatest,\n status: v.status,\n })),\n)\n\n/**\n * The combobox compares `modelValue` to options by reference, so we must\n * return the exact option object from `versionOptions` rather than a freshly\n * constructed one — otherwise the active row would never render as selected.\n */\nconst selectedOption = computed<VersionOption | undefined>(() => {\n const active = activeVersion.value\n if (!active) {\n return undefined\n }\n return versionOptions.value.find((option) => option.id === active.key)\n})\n\n/**\n * True when the current route actually resolves to a document. The workspace\n * store's `activeDocument` getter falls back to the first document in the\n * workspace even while the user is on a settings or environment page, so we\n * key off `activeEntities.documentSlug` instead — it is only populated when\n * the route actually carries a `:documentSlug` segment.\n */\nconst hasActiveDocument = computed(() =>\n Boolean(app.activeEntities.documentSlug.value),\n)\n\n/**\n * True only for registry-backed documents, which are the only ones that can\n * advertise multiple versions and therefore the only ones that get a picker.\n */\nconst hasVersionPicker = computed(() =>\n Boolean(activeItem.value && activeRegistryMeta.value),\n)\n\n/** Hide the entire breadcrumb when there is nothing meaningful to show. */\nconst isVisible = computed(() =>\n Boolean(workspaceTitle.value || hasActiveDocument.value),\n)\n\n/** Guards against double-firing the loader when the user clicks repeatedly. */\nconst isLoading = ref(false)\n\n// Run the three-way conflict check for every loaded version of the active\n// document group. We deliberately do not check versions of *other*\n// documents — the breadcrumb only renders the picker for the active one —\n// but every row inside that picker carries a status icon, so we want a\n// fresh result for each of them up front rather than on demand.\nuseVersionConflictCheck({\n store: () => app.store.value,\n fetcher: () => fetchRegistryDocument,\n registry: () => activeItem.value?.registry,\n versions,\n})\n\nconst navigateToDocument = (documentSlug: string) => {\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug,\n })\n}\n\n/**\n * Click handler for the workspace label when it renders as a plain link\n * (i.e. the user is NOT yet on the get-started page). Navigating there\n * is what flips the same segment into a dropdown on the next render.\n */\nconst handleWorkspaceLinkClick = () => {\n const id = activeWorkspaceId.value\n if (!id) {\n return\n }\n app.workspace.navigateToWorkspaceGetStarted(id)\n}\n\n/**\n * Click handler for the document title segment. Routes back to the\n * document overview - useful when the user is deep inside an operation\n * page and wants to jump back to the top-level doc view without losing\n * the workspace / document context the breadcrumb already encodes.\n */\nconst handleDocumentTitleClick = () => {\n const documentSlug = app.activeEntities.documentSlug.value\n if (!documentSlug) {\n return\n }\n navigateToDocument(documentSlug)\n}\n\n/**\n * Selecting a workspace from the combobox routes to that workspace's\n * get-started page. We deliberately go to get-started rather than the\n * environment overview because the workspace switch is the natural moment\n * to surface onboarding guidance - the user has effectively arrived at a\n * fresh workspace and may not yet have any documents loaded.\n */\nconst handleWorkspaceSelect = (option: ScalarComboboxOption | undefined) => {\n if (!option) {\n return\n }\n if (option.id === activeWorkspaceId.value) {\n return\n }\n app.workspace.navigateToWorkspaceGetStarted(option.id)\n}\n\nconst handleVersionSelect = async (option: VersionOption | undefined) => {\n if (!option) {\n return\n }\n const version = versions.value.find((v) => v.key === option.id)\n if (!version || version.key === activeVersion.value?.key) {\n return\n }\n\n // Already imported into the workspace store — just route to it. The sidebar\n // active-version selection follows from `activeDocumentSlug` so switching\n // the route is enough to keep both surfaces in sync.\n if (version.documentName) {\n navigateToDocument(version.documentName)\n return\n }\n\n // Capture `app.store.value` into a local so the closure passed to\n // `safeRun` keeps the non-nullable type without needing an assertion,\n // and so a later store swap cannot redirect the in-flight load.\n const registry = activeItem.value?.registry\n const workspaceStore = app.store.value\n if (!registry || !fetchRegistryDocument || !workspaceStore) {\n toast('Cannot load this version without a registry fetcher.', 'error')\n return\n }\n\n if (isLoading.value) {\n return\n }\n\n isLoading.value = true\n\n // The loader's helpers (fetcher, coercion, slug generation) can throw on\n // network failures or unexpected payloads. `safeRun` swallows the\n // exception and surfaces it as an `{ ok: false, error }` result so a\n // single rejection cannot leave the picker permanently disabled.\n const outcome = await safeRun(() =>\n loadRegistryDocument({\n fetcher: fetchRegistryDocument,\n workspaceStore,\n namespace: registry.namespace,\n slug: registry.slug,\n version: version.version,\n }),\n )\n\n isLoading.value = false\n\n if (!outcome.ok) {\n toast(outcome.error, 'error')\n return\n }\n\n const result = outcome.data\n if (!result.ok) {\n toast(result.error, 'error')\n return\n }\n\n navigateToDocument(result.documentName)\n}\n\n/** Modal lifecycle for the create-new-version flow. */\nconst createVersionModal = useModal()\n\n/**\n * Versions already loaded into the workspace store for the active group.\n * Used to keep the modal from accepting duplicates that would silently\n * collide with an existing local document. Versions advertised only by the\n * registry are intentionally NOT included - submitting one of those is the\n * conflict-resolution path the create-draft flow opts into.\n */\nconst loadedVersionStrings = computed(() =>\n versions.value.filter((v) => Boolean(v.documentName)).map((v) => v.version),\n)\n\nconst handleCreateVersion = async (version: string) => {\n const registry = activeItem.value?.registry\n const seedDocumentName = app.activeEntities.documentSlug.value\n const store = app.store.value\n\n if (!registry || !seedDocumentName || !store) {\n toast(\n 'Cannot create a new version without an active registry document.',\n 'error',\n )\n return\n }\n\n if (isLoading.value) {\n return\n }\n\n isLoading.value = true\n\n const result = await createDraftRegistryDocument({\n workspaceStore: store,\n namespace: registry.namespace,\n slug: registry.slug,\n version,\n seedDocumentName,\n })\n\n isLoading.value = false\n\n if (!result.ok) {\n toast(result.error, 'error')\n return\n }\n\n navigateToDocument(result.documentName)\n}\n</script>\n\n<template>\n <nav\n v-if=\"isVisible\"\n aria-label=\"Document breadcrumb\"\n class=\"flex min-w-0 items-center gap-2 text-sm font-medium\">\n <!--\n The breadcrumb leads with a vertical bar to visually divide it from\n the menu trigger to its left. The menu trigger itself surfaces the\n active scope (\"Team\" / \"Local\") so it doubles as the leading\n breadcrumb segment - the bar separates that scope label from the\n workspace selector.\n -->\n <span\n aria-hidden=\"true\"\n class=\"bg-border h-4 w-px shrink-0\" />\n\n <!--\n Workspace segment. Two-step affordance keyed off whether a document\n is active on the route:\n 1. While viewing a document: a plain link that navigates to the\n workspace's get-started page. A single click cannot accidentally\n switch workspaces while the user is mid-task.\n 2. On any workspace-level page (no active document, e.g. the\n get-started or environment pages): the same label expands into\n a `ScalarCombobox` so switching workspaces is one extra click\n from the workspace home, which is the natural moment for that\n decision.\n -->\n <template v-if=\"workspaceTitle\">\n <ScalarCombobox\n v-if=\"!hasActiveDocument\"\n class=\"workspace-picker w-72\"\n :modelValue=\"selectedWorkspaceOption\"\n :options=\"workspaceGroups\"\n placeholder=\"Search workspaces\"\n @add=\"emit('createWorkspace')\"\n @update:modelValue=\"handleWorkspaceSelect\">\n <button\n aria-label=\"Workspace\"\n class=\"hover:bg-b-2 flex min-w-0 items-center gap-1 rounded px-1.5 py-0.5\"\n type=\"button\">\n <span class=\"truncate\">{{ workspaceTitle }}</span>\n <ScalarIconCaretDown\n class=\"text-c-3 size-3 shrink-0\"\n weight=\"bold\" />\n </button>\n <template #add>\n <span class=\"text-c-1\">Create local workspace</span>\n </template>\n </ScalarCombobox>\n <button\n v-else\n aria-label=\"Open workspace home\"\n class=\"hover:bg-b-2 truncate rounded px-1.5 py-0.5\"\n type=\"button\"\n @click=\"handleWorkspaceLinkClick\">\n {{ workspaceTitle }}\n </button>\n </template>\n\n <template v-if=\"hasActiveDocument\">\n <!--\n Forward slashes separate the inner breadcrumb segments because\n they read as a path-like hierarchy (\"Workspace / Document /\n Version\"), in contrast to the leading bar that separates the\n breadcrumb from the menu trigger.\n -->\n <span\n aria-hidden=\"true\"\n class=\"text-c-3\">\n /\n </span>\n <button\n class=\"hover:bg-b-2 truncate rounded px-1.5 py-0.5\"\n type=\"button\"\n @click=\"handleDocumentTitleClick\">\n {{ documentTitle }}\n </button>\n <template v-if=\"hasVersionPicker\">\n <span\n aria-hidden=\"true\"\n class=\"text-c-3\">\n /\n </span>\n <ScalarCombobox\n class=\"version-picker w-64\"\n :modelValue=\"selectedOption\"\n :options=\"versionOptions\"\n placeholder=\"Search versions\"\n @add=\"createVersionModal.show()\"\n @update:modelValue=\"handleVersionSelect\">\n <button\n aria-label=\"Document version\"\n class=\"hover:bg-b-2 flex items-center gap-1 rounded px-1.5 py-0.5 disabled:opacity-50\"\n :disabled=\"isLoading\"\n type=\"button\">\n <span>{{ activeVersion?.version ?? '' }}</span>\n <ScalarIconCaretDown\n class=\"text-c-3 size-3\"\n weight=\"bold\" />\n </button>\n <template #option=\"{ option, selected }\">\n <component\n :is=\"VERSION_STATUS_PRESENTATION[option.status].icon\"\n :aria-label=\"VERSION_STATUS_PRESENTATION[option.status].label\"\n class=\"size-4 shrink-0\"\n :class=\"VERSION_STATUS_PRESENTATION[option.status].class\"\n :title=\"VERSION_STATUS_PRESENTATION[option.status].label\" />\n <span\n class=\"text-c-1 min-w-0 flex-1 truncate\"\n :class=\"{ 'font-medium': selected }\">\n {{ option.label }}\n </span>\n <span\n v-if=\"option.isLatest\"\n class=\"text-c-3 ml-2 shrink-0 text-xs\">\n Latest\n </span>\n </template>\n <!--\n The combobox's built-in `add` slot renders a `+` icon row below\n the version list. Wiring it up here keeps the create-draft\n affordance discoverable inside the same surface where the user\n picks versions, instead of as a separate button next to it.\n -->\n <template #add>\n <span class=\"text-c-1 font-medium\">New Version</span>\n </template>\n </ScalarCombobox>\n <span\n v-if=\"isLoading\"\n class=\"text-c-3 ml-1 text-xs\">\n Loading…\n </span>\n </template>\n </template>\n <CreateVersionModal\n :existingVersions=\"loadedVersionStrings\"\n :sourceVersion=\"activeVersion?.version\"\n :state=\"createVersionModal\"\n @create=\"handleCreateVersion\" />\n </nav>\n</template>\n\n<style scoped>\n/*\n * The combobox option only marks the *active* (hovered / keyboard-focused)\n * row with a background. We also want a persistent highlight on the\n * currently *selected* version so the user can spot it at a glance, even\n * after moving the cursor or arrowing to another row.\n */\n.version-picker :deep([role='option'][aria-selected='true']) {\n background-color: var(--scalar-background-2);\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8CA,MAAM,OAAO;EASb,MAAM,EAAE,UAAU,WAAU;;;;;;EAO5B,MAAM,EAAE,oBAAoB,YAAY,UAAU,kBAChD,yBAAyB;GACvB,KAAE,QAAA;GACF,yBAAyB,QAAA;GAC1B,CAAA;;EAGH,MAAM,iBAAiB,eACf,QAAA,IAAI,UAAU,gBAAgB,OAAO,SAAS,GACtD;;EAGA,MAAM,oBAAoB,eAClB,QAAA,IAAI,UAAU,gBAAgB,OAAO,GAC7C;;;;;;;EAQA,MAAM,kBAAkB,eACtB,QAAA,IAAI,UAAU,gBAAgB,MAAM,KAAK,WAAW;GAKlD,OAAO,MAAM,SAAS;GACtB,SAAS,MAAM,QAAQ,KAAK,YAAY;IACtC,IAAI,OAAO;IACX,OAAO,OAAO;IACf,EAAE;GACJ,EAAE,CACL;;;;;;EAOA,MAAM,0BAA0B,eACxB;GACJ,MAAM,KAAK,kBAAkB;AAC7B,OAAI,CAAC,GACH;AAEF,QAAK,MAAM,SAAS,gBAAgB,OAAO;IACzC,MAAM,QAAQ,MAAM,QAAQ,MAAM,WAAW,OAAO,OAAO,GAAE;AAC7D,QAAI,MACF,QAAO;;IAKf;;EAGA,MAAM,gBAAgB,eAAe;GACnC,MAAM,OAAO,WAAW;AACxB,OAAI,KACF,QAAO,KAAK;AAGd,WADY,QAAA,IAAI,MAAM,OAAO,UAAU,iBAC3B,MAAM,SAAS;IAC5B;;;;;;;;;;EAgBD,MAAM,iBAAiB,eACrB,SAAS,MAAM,KAAK,OAAO;GACzB,IAAI,EAAE;GACN,OAAO,EAAE;GAIT,UAAU,EAAE;GACZ,QAAQ,EAAE;GACX,EAAE,CACL;;;;;;EAOA,MAAM,iBAAiB,eAA0C;GAC/D,MAAM,SAAS,cAAc;AAC7B,OAAI,CAAC,OACH;AAEF,UAAO,eAAe,MAAM,MAAM,WAAW,OAAO,OAAO,OAAO,IAAG;IACtE;;;;;;;;EASD,MAAM,oBAAoB,eACxB,QAAQ,QAAA,IAAI,eAAe,aAAa,MAAM,CAChD;;;;;EAMA,MAAM,mBAAmB,eACvB,QAAQ,WAAW,SAAS,mBAAmB,MAAM,CACvD;;EAGA,MAAM,YAAY,eAChB,QAAQ,eAAe,SAAS,kBAAkB,MAAM,CAC1D;;EAGA,MAAM,YAAY,IAAI,MAAK;AAO3B,0BAAwB;GACtB,aAAa,QAAA,IAAI,MAAM;GACvB,eAAe,QAAA;GACf,gBAAgB,WAAW,OAAO;GAClC;GACD,CAAA;EAED,MAAM,sBAAsB,iBAAyB;AACnD,WAAA,IAAI,SAAS,KAAK,eAAe;IAC/B,MAAM;IACN,MAAM;IACN;IACD,CAAA;;;;;;;EAQH,MAAM,iCAAiC;GACrC,MAAM,KAAK,kBAAkB;AAC7B,OAAI,CAAC,GACH;AAEF,WAAA,IAAI,UAAU,8BAA8B,GAAE;;;;;;;;EAShD,MAAM,iCAAiC;GACrC,MAAM,eAAe,QAAA,IAAI,eAAe,aAAa;AACrD,OAAI,CAAC,aACH;AAEF,sBAAmB,aAAY;;;;;;;;;EAUjC,MAAM,yBAAyB,WAA6C;AAC1E,OAAI,CAAC,OACH;AAEF,OAAI,OAAO,OAAO,kBAAkB,MAClC;AAEF,WAAA,IAAI,UAAU,8BAA8B,OAAO,GAAE;;EAGvD,MAAM,sBAAsB,OAAO,WAAsC;AACvE,OAAI,CAAC,OACH;GAEF,MAAM,UAAU,SAAS,MAAM,MAAM,MAAM,EAAE,QAAQ,OAAO,GAAE;AAC9D,OAAI,CAAC,WAAW,QAAQ,QAAQ,cAAc,OAAO,IACnD;AAMF,OAAI,QAAQ,cAAc;AACxB,uBAAmB,QAAQ,aAAY;AACvC;;GAMF,MAAM,WAAW,WAAW,OAAO;GACnC,MAAM,iBAAiB,QAAA,IAAI,MAAM;AACjC,OAAI,CAAC,YAAY,CAAC,QAAA,yBAAyB,CAAC,gBAAgB;AAC1D,UAAM,wDAAwD,QAAO;AACrE;;AAGF,OAAI,UAAU,MACZ;AAGF,aAAU,QAAQ;GAMlB,MAAM,UAAU,MAAM,cACpB,qBAAqB;IACnB,SAAS,QAAA;IACT;IACA,WAAW,SAAS;IACpB,MAAM,SAAS;IACf,SAAS,QAAQ;IAClB,CAAC,CACJ;AAEA,aAAU,QAAQ;AAElB,OAAI,CAAC,QAAQ,IAAI;AACf,UAAM,QAAQ,OAAO,QAAO;AAC5B;;GAGF,MAAM,SAAS,QAAQ;AACvB,OAAI,CAAC,OAAO,IAAI;AACd,UAAM,OAAO,OAAO,QAAO;AAC3B;;AAGF,sBAAmB,OAAO,aAAY;;;EAIxC,MAAM,qBAAqB,UAAS;;;;;;;;EASpC,MAAM,uBAAuB,eAC3B,SAAS,MAAM,QAAQ,MAAM,QAAQ,EAAE,aAAa,CAAC,CAAC,KAAK,MAAM,EAAE,QAAQ,CAC7E;EAEA,MAAM,sBAAsB,OAAO,YAAoB;GACrD,MAAM,WAAW,WAAW,OAAO;GACnC,MAAM,mBAAmB,QAAA,IAAI,eAAe,aAAa;GACzD,MAAM,QAAQ,QAAA,IAAI,MAAM;AAExB,OAAI,CAAC,YAAY,CAAC,oBAAoB,CAAC,OAAO;AAC5C,UACE,oEACA,QACF;AACA;;AAGF,OAAI,UAAU,MACZ;AAGF,aAAU,QAAQ;GAElB,MAAM,SAAS,MAAM,4BAA4B;IAC/C,gBAAgB;IAChB,WAAW,SAAS;IACpB,MAAM,SAAS;IACf;IACA;IACD,CAAA;AAED,aAAU,QAAQ;AAElB,OAAI,CAAC,OAAO,IAAI;AACd,UAAM,OAAO,OAAO,QAAO;AAC3B;;AAGF,sBAAmB,OAAO,aAAY;;;UAM9B,UAAA,SAAA,WAAA,EADR,mBA4IM,OA5IN,YA4IM;8BAjIJ,mBAEwC,QAAA;KADtC,eAAY;KACZ,OAAM;;IAcQ,eAAA,SAAA,WAAA,EAAhB,mBA8BW,UAAA,EAAA,KAAA,GAAA,EAAA,CAAA,CA5BA,kBAAA,SAAA,WAAA,EADT,YAoBiB,MAAA,eAAA,EAAA;;KAlBf,OAAM;KACL,YAAY,wBAAA;KACZ,SAAS,gBAAA;KACV,aAAY;KACX,OAAG,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,kBAAA;KACT,uBAAmB;;KAUT,KAAG,cACwC,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAApD,mBAAoD,QAAA,EAA9C,OAAM,YAAU,EAAC,0BAAsB,GAAA,CAAA,EAAA,CAAA;4BAFtC,CART,mBAQS,UART,YAQS,CAJP,mBAAkD,QAAlD,YAAkD,gBAAxB,eAAA,MAAc,EAAA,EAAA,EACxC,YAEkB,MAAA,oBAAA,EAAA;MADhB,OAAM;MACN,QAAO;;;sDAMb,mBAOS,UAAA;;KALP,cAAW;KACX,OAAM;KACN,MAAK;KACJ,SAAO;uBACL,eAAA,MAAc,EAAA,EAAA,EAAA,EAAA,GAAA,IAAA,mBAAA,IAAA,KAAA;IAIL,kBAAA,SAAA,WAAA,EAAhB,mBA2EW,UAAA,EAAA,KAAA,GAAA,EAAA;+BApET,mBAIO,QAAA;MAHL,eAAY;MACZ,OAAM;QAAW,OAEnB,GAAA;KACA,mBAKS,UAAA;MAJP,OAAM;MACN,MAAK;MACJ,SAAO;wBACL,cAAA,MAAa,EAAA,EAAA;KAEF,iBAAA,SAAA,WAAA,EAAhB,mBAwDW,UAAA,EAAA,KAAA,GAAA,EAAA;gCAvDT,mBAIO,QAAA;OAHL,eAAY;OACZ,OAAM;SAAW,OAEnB,GAAA;MACA,YA4CiB,MAAA,eAAA,EAAA;OA3Cf,OAAM;OACL,YAAY,eAAA;OACZ,SAAS,eAAA;OACV,aAAY;OACX,OAAG,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,mBAAkB,CAAC,MAAI;OAC5B,uBAAmB;;OAWT,QAAM,SAM+C,EAN3C,QAAQ,eAAQ;sBACnC,YAK8D,wBAJvD,MAAA,4BAA2B,CAAC,OAAO,QAAQ,KAAI,EAAA;SACnD,cAAY,MAAA,4BAA2B,CAAC,OAAO,QAAQ;SACxD,OAAK,eAAA,CAAC,mBACE,MAAA,4BAA2B,CAAC,OAAO,QAAQ,MAAK,CAAA;SACvD,OAAO,MAAA,4BAA2B,CAAC,OAAO,QAAQ;;;;;;QACrD,mBAIO,QAAA,EAHL,OAAK,eAAA,CAAC,oCAAkC,EAAA,eACf,UAAQ,CAAA,CAAA,EAAA,EAAA,gBAC9B,OAAO,MAAK,EAAA,EAAA;QAGT,OAAO,YAAA,WAAA,EADf,mBAIO,QAJP,YAEyC,WAEzC,IAAA,mBAAA,IAAA,KAAA;;OAQS,KAAG,cACyC,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAArD,mBAAqD,QAAA,EAA/C,OAAM,wBAAsB,EAAC,eAAW,GAAA,CAAA,EAAA,CAAA;8BA1BvC,CATT,mBASS,UAAA;QARP,cAAW;QACX,OAAM;QACL,UAAU,UAAA;QACX,MAAK;WACL,mBAA+C,QAAA,MAAA,gBAAtC,MAAA,cAAa,EAAE,WAAO,GAAA,EAAA,EAAA,EAC/B,YAEkB,MAAA,oBAAA,EAAA;QADhB,OAAM;QACN,QAAO;;;;MA+BL,UAAA,SAAA,WAAA,EADR,mBAIO,QAJP,YAEgC,aAEhC,IAAA,mBAAA,IAAA,KAAA;;;IAGJ,YAIkC,4BAAA;KAH/B,kBAAkB,qBAAA;KAClB,eAAe,MAAA,cAAa,EAAE;KAC9B,OAAO,MAAA,mBAAkB;KACzB,UAAQ"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AppState } from '../../../../v2/features/app/app-state.js';
|
|
2
|
-
import type { RegistryDocumentsState } from '../../../../v2/
|
|
2
|
+
import type { RegistryDocumentsState } from '../../../../v2/types/configuration';
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
/** App state used to read the active document and registry meta. */
|
|
5
5
|
app: AppState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentSyncIndicator.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/DocumentSyncIndicator.vue"],"names":[],"mappings":"AA0DA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"DocumentSyncIndicator.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/DocumentSyncIndicator.vue"],"names":[],"mappings":"AA0DA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AAEtE,KAAK,WAAW,GAAG;IACf,oEAAoE;IACpE,GAAG,EAAE,QAAQ,CAAA;IACb;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,sBAAsB,CAAA;CAC3C,CAAC;AAqEJ,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type ModalState } from '@scalar/components';
|
|
2
|
+
import type { RegistryNamespacesState } from '../../../../v2/types/configuration';
|
|
3
|
+
/**
|
|
4
|
+
* Modal that walks the user through publishing a brand-new document to
|
|
5
|
+
* the registry.
|
|
6
|
+
*
|
|
7
|
+
* The component is purely presentational: it owns the form state, the
|
|
8
|
+
* loading / error indicator and the validation rules, and delegates the
|
|
9
|
+
* actual publish call to the parent through `submit`. The parent
|
|
10
|
+
* resolves `submit` with `{ ok: true }` to close the modal or
|
|
11
|
+
* `{ ok: false, message }` to surface the error inline so the user can
|
|
12
|
+
* fix the input and retry without losing what they typed.
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
17
|
+
/** Modal control returned by `useModal()`. */
|
|
18
|
+
state: ModalState;
|
|
19
|
+
/** Namespaces the user can publish into, with loading status. */
|
|
20
|
+
namespaces: RegistryNamespacesState;
|
|
21
|
+
/**
|
|
22
|
+
* Initial slug to seed the slug input with. The parent typically
|
|
23
|
+
* computes this from the active document's title. Falls back to an
|
|
24
|
+
* empty string so the placeholder is visible.
|
|
25
|
+
*/
|
|
26
|
+
defaultSlug?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Initial version. Defaults to `1.0.0` when the active document does
|
|
29
|
+
* not advertise an `info.version` yet.
|
|
30
|
+
*/
|
|
31
|
+
defaultVersion?: string;
|
|
32
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
33
|
+
submit: (payload: {
|
|
34
|
+
input: {
|
|
35
|
+
namespace: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
version: string;
|
|
38
|
+
};
|
|
39
|
+
done: (outcome: {
|
|
40
|
+
ok: true;
|
|
41
|
+
} | {
|
|
42
|
+
ok: false;
|
|
43
|
+
message: string;
|
|
44
|
+
}) => void;
|
|
45
|
+
}) => any;
|
|
46
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
47
|
+
/** Modal control returned by `useModal()`. */
|
|
48
|
+
state: ModalState;
|
|
49
|
+
/** Namespaces the user can publish into, with loading status. */
|
|
50
|
+
namespaces: RegistryNamespacesState;
|
|
51
|
+
/**
|
|
52
|
+
* Initial slug to seed the slug input with. The parent typically
|
|
53
|
+
* computes this from the active document's title. Falls back to an
|
|
54
|
+
* empty string so the placeholder is visible.
|
|
55
|
+
*/
|
|
56
|
+
defaultSlug?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Initial version. Defaults to `1.0.0` when the active document does
|
|
59
|
+
* not advertise an `info.version` yet.
|
|
60
|
+
*/
|
|
61
|
+
defaultVersion?: string;
|
|
62
|
+
}> & Readonly<{
|
|
63
|
+
onSubmit?: ((payload: {
|
|
64
|
+
input: {
|
|
65
|
+
namespace: string;
|
|
66
|
+
slug: string;
|
|
67
|
+
version: string;
|
|
68
|
+
};
|
|
69
|
+
done: (outcome: {
|
|
70
|
+
ok: true;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
message: string;
|
|
74
|
+
}) => void;
|
|
75
|
+
}) => any) | undefined;
|
|
76
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
77
|
+
//# sourceMappingURL=PublishDocumentModal.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublishDocumentModal.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/PublishDocumentModal.vue"],"names":[],"mappings":"AAsVA,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,oBAAoB,CAAA;AAK3B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAGvE;;;;;;;;;;GAUG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,8CAA8C;WACvC,UAAU;IACjB,iEAAiE;gBACrD,uBAAuB;IACnC;;;;OAIG;kBACW,MAAM;IACpB;;;OAGG;qBACc,MAAM;;;eAoBZ;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE;cACrD,CAAC,OAAO,EAAE;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,GAAG;YAAE,EAAE,EAAE,KAAK,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI;;;IAnC1E,8CAA8C;WACvC,UAAU;IACjB,iEAAiE;gBACrD,uBAAuB;IACnC;;;;OAIG;kBACW,MAAM;IACpB;;;OAGG;qBACc,MAAM;;;eAoBZ;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE;cACrD,CAAC,OAAO,EAAE;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,GAAG;YAAE,EAAE,EAAE,KAAK,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI;;kFAwgBxE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import PublishDocumentModal_vue_vue_type_script_setup_true_lang_default from "./PublishDocumentModal.vue.script.js";
|
|
2
|
+
//#region src/v2/features/app/components/PublishDocumentModal.vue
|
|
3
|
+
var PublishDocumentModal_default = PublishDocumentModal_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { PublishDocumentModal_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=PublishDocumentModal.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublishDocumentModal.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/PublishDocumentModal.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Modal that walks the user through publishing a brand-new document to\n * the registry.\n *\n * The component is purely presentational: it owns the form state, the\n * loading / error indicator and the validation rules, and delegates the\n * actual publish call to the parent through `submit`. The parent\n * resolves `submit` with `{ ok: true }` to close the modal or\n * `{ ok: false, message }` to surface the error inline so the user can\n * fix the input and retry without losing what they typed.\n */\nexport default {}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarListbox,\n ScalarModal,\n useLoadingState,\n type ModalState,\n type ScalarListboxOption,\n} from '@scalar/components'\nimport { slugify } from '@scalar/helpers/string/slugify'\nimport { ScalarIconBuildings, ScalarIconCaretDown } from '@scalar/icons'\nimport { computed, ref, watch } from 'vue'\n\nimport type { RegistryNamespacesState } from '@/v2/types/configuration'\n\nconst {\n state,\n namespaces,\n defaultSlug = '',\n defaultVersion = '1.0.0',\n} = defineProps<{\n /** Modal control returned by `useModal()`. */\n state: ModalState\n /** Namespaces the user can publish into, with loading status. */\n namespaces: RegistryNamespacesState\n /**\n * Initial slug to seed the slug input with. The parent typically\n * computes this from the active document's title. Falls back to an\n * empty string so the placeholder is visible.\n */\n defaultSlug?: string\n /**\n * Initial version. Defaults to `1.0.0` when the active document does\n * not advertise an `info.version` yet.\n */\n defaultVersion?: string\n}>()\n\nconst emit = defineEmits<{\n /**\n * Fired when the user submits a valid form. The parent must resolve\n * the returned promise with either `{ ok: true }` (modal closes) or\n * `{ ok: false, message }` (message is rendered inline so the user\n * can retry).\n */\n (\n event: 'submit',\n payload: {\n input: { namespace: string; slug: string; version: string }\n done: (outcome: { ok: true } | { ok: false; message: string }) => void\n },\n ): void\n}>()\n\nconst namespaceOptions = computed<ScalarListboxOption[]>(() =>\n (namespaces.status === 'success'\n ? namespaces.namespaces\n : (namespaces.namespaces ?? [])\n ).map((entry) => ({\n id: entry.namespace,\n label: entry.title?.trim() || entry.namespace,\n })),\n)\n\nconst isNamespacesLoading = computed(() => namespaces.status === 'loading')\n\n/**\n * The picker component is only rendered when there is more than one\n * namespace to choose between. Single-namespace setups read as a static\n * label so the user does not have to interact with a one-item dropdown.\n */\nconst hasMultipleNamespaces = computed(() => namespaceOptions.value.length > 1)\n\nconst selectedNamespace = ref<ScalarListboxOption | undefined>(undefined)\nconst slug = ref('')\nconst version = ref('')\nconst errorMessage = ref<string | null>(null)\nconst loader = useLoadingState()\n\n/**\n * Resets the form whenever the modal opens. Keeping this scoped to the\n * `open` flag (and not to every prop change) preserves user input\n * across reactive updates while the modal is already on screen.\n */\nwatch(\n () => state.open,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n slug.value = slugify(defaultSlug)\n version.value = defaultVersion\n errorMessage.value = null\n selectedNamespace.value = namespaceOptions.value[0]\n void loader.clear()\n },\n)\n\n/** Keep the listbox selection in sync when the namespace list arrives. */\nwatch(namespaceOptions, (options) => {\n if (selectedNamespace.value) {\n return\n }\n selectedNamespace.value = options[0]\n})\n\nconst trimmedSlug = computed(() => slug.value.trim())\nconst trimmedVersion = computed(() => version.value.trim())\n\n/**\n * Human-friendly preview of the resulting registry coordinate\n * (`namespace/slug@version`). Returns `null` while any of the three\n * pieces is still missing so we never show a half-rendered path.\n * Mirrors the same fields submitted to the publish call so the user\n * sees exactly what will land on the registry.\n */\nconst publishPreview = computed<string | null>(() => {\n const namespace = selectedNamespace.value?.id\n if (!namespace || !trimmedSlug.value || !trimmedVersion.value) {\n return null\n }\n return `${namespace}/${trimmedSlug.value}@${trimmedVersion.value}`\n})\n\nconst isSubmitDisabled = computed(\n () =>\n !selectedNamespace.value ||\n trimmedSlug.value.length === 0 ||\n trimmedVersion.value.length === 0 ||\n loader.isLoading,\n)\n\nconst handleSubmit = (): void => {\n errorMessage.value = null\n\n const namespace = selectedNamespace.value?.id\n if (!namespace || !trimmedSlug.value || !trimmedVersion.value) {\n return\n }\n\n loader.start()\n\n emit('submit', {\n input: {\n namespace: String(namespace),\n slug: trimmedSlug.value,\n version: trimmedVersion.value,\n },\n done: (outcome) => {\n if (outcome.ok) {\n void loader.clear()\n state.hide()\n return\n }\n errorMessage.value = outcome.message\n void loader.invalidate()\n },\n })\n}\n</script>\n\n<template>\n <ScalarModal\n size=\"sm\"\n :state=\"state\"\n title=\"Publish to registry\"\n variant=\"form\">\n <form\n class=\"flex flex-col gap-4\"\n @submit.prevent=\"handleSubmit\">\n <!--\n Namespace picker.\n\n All three states (single namespace, multiple namespaces, loading)\n render into a bordered field of the same height as the inputs\n below so the modal reads as one cohesive form. A leading\n `Buildings` icon anchors the namespace context visually so the\n single-value state does not look like loose plain text.\n\n - One namespace -> read-only field with the namespace label and\n a \"fixed\" hint, so the user understands there is nothing to\n pick without staring at a one-item dropdown.\n - Multiple namespaces -> dropdown listbox.\n - Loading -> the same field shape with a skeleton-style label\n so layout does not shift once the list arrives.\n -->\n <div class=\"flex flex-col gap-1.5\">\n <label class=\"text-c-1 text-xs font-medium\">Namespace</label>\n <template v-if=\"isNamespacesLoading && namespaceOptions.length === 0\">\n <div\n class=\"border-border bg-b-2 flex h-8 items-center gap-2 rounded border px-3 text-sm\">\n <ScalarIconBuildings\n class=\"text-c-3 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"text-c-3\">Loading namespaces…</span>\n </div>\n </template>\n <template v-else-if=\"hasMultipleNamespaces\">\n <ScalarListbox\n v-model=\"selectedNamespace\"\n :options=\"namespaceOptions\"\n teleport>\n <ScalarButton\n class=\"border-border text-c-1 hover:bg-b-2 flex h-8 w-full items-center justify-between gap-2 rounded border px-3 font-normal\"\n fullWidth\n type=\"button\"\n variant=\"outlined\">\n <span class=\"flex min-w-0 items-center gap-2\">\n <ScalarIconBuildings\n class=\"text-c-2 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"truncate\">{{\n selectedNamespace?.label ?? 'Select a namespace'\n }}</span>\n </span>\n <ScalarIconCaretDown\n class=\"text-c-2 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n </ScalarListbox>\n </template>\n <template v-else>\n <div\n class=\"border-border bg-b-2 text-c-1 flex h-8 items-center gap-2 rounded border px-3 text-sm\"\n :title=\"\n selectedNamespace\n ? `Publishing to ${selectedNamespace.label}`\n : undefined\n \">\n <ScalarIconBuildings\n class=\"text-c-2 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"truncate\">\n {{ selectedNamespace?.label ?? '—' }}\n </span>\n </div>\n </template>\n </div>\n\n <!-- Slug input. Pre-filled with a slugified version of the document title. -->\n <div class=\"flex flex-col gap-1.5\">\n <label\n class=\"text-c-1 text-xs font-medium\"\n for=\"publish-document-slug\">\n Slug\n </label>\n <input\n id=\"publish-document-slug\"\n v-model=\"slug\"\n autocomplete=\"off\"\n class=\"border-border bg-b-1 text-c-1 placeholder:text-c-3 focus:border-c-accent h-8 rounded border px-3 text-sm outline-none\"\n placeholder=\"pets-api\"\n type=\"text\" />\n </div>\n\n <!--\n Version input. The string lives on `info.version` after a\n successful publish so the local document and the registry stay\n in sync without an extra edit.\n -->\n <div class=\"flex flex-col gap-1.5\">\n <label\n class=\"text-c-1 text-xs font-medium\"\n for=\"publish-document-version\">\n Version\n </label>\n <input\n id=\"publish-document-version\"\n v-model=\"version\"\n autocomplete=\"off\"\n class=\"border-border bg-b-1 text-c-1 placeholder:text-c-3 focus:border-c-accent h-8 rounded border px-3 text-sm outline-none\"\n placeholder=\"1.0.0\"\n type=\"text\" />\n </div>\n\n <!--\n Live preview of the published identity. Reading the modal\n top-down the user types a slug + version and sees the resulting\n coordinate update in place, which is more reassuring than\n decoding three separate fields. Hidden until enough fields\n resolve so we never render half a path.\n -->\n <div\n v-if=\"publishPreview\"\n class=\"text-c-2 -mt-1 text-xs\">\n Publishing as\n <span class=\"text-c-1 font-mono\">{{ publishPreview }}</span>\n </div>\n\n <!--\n Inline error region. Stays mounted when present so the user can\n read the message while they are correcting the form rather than\n having it disappear after a toast timeout.\n -->\n <p\n v-if=\"errorMessage\"\n class=\"text-red bg-b-2 rounded p-2 text-xs\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-1\">\n <ScalarButton\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"state.hide()\">\n Cancel\n </ScalarButton>\n <ScalarButton\n :disabled=\"isSubmitDisabled\"\n :loader=\"loader\"\n size=\"sm\"\n type=\"submit\">\n Publish\n </ScalarButton>\n </div>\n </form>\n </ScalarModal>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, ref, toDisplayString, unref, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
|
|
2
|
+
import { ScalarButton, ScalarListbox, ScalarModal, useLoadingState } from "@scalar/components";
|
|
3
|
+
import { ScalarIconBuildings, ScalarIconCaretDown } from "@scalar/icons";
|
|
4
|
+
import { slugify } from "@scalar/helpers/string/slugify";
|
|
5
|
+
//#region src/v2/features/app/components/PublishDocumentModal.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
+
var _hoisted_1 = { class: "flex flex-col gap-1.5" };
|
|
7
|
+
var _hoisted_2 = {
|
|
8
|
+
key: 0,
|
|
9
|
+
class: "border-border bg-b-2 flex h-8 items-center gap-2 rounded border px-3 text-sm"
|
|
10
|
+
};
|
|
11
|
+
var _hoisted_3 = { class: "flex min-w-0 items-center gap-2" };
|
|
12
|
+
var _hoisted_4 = { class: "truncate" };
|
|
13
|
+
var _hoisted_5 = ["title"];
|
|
14
|
+
var _hoisted_6 = { class: "truncate" };
|
|
15
|
+
var _hoisted_7 = { class: "flex flex-col gap-1.5" };
|
|
16
|
+
var _hoisted_8 = { class: "flex flex-col gap-1.5" };
|
|
17
|
+
var _hoisted_9 = {
|
|
18
|
+
key: 0,
|
|
19
|
+
class: "text-c-2 -mt-1 text-xs"
|
|
20
|
+
};
|
|
21
|
+
var _hoisted_10 = { class: "text-c-1 font-mono" };
|
|
22
|
+
var _hoisted_11 = {
|
|
23
|
+
key: 1,
|
|
24
|
+
class: "text-red bg-b-2 rounded p-2 text-xs",
|
|
25
|
+
role: "alert"
|
|
26
|
+
};
|
|
27
|
+
var _hoisted_12 = { class: "flex justify-end gap-2 pt-1" };
|
|
28
|
+
var PublishDocumentModal_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
29
|
+
__name: "PublishDocumentModal",
|
|
30
|
+
props: {
|
|
31
|
+
state: {},
|
|
32
|
+
namespaces: {},
|
|
33
|
+
defaultSlug: { default: "" },
|
|
34
|
+
defaultVersion: { default: "1.0.0" }
|
|
35
|
+
},
|
|
36
|
+
emits: ["submit"],
|
|
37
|
+
setup(__props, { emit: __emit }) {
|
|
38
|
+
const emit = __emit;
|
|
39
|
+
const namespaceOptions = computed(() => (__props.namespaces.status === "success" ? __props.namespaces.namespaces : __props.namespaces.namespaces ?? []).map((entry) => ({
|
|
40
|
+
id: entry.namespace,
|
|
41
|
+
label: entry.title?.trim() || entry.namespace
|
|
42
|
+
})));
|
|
43
|
+
const isNamespacesLoading = computed(() => __props.namespaces.status === "loading");
|
|
44
|
+
/**
|
|
45
|
+
* The picker component is only rendered when there is more than one
|
|
46
|
+
* namespace to choose between. Single-namespace setups read as a static
|
|
47
|
+
* label so the user does not have to interact with a one-item dropdown.
|
|
48
|
+
*/
|
|
49
|
+
const hasMultipleNamespaces = computed(() => namespaceOptions.value.length > 1);
|
|
50
|
+
const selectedNamespace = ref(void 0);
|
|
51
|
+
const slug = ref("");
|
|
52
|
+
const version = ref("");
|
|
53
|
+
const errorMessage = ref(null);
|
|
54
|
+
const loader = useLoadingState();
|
|
55
|
+
/**
|
|
56
|
+
* Resets the form whenever the modal opens. Keeping this scoped to the
|
|
57
|
+
* `open` flag (and not to every prop change) preserves user input
|
|
58
|
+
* across reactive updates while the modal is already on screen.
|
|
59
|
+
*/
|
|
60
|
+
watch(() => __props.state.open, (isOpen) => {
|
|
61
|
+
if (!isOpen) return;
|
|
62
|
+
slug.value = slugify(__props.defaultSlug);
|
|
63
|
+
version.value = __props.defaultVersion;
|
|
64
|
+
errorMessage.value = null;
|
|
65
|
+
selectedNamespace.value = namespaceOptions.value[0];
|
|
66
|
+
loader.clear();
|
|
67
|
+
});
|
|
68
|
+
/** Keep the listbox selection in sync when the namespace list arrives. */
|
|
69
|
+
watch(namespaceOptions, (options) => {
|
|
70
|
+
if (selectedNamespace.value) return;
|
|
71
|
+
selectedNamespace.value = options[0];
|
|
72
|
+
});
|
|
73
|
+
const trimmedSlug = computed(() => slug.value.trim());
|
|
74
|
+
const trimmedVersion = computed(() => version.value.trim());
|
|
75
|
+
/**
|
|
76
|
+
* Human-friendly preview of the resulting registry coordinate
|
|
77
|
+
* (`namespace/slug@version`). Returns `null` while any of the three
|
|
78
|
+
* pieces is still missing so we never show a half-rendered path.
|
|
79
|
+
* Mirrors the same fields submitted to the publish call so the user
|
|
80
|
+
* sees exactly what will land on the registry.
|
|
81
|
+
*/
|
|
82
|
+
const publishPreview = computed(() => {
|
|
83
|
+
const namespace = selectedNamespace.value?.id;
|
|
84
|
+
if (!namespace || !trimmedSlug.value || !trimmedVersion.value) return null;
|
|
85
|
+
return `${namespace}/${trimmedSlug.value}@${trimmedVersion.value}`;
|
|
86
|
+
});
|
|
87
|
+
const isSubmitDisabled = computed(() => !selectedNamespace.value || trimmedSlug.value.length === 0 || trimmedVersion.value.length === 0 || loader.isLoading);
|
|
88
|
+
const handleSubmit = () => {
|
|
89
|
+
errorMessage.value = null;
|
|
90
|
+
const namespace = selectedNamespace.value?.id;
|
|
91
|
+
if (!namespace || !trimmedSlug.value || !trimmedVersion.value) return;
|
|
92
|
+
loader.start();
|
|
93
|
+
emit("submit", {
|
|
94
|
+
input: {
|
|
95
|
+
namespace: String(namespace),
|
|
96
|
+
slug: trimmedSlug.value,
|
|
97
|
+
version: trimmedVersion.value
|
|
98
|
+
},
|
|
99
|
+
done: (outcome) => {
|
|
100
|
+
if (outcome.ok) {
|
|
101
|
+
loader.clear();
|
|
102
|
+
__props.state.hide();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
errorMessage.value = outcome.message;
|
|
106
|
+
loader.invalidate();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
return (_ctx, _cache) => {
|
|
111
|
+
return openBlock(), createBlock(unref(ScalarModal), {
|
|
112
|
+
size: "sm",
|
|
113
|
+
state: __props.state,
|
|
114
|
+
title: "Publish to registry",
|
|
115
|
+
variant: "form"
|
|
116
|
+
}, {
|
|
117
|
+
default: withCtx(() => [createElementVNode("form", {
|
|
118
|
+
class: "flex flex-col gap-4",
|
|
119
|
+
onSubmit: withModifiers(handleSubmit, ["prevent"])
|
|
120
|
+
}, [
|
|
121
|
+
createElementVNode("div", _hoisted_1, [_cache[5] || (_cache[5] = createElementVNode("label", { class: "text-c-1 text-xs font-medium" }, "Namespace", -1)), isNamespacesLoading.value && namespaceOptions.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_2, [createVNode(unref(ScalarIconBuildings), {
|
|
122
|
+
class: "text-c-3 size-3.5 shrink-0",
|
|
123
|
+
size: "sm",
|
|
124
|
+
thickness: "1.5"
|
|
125
|
+
}), _cache[4] || (_cache[4] = createElementVNode("span", { class: "text-c-3" }, "Loading namespaces…", -1))])) : hasMultipleNamespaces.value ? (openBlock(), createBlock(unref(ScalarListbox), {
|
|
126
|
+
key: 1,
|
|
127
|
+
modelValue: selectedNamespace.value,
|
|
128
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedNamespace.value = $event),
|
|
129
|
+
options: namespaceOptions.value,
|
|
130
|
+
teleport: ""
|
|
131
|
+
}, {
|
|
132
|
+
default: withCtx(() => [createVNode(unref(ScalarButton), {
|
|
133
|
+
class: "border-border text-c-1 hover:bg-b-2 flex h-8 w-full items-center justify-between gap-2 rounded border px-3 font-normal",
|
|
134
|
+
fullWidth: "",
|
|
135
|
+
type: "button",
|
|
136
|
+
variant: "outlined"
|
|
137
|
+
}, {
|
|
138
|
+
default: withCtx(() => [createElementVNode("span", _hoisted_3, [createVNode(unref(ScalarIconBuildings), {
|
|
139
|
+
class: "text-c-2 size-3.5 shrink-0",
|
|
140
|
+
size: "sm",
|
|
141
|
+
thickness: "1.5"
|
|
142
|
+
}), createElementVNode("span", _hoisted_4, toDisplayString(selectedNamespace.value?.label ?? "Select a namespace"), 1)]), createVNode(unref(ScalarIconCaretDown), {
|
|
143
|
+
class: "text-c-2 size-3.5 shrink-0",
|
|
144
|
+
size: "sm",
|
|
145
|
+
thickness: "1.5"
|
|
146
|
+
})]),
|
|
147
|
+
_: 1
|
|
148
|
+
})]),
|
|
149
|
+
_: 1
|
|
150
|
+
}, 8, ["modelValue", "options"])) : (openBlock(), createElementBlock("div", {
|
|
151
|
+
key: 2,
|
|
152
|
+
class: "border-border bg-b-2 text-c-1 flex h-8 items-center gap-2 rounded border px-3 text-sm",
|
|
153
|
+
title: selectedNamespace.value ? `Publishing to ${selectedNamespace.value.label}` : void 0
|
|
154
|
+
}, [createVNode(unref(ScalarIconBuildings), {
|
|
155
|
+
class: "text-c-2 size-3.5 shrink-0",
|
|
156
|
+
size: "sm",
|
|
157
|
+
thickness: "1.5"
|
|
158
|
+
}), createElementVNode("span", _hoisted_6, toDisplayString(selectedNamespace.value?.label ?? "—"), 1)], 8, _hoisted_5))]),
|
|
159
|
+
createElementVNode("div", _hoisted_7, [_cache[6] || (_cache[6] = createElementVNode("label", {
|
|
160
|
+
class: "text-c-1 text-xs font-medium",
|
|
161
|
+
for: "publish-document-slug"
|
|
162
|
+
}, " Slug ", -1)), withDirectives(createElementVNode("input", {
|
|
163
|
+
id: "publish-document-slug",
|
|
164
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => slug.value = $event),
|
|
165
|
+
autocomplete: "off",
|
|
166
|
+
class: "border-border bg-b-1 text-c-1 placeholder:text-c-3 focus:border-c-accent h-8 rounded border px-3 text-sm outline-none",
|
|
167
|
+
placeholder: "pets-api",
|
|
168
|
+
type: "text"
|
|
169
|
+
}, null, 512), [[vModelText, slug.value]])]),
|
|
170
|
+
createElementVNode("div", _hoisted_8, [_cache[7] || (_cache[7] = createElementVNode("label", {
|
|
171
|
+
class: "text-c-1 text-xs font-medium",
|
|
172
|
+
for: "publish-document-version"
|
|
173
|
+
}, " Version ", -1)), withDirectives(createElementVNode("input", {
|
|
174
|
+
id: "publish-document-version",
|
|
175
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => version.value = $event),
|
|
176
|
+
autocomplete: "off",
|
|
177
|
+
class: "border-border bg-b-1 text-c-1 placeholder:text-c-3 focus:border-c-accent h-8 rounded border px-3 text-sm outline-none",
|
|
178
|
+
placeholder: "1.0.0",
|
|
179
|
+
type: "text"
|
|
180
|
+
}, null, 512), [[vModelText, version.value]])]),
|
|
181
|
+
publishPreview.value ? (openBlock(), createElementBlock("div", _hoisted_9, [_cache[8] || (_cache[8] = createTextVNode(" Publishing as ", -1)), createElementVNode("span", _hoisted_10, toDisplayString(publishPreview.value), 1)])) : createCommentVNode("", true),
|
|
182
|
+
errorMessage.value ? (openBlock(), createElementBlock("p", _hoisted_11, toDisplayString(errorMessage.value), 1)) : createCommentVNode("", true),
|
|
183
|
+
createElementVNode("div", _hoisted_12, [createVNode(unref(ScalarButton), {
|
|
184
|
+
size: "sm",
|
|
185
|
+
type: "button",
|
|
186
|
+
variant: "ghost",
|
|
187
|
+
onClick: _cache[3] || (_cache[3] = ($event) => __props.state.hide())
|
|
188
|
+
}, {
|
|
189
|
+
default: withCtx(() => [..._cache[9] || (_cache[9] = [createTextVNode(" Cancel ", -1)])]),
|
|
190
|
+
_: 1
|
|
191
|
+
}), createVNode(unref(ScalarButton), {
|
|
192
|
+
disabled: isSubmitDisabled.value,
|
|
193
|
+
loader: unref(loader),
|
|
194
|
+
size: "sm",
|
|
195
|
+
type: "submit"
|
|
196
|
+
}, {
|
|
197
|
+
default: withCtx(() => [..._cache[10] || (_cache[10] = [createTextVNode(" Publish ", -1)])]),
|
|
198
|
+
_: 1
|
|
199
|
+
}, 8, ["disabled", "loader"])])
|
|
200
|
+
], 32)]),
|
|
201
|
+
_: 1
|
|
202
|
+
}, 8, ["state"]);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
//#endregion
|
|
207
|
+
export { PublishDocumentModal_vue_vue_type_script_setup_true_lang_default as default };
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=PublishDocumentModal.vue.script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublishDocumentModal.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/PublishDocumentModal.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Modal that walks the user through publishing a brand-new document to\n * the registry.\n *\n * The component is purely presentational: it owns the form state, the\n * loading / error indicator and the validation rules, and delegates the\n * actual publish call to the parent through `submit`. The parent\n * resolves `submit` with `{ ok: true }` to close the modal or\n * `{ ok: false, message }` to surface the error inline so the user can\n * fix the input and retry without losing what they typed.\n */\nexport default {}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarListbox,\n ScalarModal,\n useLoadingState,\n type ModalState,\n type ScalarListboxOption,\n} from '@scalar/components'\nimport { slugify } from '@scalar/helpers/string/slugify'\nimport { ScalarIconBuildings, ScalarIconCaretDown } from '@scalar/icons'\nimport { computed, ref, watch } from 'vue'\n\nimport type { RegistryNamespacesState } from '@/v2/types/configuration'\n\nconst {\n state,\n namespaces,\n defaultSlug = '',\n defaultVersion = '1.0.0',\n} = defineProps<{\n /** Modal control returned by `useModal()`. */\n state: ModalState\n /** Namespaces the user can publish into, with loading status. */\n namespaces: RegistryNamespacesState\n /**\n * Initial slug to seed the slug input with. The parent typically\n * computes this from the active document's title. Falls back to an\n * empty string so the placeholder is visible.\n */\n defaultSlug?: string\n /**\n * Initial version. Defaults to `1.0.0` when the active document does\n * not advertise an `info.version` yet.\n */\n defaultVersion?: string\n}>()\n\nconst emit = defineEmits<{\n /**\n * Fired when the user submits a valid form. The parent must resolve\n * the returned promise with either `{ ok: true }` (modal closes) or\n * `{ ok: false, message }` (message is rendered inline so the user\n * can retry).\n */\n (\n event: 'submit',\n payload: {\n input: { namespace: string; slug: string; version: string }\n done: (outcome: { ok: true } | { ok: false; message: string }) => void\n },\n ): void\n}>()\n\nconst namespaceOptions = computed<ScalarListboxOption[]>(() =>\n (namespaces.status === 'success'\n ? namespaces.namespaces\n : (namespaces.namespaces ?? [])\n ).map((entry) => ({\n id: entry.namespace,\n label: entry.title?.trim() || entry.namespace,\n })),\n)\n\nconst isNamespacesLoading = computed(() => namespaces.status === 'loading')\n\n/**\n * The picker component is only rendered when there is more than one\n * namespace to choose between. Single-namespace setups read as a static\n * label so the user does not have to interact with a one-item dropdown.\n */\nconst hasMultipleNamespaces = computed(() => namespaceOptions.value.length > 1)\n\nconst selectedNamespace = ref<ScalarListboxOption | undefined>(undefined)\nconst slug = ref('')\nconst version = ref('')\nconst errorMessage = ref<string | null>(null)\nconst loader = useLoadingState()\n\n/**\n * Resets the form whenever the modal opens. Keeping this scoped to the\n * `open` flag (and not to every prop change) preserves user input\n * across reactive updates while the modal is already on screen.\n */\nwatch(\n () => state.open,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n slug.value = slugify(defaultSlug)\n version.value = defaultVersion\n errorMessage.value = null\n selectedNamespace.value = namespaceOptions.value[0]\n void loader.clear()\n },\n)\n\n/** Keep the listbox selection in sync when the namespace list arrives. */\nwatch(namespaceOptions, (options) => {\n if (selectedNamespace.value) {\n return\n }\n selectedNamespace.value = options[0]\n})\n\nconst trimmedSlug = computed(() => slug.value.trim())\nconst trimmedVersion = computed(() => version.value.trim())\n\n/**\n * Human-friendly preview of the resulting registry coordinate\n * (`namespace/slug@version`). Returns `null` while any of the three\n * pieces is still missing so we never show a half-rendered path.\n * Mirrors the same fields submitted to the publish call so the user\n * sees exactly what will land on the registry.\n */\nconst publishPreview = computed<string | null>(() => {\n const namespace = selectedNamespace.value?.id\n if (!namespace || !trimmedSlug.value || !trimmedVersion.value) {\n return null\n }\n return `${namespace}/${trimmedSlug.value}@${trimmedVersion.value}`\n})\n\nconst isSubmitDisabled = computed(\n () =>\n !selectedNamespace.value ||\n trimmedSlug.value.length === 0 ||\n trimmedVersion.value.length === 0 ||\n loader.isLoading,\n)\n\nconst handleSubmit = (): void => {\n errorMessage.value = null\n\n const namespace = selectedNamespace.value?.id\n if (!namespace || !trimmedSlug.value || !trimmedVersion.value) {\n return\n }\n\n loader.start()\n\n emit('submit', {\n input: {\n namespace: String(namespace),\n slug: trimmedSlug.value,\n version: trimmedVersion.value,\n },\n done: (outcome) => {\n if (outcome.ok) {\n void loader.clear()\n state.hide()\n return\n }\n errorMessage.value = outcome.message\n void loader.invalidate()\n },\n })\n}\n</script>\n\n<template>\n <ScalarModal\n size=\"sm\"\n :state=\"state\"\n title=\"Publish to registry\"\n variant=\"form\">\n <form\n class=\"flex flex-col gap-4\"\n @submit.prevent=\"handleSubmit\">\n <!--\n Namespace picker.\n\n All three states (single namespace, multiple namespaces, loading)\n render into a bordered field of the same height as the inputs\n below so the modal reads as one cohesive form. A leading\n `Buildings` icon anchors the namespace context visually so the\n single-value state does not look like loose plain text.\n\n - One namespace -> read-only field with the namespace label and\n a \"fixed\" hint, so the user understands there is nothing to\n pick without staring at a one-item dropdown.\n - Multiple namespaces -> dropdown listbox.\n - Loading -> the same field shape with a skeleton-style label\n so layout does not shift once the list arrives.\n -->\n <div class=\"flex flex-col gap-1.5\">\n <label class=\"text-c-1 text-xs font-medium\">Namespace</label>\n <template v-if=\"isNamespacesLoading && namespaceOptions.length === 0\">\n <div\n class=\"border-border bg-b-2 flex h-8 items-center gap-2 rounded border px-3 text-sm\">\n <ScalarIconBuildings\n class=\"text-c-3 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"text-c-3\">Loading namespaces…</span>\n </div>\n </template>\n <template v-else-if=\"hasMultipleNamespaces\">\n <ScalarListbox\n v-model=\"selectedNamespace\"\n :options=\"namespaceOptions\"\n teleport>\n <ScalarButton\n class=\"border-border text-c-1 hover:bg-b-2 flex h-8 w-full items-center justify-between gap-2 rounded border px-3 font-normal\"\n fullWidth\n type=\"button\"\n variant=\"outlined\">\n <span class=\"flex min-w-0 items-center gap-2\">\n <ScalarIconBuildings\n class=\"text-c-2 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"truncate\">{{\n selectedNamespace?.label ?? 'Select a namespace'\n }}</span>\n </span>\n <ScalarIconCaretDown\n class=\"text-c-2 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n </ScalarListbox>\n </template>\n <template v-else>\n <div\n class=\"border-border bg-b-2 text-c-1 flex h-8 items-center gap-2 rounded border px-3 text-sm\"\n :title=\"\n selectedNamespace\n ? `Publishing to ${selectedNamespace.label}`\n : undefined\n \">\n <ScalarIconBuildings\n class=\"text-c-2 size-3.5 shrink-0\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"truncate\">\n {{ selectedNamespace?.label ?? '—' }}\n </span>\n </div>\n </template>\n </div>\n\n <!-- Slug input. Pre-filled with a slugified version of the document title. -->\n <div class=\"flex flex-col gap-1.5\">\n <label\n class=\"text-c-1 text-xs font-medium\"\n for=\"publish-document-slug\">\n Slug\n </label>\n <input\n id=\"publish-document-slug\"\n v-model=\"slug\"\n autocomplete=\"off\"\n class=\"border-border bg-b-1 text-c-1 placeholder:text-c-3 focus:border-c-accent h-8 rounded border px-3 text-sm outline-none\"\n placeholder=\"pets-api\"\n type=\"text\" />\n </div>\n\n <!--\n Version input. The string lives on `info.version` after a\n successful publish so the local document and the registry stay\n in sync without an extra edit.\n -->\n <div class=\"flex flex-col gap-1.5\">\n <label\n class=\"text-c-1 text-xs font-medium\"\n for=\"publish-document-version\">\n Version\n </label>\n <input\n id=\"publish-document-version\"\n v-model=\"version\"\n autocomplete=\"off\"\n class=\"border-border bg-b-1 text-c-1 placeholder:text-c-3 focus:border-c-accent h-8 rounded border px-3 text-sm outline-none\"\n placeholder=\"1.0.0\"\n type=\"text\" />\n </div>\n\n <!--\n Live preview of the published identity. Reading the modal\n top-down the user types a slug + version and sees the resulting\n coordinate update in place, which is more reassuring than\n decoding three separate fields. Hidden until enough fields\n resolve so we never render half a path.\n -->\n <div\n v-if=\"publishPreview\"\n class=\"text-c-2 -mt-1 text-xs\">\n Publishing as\n <span class=\"text-c-1 font-mono\">{{ publishPreview }}</span>\n </div>\n\n <!--\n Inline error region. Stays mounted when present so the user can\n read the message while they are correcting the form rather than\n having it disappear after a toast timeout.\n -->\n <p\n v-if=\"errorMessage\"\n class=\"text-red bg-b-2 rounded p-2 text-xs\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-1\">\n <ScalarButton\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"state.hide()\">\n Cancel\n </ScalarButton>\n <ScalarButton\n :disabled=\"isSubmitDisabled\"\n :loader=\"loader\"\n size=\"sm\"\n type=\"submit\">\n Publish\n </ScalarButton>\n </div>\n </form>\n </ScalarModal>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncConflictResolutionEditor.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/SyncConflictResolutionEditor.vue"],"names":[],"mappings":"AA0UA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAOpD,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,CAAA;IAChD,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C,CAAC;AA+UF,QAAA,MAAM,YAAY;;0BAxUM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;0BAAvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;kFA2U7C,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_export-helper.js";
|
|
2
2
|
import SyncConflictResolutionEditor_vue_vue_type_script_setup_true_lang_default from "./SyncConflictResolutionEditor.vue.script.js";
|
|
3
3
|
/* empty css */
|
|
4
|
-
//#region src/v2/features/
|
|
5
|
-
var SyncConflictResolutionEditor_default = /* @__PURE__ */ _plugin_vue_export_helper_default(SyncConflictResolutionEditor_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
4
|
+
//#region src/v2/features/app/components/SyncConflictResolutionEditor.vue
|
|
5
|
+
var SyncConflictResolutionEditor_default = /* @__PURE__ */ _plugin_vue_export_helper_default(SyncConflictResolutionEditor_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-65ed5337"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { SyncConflictResolutionEditor_default as default };
|
|
8
8
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncConflictResolutionEditor.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/SyncConflictResolutionEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components'\nimport { type merge } from '@scalar/json-magic/diff'\nimport { useToasts } from '@scalar/use-toasts'\nimport { computed, onMounted, onUnmounted, ref } from 'vue'\n\nimport { useSplitResize } from '@/v2/components/resize'\nimport { useThreeWayMergeEditor } from '@/v2/features/editor/hooks/use-three-way-merge-editor'\n\nconst { conflicts, baseDocument, resolvedDocument } = defineProps<{\n conflicts: ReturnType<typeof merge>['conflicts']\n baseDocument: Record<string, unknown>\n resolvedDocument: Record<string, unknown>\n}>()\n\nconst emit = defineEmits<{\n applyChanges: [\n payload: {\n resolvedDocument: Record<string, unknown>\n },\n ]\n}>()\n\nconst { toast } = useToasts()\n\nconst splitContainerRef = ref<HTMLDivElement>()\nconst topEditorsRowRef = ref<HTMLDivElement>()\nconst topPaneSize = ref(50)\nconst leftPaneSize = ref(50)\n\nconst { onHorizontalResizeStart, onVerticalResizeStart, stopActiveResize } =\n useSplitResize({\n horizontalContainerRef: topEditorsRowRef,\n verticalContainerRef: splitContainerRef,\n leftPaneSize,\n topPaneSize,\n horizontalMin: 20,\n horizontalMax: 80,\n verticalMin: 25,\n verticalMax: 75,\n })\n\nconst mergeEditor = useThreeWayMergeEditor({\n baseDocument,\n resolvedDocument,\n conflicts,\n onApplyChanges: (resolvedDoc) =>\n emit('applyChanges', { resolvedDocument: resolvedDoc }),\n onError: (message) => toast(message, 'error'),\n})\n\nconst conflictsLeft = computed(() => mergeEditor.conflictsLeft.value)\n\nconst topPaneStyle = computed(() => ({ height: `${topPaneSize.value}%` }))\nconst leftPaneStyle = computed(() => ({ width: `${leftPaneSize.value}%` }))\nconst rightPaneStyle = computed(() => ({\n width: `${100 - leftPaneSize.value}%`,\n}))\n\nconst localChangesEditorRef = ref<HTMLDivElement>()\nconst remoteChangesEditorRef = ref<HTMLDivElement>()\nconst resultEditorRef = ref<HTMLDivElement>()\n\nonMounted(() => {\n const localEl = localChangesEditorRef.value\n const remoteEl = remoteChangesEditorRef.value\n const resultEl = resultEditorRef.value\n if (localEl && remoteEl && resultEl) {\n mergeEditor.init({\n local: localEl,\n remote: remoteEl,\n result: resultEl,\n })\n }\n})\n\nonUnmounted(() => {\n stopActiveResize()\n mergeEditor.dispose()\n})\n</script>\n\n<template>\n <p class=\"text-c-2 text-xs\">\n Resolve conflicts inline in the full document editor. Use the buttons inside\n the editor for each conflict.\n </p>\n\n <div\n ref=\"splitContainerRef\"\n class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\n <div\n ref=\"topEditorsRowRef\"\n class=\"flex min-h-0 gap-1 p-1\"\n :style=\"topPaneStyle\">\n <div\n class=\"sync-editor-pane border-c-3 flex min-h-0 flex-col overflow-hidden rounded-lg border\"\n :style=\"leftPaneStyle\">\n <div\n class=\"sync-pane-title text-c-2 border-c-3 shrink-0 border-b px-2 py-1 text-[11px]\">\n Current\n </div>\n <div\n ref=\"localChangesEditorRef\"\n class=\"min-h-0 flex-1\"></div>\n </div>\n\n <button\n aria-label=\"Resize current and remote editors\"\n class=\"resize-handle resize-handle-col\"\n type=\"button\"\n @pointerdown=\"onHorizontalResizeStart\" />\n\n <div\n class=\"sync-editor-pane border-c-3 flex min-h-0 flex-col overflow-hidden rounded-lg border\"\n :style=\"rightPaneStyle\">\n <div\n class=\"sync-pane-title text-c-2 border-c-3 shrink-0 border-b px-2 py-1 text-[11px]\">\n Remote\n </div>\n <div\n ref=\"remoteChangesEditorRef\"\n class=\"min-h-0 flex-1\"></div>\n </div>\n </div>\n\n <button\n aria-label=\"Resize top and result editors\"\n class=\"resize-handle resize-handle-row\"\n type=\"button\"\n @pointerdown=\"onVerticalResizeStart\" />\n\n <div\n class=\"sync-editor-pane border-c-3 mx-1 mb-1 flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border\">\n <div\n class=\"sync-pane-title text-c-2 border-c-3 flex shrink-0 items-center justify-between border-b px-2 py-1 text-[11px]\">\n <span> Result </span>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-c-2 text-[11px] normal-case\">\n {{ conflictsLeft }} conflict{{ conflictsLeft === 1 ? '' : 's' }}\n left\n </span>\n <ScalarButton\n :disabled=\"conflictsLeft === 0\"\n size=\"xs\"\n type=\"button\"\n variant=\"outlined\"\n @click=\"mergeEditor.goToNextConflict\">\n Next Conflict\n </ScalarButton>\n </div>\n </div>\n <div\n ref=\"resultEditorRef\"\n class=\"min-h-0 flex-1\"></div>\n </div>\n\n <div class=\"flex shrink-0 items-center justify-end gap-2\">\n <ScalarButton\n :disabled=\"conflictsLeft > 0\"\n size=\"xs\"\n type=\"button\"\n @click=\"mergeEditor.applyResolvedConflicts\">\n Apply changes\n </ScalarButton>\n </div>\n </div>\n</template>\n<style scoped>\n.editor-container {\n width: 100%;\n height: 100%;\n}\n\n.sync-layout-root {\n background: color-mix(\n in srgb,\n var(--scalar-color-background-1, #1e1e1e) 96%,\n transparent\n );\n}\n\n.sync-editor-pane {\n background: color-mix(\n in srgb,\n var(--scalar-color-background-1, #1e1e1e) 95%,\n transparent\n );\n}\n\n.sync-pane-title {\n letter-spacing: 0.03em;\n text-transform: uppercase;\n font-weight: 600;\n background: color-mix(\n in srgb,\n var(--scalar-color-background-2, #2d2d30) 85%,\n transparent\n );\n}\n\n.resize-handle {\n position: relative;\n display: block;\n flex-shrink: 0;\n border: none;\n border-radius: 999px;\n background: transparent;\n transition:\n background-color 0.12s ease,\n box-shadow 0.12s ease;\n}\n\n.resize-handle::before {\n content: '';\n position: absolute;\n border-radius: 999px;\n opacity: 1;\n transition:\n background-color 0.12s ease,\n transform 0.12s ease;\n}\n\n.resize-handle:hover {\n background: color-mix(\n in srgb,\n var(--scalar-color-accent, #007acc) 12%,\n transparent\n );\n}\n\n.resize-handle:active {\n background: color-mix(\n in srgb,\n var(--scalar-color-accent, #007acc) 18%,\n transparent\n );\n}\n\n.resize-handle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 1px\n color-mix(in srgb, var(--scalar-color-accent, #007acc) 70%, transparent);\n}\n\n.resize-handle-col {\n width: 8px;\n min-height: 44px;\n margin: 2px 0;\n cursor: col-resize;\n}\n\n.resize-handle-col::before {\n top: 50%;\n left: 50%;\n width: 1px;\n height: calc(100% - 8px);\n transform: translate(-50%, -50%);\n background: color-mix(\n in srgb,\n var(--scalar-color-border, #3c3c3c) 85%,\n transparent\n );\n}\n\n.resize-handle-row {\n height: 8px;\n margin: 0 4px;\n cursor: row-resize;\n}\n\n.resize-handle-row::before {\n top: 50%;\n left: 50%;\n width: calc(100% - 8px);\n height: 1px;\n transform: translate(-50%, -50%);\n background: color-mix(\n in srgb,\n var(--scalar-color-border, #3c3c3c) 85%,\n transparent\n );\n}\n\n.resize-handle-col:hover::before,\n.resize-handle-col:active::before,\n.resize-handle-row:hover::before,\n.resize-handle-row:active::before {\n background: color-mix(\n in srgb,\n var(--scalar-color-accent, #007acc) 78%,\n transparent\n );\n}\n\n:deep(.json-path-highlight) {\n background-color: rgba(255, 200, 0, 0.35);\n border-radius: 4px;\n}\n\n:deep(.json-focus-highlight-box-single) {\n border: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n box-sizing: border-box;\n}\n\n:deep(.json-focus-highlight-box-top) {\n border-top: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n border-left: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n border-right: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n box-sizing: border-box;\n}\n\n:deep(.json-focus-highlight-box-middle) {\n border-left: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n border-right: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n box-sizing: border-box;\n}\n\n:deep(.json-focus-highlight-box-bottom) {\n border-bottom: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n border-left: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n border-right: 2px solid color-mix(in srgb, #facc15 90%, #eab308 10%);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, #fde047 35%, transparent);\n box-sizing: border-box;\n}\n</style>\n"],"mappings":""}
|
package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.script.js
RENAMED
|
@@ -3,7 +3,7 @@ import { useThreeWayMergeEditor } from "../../editor/hooks/use-three-way-merge-e
|
|
|
3
3
|
import { Fragment, computed, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeStyle, onMounted, onUnmounted, openBlock, ref, toDisplayString, unref, withCtx } from "vue";
|
|
4
4
|
import { ScalarButton } from "@scalar/components";
|
|
5
5
|
import { useToasts } from "@scalar/use-toasts";
|
|
6
|
-
//#region src/v2/features/
|
|
6
|
+
//#region src/v2/features/app/components/SyncConflictResolutionEditor.vue?vue&type=script&setup=true&lang.ts
|
|
7
7
|
var _hoisted_1 = { class: "sync-editor-pane border-c-3 mx-1 mb-1 flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border" };
|
|
8
8
|
var _hoisted_2 = { class: "sync-pane-title text-c-2 border-c-3 flex shrink-0 items-center justify-between border-b px-2 py-1 text-[11px]" };
|
|
9
9
|
var _hoisted_3 = { class: "flex items-center gap-2" };
|