@scalar/api-client 3.3.1 → 3.4.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 +15 -0
- package/dist/monacoeditorwork/yaml.worker.bundle.js +92 -79
- package/dist/style.css +87 -57
- package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts +2 -0
- package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js +5 -0
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.d.ts +4 -0
- package/dist/v2/blocks/operation-block/components/Header.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.js +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.script.js +6 -0
- package/dist/v2/blocks/operation-block/components/Header.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/response-cache.d.ts +2 -1
- package/dist/v2/blocks/operation-block/helpers/response-cache.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/response-cache.js +9 -2
- package/dist/v2/blocks/operation-block/helpers/response-cache.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTable.vue.d.ts +2 -2
- package/dist/v2/blocks/request-block/components/RequestTableRow.vue.d.ts +2 -2
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.d.ts +4 -0
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.js +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.script.js +84 -71
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/helpers/is-placeholder-path.d.ts +13 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/is-placeholder-path.d.ts.map +1 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/is-placeholder-path.js +28 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/is-placeholder-path.js.map +1 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/refocus-blur-target.d.ts +16 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/refocus-blur-target.d.ts.map +1 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/refocus-blur-target.js +28 -0
- package/dist/v2/blocks/scalar-address-bar-block/helpers/refocus-blur-target.js.map +1 -0
- package/dist/v2/blocks/scalar-address-bar-block/hooks/use-path-masking.d.ts +31 -0
- package/dist/v2/blocks/scalar-address-bar-block/hooks/use-path-masking.d.ts.map +1 -0
- package/dist/v2/blocks/scalar-address-bar-block/hooks/use-path-masking.js +18 -0
- package/dist/v2/blocks/scalar-address-bar-block/hooks/use-path-masking.js.map +1 -0
- package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.js +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.js.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.script.js +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/app/App.vue.d.ts +25 -1
- 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 +54 -39
- package/dist/v2/features/app/App.vue.script.js.map +1 -1
- package/dist/v2/features/app/app-events.js +4 -4
- package/dist/v2/features/app/app-events.js.map +1 -1
- package/dist/v2/features/app/app-state.d.ts +20 -14
- package/dist/v2/features/app/app-state.d.ts.map +1 -1
- package/dist/v2/features/app/app-state.js +89 -55
- package/dist/v2/features/app/app-state.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.d.ts +26 -3
- 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 +15 -6
- package/dist/v2/features/app/components/AppHeader.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts +2 -2
- 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 +86 -108
- package/dist/v2/features/app/components/AppSidebar.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/CreateVersionModal.vue.d.ts +28 -0
- package/dist/v2/features/app/components/CreateVersionModal.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/CreateVersionModal.vue.js +7 -0
- package/dist/v2/features/app/components/CreateVersionModal.vue.js.map +1 -0
- package/dist/v2/features/app/components/CreateVersionModal.vue.script.js +84 -0
- package/dist/v2/features/app/components/CreateVersionModal.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts +26 -0
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js +9 -0
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js.map +1 -0
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js +376 -0
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.d.ts +16 -0
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.js +7 -0
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.js.map +1 -0
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.script.js +51 -0
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/SidebarDocument.vue.d.ts +45 -0
- package/dist/v2/features/app/components/SidebarDocument.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/SidebarDocument.vue.js +7 -0
- package/dist/v2/features/app/components/SidebarDocument.vue.js.map +1 -0
- package/dist/v2/features/app/components/SidebarDocument.vue.script.js +137 -0
- package/dist/v2/features/app/components/SidebarDocument.vue.script.js.map +1 -0
- package/dist/v2/features/app/helpers/check-version-conflict.d.ts +51 -0
- package/dist/v2/features/app/helpers/check-version-conflict.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/check-version-conflict.js +79 -0
- package/dist/v2/features/app/helpers/check-version-conflict.js.map +1 -0
- package/dist/v2/features/app/helpers/compute-version-status.d.ts +45 -0
- package/dist/v2/features/app/helpers/compute-version-status.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/compute-version-status.js +18 -0
- package/dist/v2/features/app/helpers/compute-version-status.js.map +1 -0
- package/dist/v2/features/app/helpers/create-draft-registry-document.d.ts +39 -0
- package/dist/v2/features/app/helpers/create-draft-registry-document.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/create-draft-registry-document.js +64 -0
- package/dist/v2/features/app/helpers/create-draft-registry-document.js.map +1 -0
- package/dist/v2/features/app/helpers/create-temp-operation.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/create-temp-operation.js +5 -8
- package/dist/v2/features/app/helpers/create-temp-operation.js.map +1 -1
- package/dist/v2/features/app/helpers/detect-document-conflicts.d.ts +26 -0
- package/dist/v2/features/app/helpers/detect-document-conflicts.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/detect-document-conflicts.js +27 -0
- package/dist/v2/features/app/helpers/detect-document-conflicts.js.map +1 -0
- package/dist/v2/features/app/helpers/filter-workspaces.d.ts +14 -14
- package/dist/v2/features/app/helpers/filter-workspaces.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/filter-workspaces.js +15 -15
- package/dist/v2/features/app/helpers/filter-workspaces.js.map +1 -1
- package/dist/v2/features/app/helpers/group-workspaces.d.ts +23 -3
- package/dist/v2/features/app/helpers/group-workspaces.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/group-workspaces.js +22 -7
- package/dist/v2/features/app/helpers/group-workspaces.js.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.d.ts +16 -1
- package/dist/v2/features/app/helpers/load-registry-document.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.js +7 -6
- package/dist/v2/features/app/helpers/load-registry-document.js.map +1 -1
- package/dist/v2/features/app/helpers/routes.d.ts +5 -1
- package/dist/v2/features/app/helpers/routes.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/routes.js +1 -1
- package/dist/v2/features/app/helpers/routes.js.map +1 -1
- package/dist/v2/features/app/helpers/version-status-presentation.d.ts +24 -0
- package/dist/v2/features/app/helpers/version-status-presentation.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/version-status-presentation.js +43 -0
- package/dist/v2/features/app/helpers/version-status-presentation.js.map +1 -0
- package/dist/v2/features/app/hooks/use-active-document-version.d.ts +41 -0
- package/dist/v2/features/app/hooks/use-active-document-version.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-active-document-version.js +60 -0
- package/dist/v2/features/app/hooks/use-active-document-version.js.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts +71 -23
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts.map +1 -1
- package/dist/v2/features/app/hooks/use-sidebar-documents.js +167 -45
- package/dist/v2/features/app/hooks/use-sidebar-documents.js.map +1 -1
- package/dist/v2/features/app/hooks/use-version-conflict-check.d.ts +35 -0
- package/dist/v2/features/app/hooks/use-version-conflict-check.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-version-conflict-check.js +62 -0
- package/dist/v2/features/app/hooks/use-version-conflict-check.js.map +1 -0
- 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 +6 -1
- package/dist/v2/features/collection/DocumentCollection.vue.script.js.map +1 -1
- package/dist/v2/features/collection/OperationCollection.vue.script.js +1 -0
- package/dist/v2/features/collection/OperationCollection.vue.script.js.map +1 -1
- package/dist/v2/features/collection/WorkspaceCollection.vue.script.js +1 -0
- package/dist/v2/features/collection/WorkspaceCollection.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Authentication.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Authentication.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Cookies.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Cookies.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Editor/Editor.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Editor/Editor.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Environment.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Environment.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/GetStarted.vue.d.ts +12 -4
- package/dist/v2/features/collection/components/GetStarted.vue.d.ts.map +1 -1
- package/dist/v2/features/collection/components/GetStarted.vue.js.map +1 -1
- package/dist/v2/features/collection/components/GetStarted.vue.script.js +56 -13
- package/dist/v2/features/collection/components/GetStarted.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Overview.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Overview.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Runner/components/Runner.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Runner/components/Runner.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Runner/hooks/use-runner-execution.js +2 -2
- package/dist/v2/features/collection/components/Runner/hooks/use-runner-execution.js.map +1 -1
- package/dist/v2/features/collection/components/Scripts.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Scripts.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Servers.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Servers.vue.script.js.map +1 -1
- package/dist/v2/features/collection/components/Settings.vue.script.js +1 -0
- package/dist/v2/features/collection/components/Settings.vue.script.js.map +1 -1
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.js +1 -1
- package/dist/v2/features/operation/Operation.vue.d.ts.map +1 -1
- package/dist/v2/features/operation/Operation.vue.js.map +1 -1
- package/dist/v2/features/operation/Operation.vue.script.js +3 -0
- package/dist/v2/features/operation/Operation.vue.script.js.map +1 -1
- package/dist/v2/helpers/safe-run.d.ts +25 -1
- package/dist/v2/helpers/safe-run.d.ts.map +1 -1
- package/dist/v2/helpers/safe-run.js +26 -2
- package/dist/v2/helpers/safe-run.js.map +1 -1
- package/package.json +12 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppSidebar.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AppState } from '@scalar/api-client/v2/features/app'\nimport {\n ScalarIconButton,\n ScalarModal,\n ScalarSidebar,\n ScalarSidebarButton,\n ScalarSidebarItem,\n ScalarSidebarItems,\n ScalarSidebarNestedItems,\n ScalarSidebarSearchInput,\n ScalarSidebarSection,\n useModal,\n} from '@scalar/components'\nimport {\n ScalarIconCaretLeft,\n ScalarIconDotsThree,\n ScalarIconFolderDashed,\n ScalarIconFunnel,\n ScalarIconGearSix,\n ScalarIconMagnifyingGlass,\n ScalarIconPlus,\n} from '@scalar/icons'\nimport {\n filterItems,\n SidebarItem,\n type DraggingItem,\n type HoveredItem,\n} from '@scalar/sidebar'\nimport { useToasts } from '@scalar/use-toasts'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, onBeforeMount, onBeforeUnmount, ref } from 'vue'\n\nimport DeleteSidebarListElement from '@/components/Sidebar/Actions/DeleteSidebarListElement.vue'\nimport { Resize } from '@/v2/components/resize'\nimport SidebarItemMenu from '@/v2/features/app/components/SidebarItemMenu.vue'\nimport { createTempOperation } from '@/v2/features/app/helpers/create-temp-operation'\nimport { loadRegistryDocument } from '@/v2/features/app/helpers/load-registry-document'\nimport { useDocumentFilter } from '@/v2/features/app/hooks/use-document-filter'\nimport { useSidebarContextMenu } from '@/v2/features/app/hooks/use-sidebar-context-menu'\nimport {\n useSidebarDocuments,\n type RegistryDocumentsState,\n type SidebarDocumentItem,\n} from '@/v2/features/app/hooks/use-sidebar-documents'\nimport { DocumentSearchModal } from '@/v2/features/search'\nimport { dragHandleFactory } from '@/v2/helpers/drag-handle-factory'\nimport type { ImportDocumentFromRegistry } from '@/v2/types/configuration'\n\nconst {\n app,\n indent = 20,\n registryDocuments = { status: 'success', documents: [] },\n fetchRegistryDocument,\n} = defineProps<{\n /** The app state from @scalar/api-client. */\n app: AppState\n /** Horizontal indent applied to nested sidebar items, in pixels. */\n indent?: number\n /**\n * The list of all available registry documents, wrapped in a loading state\n * so the sidebar can render skeleton placeholders while the registry is\n * still being fetched.\n */\n registryDocuments?: RegistryDocumentsState\n /** A function to fetch a registry document */\n fetchRegistryDocument?: ImportDocumentFromRegistry\n}>()\n\nconst { toast } = useToasts()\n\n/**\n * Whether the caller is still fetching the list of registry documents. We\n * only surface the loading state on team workspaces because local workspaces\n * never consult the registry\n */\nconst isLoadingRegistry = computed(\n () =>\n registryDocuments.status === 'loading' &&\n app.workspace.isTeamWorkspace.value,\n)\n\nconst { rest } = useSidebarDocuments({\n app,\n managedDocs: () => registryDocuments.documents ?? [],\n})\n\n/**\n * Whether the workspace truly has no documents to show. Distinct from the\n * filter producing no results: we only surface the \"No APIs yet\" empty state\n * when the workspace is genuinely empty so users see a clear call-to-action\n * instead of a confusing blank space.\n */\nconst isEmpty = computed(\n () => !isLoadingRegistry.value && rest.value.length === 0,\n)\n\n/**\n * Fuzzy filter over the top-level documents. Owns its own input visibility,\n * query string and Fuse index. See `use-document-filter.ts` for details.\n */\nconst {\n isVisible: isFilterVisible,\n query: filterQuery,\n filteredItems: filteredRest,\n toggle: toggleFilter,\n} = useDocumentFilter(rest)\n\nconst sidebarState = app.sidebar.state\n\n/** Which registry documents are currently being fetched. */\nconst loadingKeys = ref<Record<string, boolean>>({})\n\n/**\n * Check if the given {@link SidebarDocumentItem} is the currently active document (from the sidebar state).\n */\nconst isDocActive = (item: SidebarDocumentItem) => {\n if (!item.navigation) {\n return false\n }\n\n return (\n sidebarState.selectedItem.value === item.navigation.id ||\n Boolean(sidebarState.selectedItems.value[item.navigation.id])\n )\n}\n\nconst handleDocumentClick = async (item: SidebarDocumentItem) => {\n if (item.navigation) {\n app.sidebar.handleSelectItem(item.navigation.id)\n return\n }\n\n if (!item.registry || !app.store.value) {\n console.warn('Document does not have a sidebar navigation, skipping...')\n return\n }\n\n if (!fetchRegistryDocument) {\n console.warn(\n 'You need to provide a fetchRegistryDocument function to load registry documents',\n )\n return\n }\n\n if (loadingKeys.value[item.key]) {\n return\n }\n\n loadingKeys.value[item.key] = true\n\n const result = await loadRegistryDocument({\n fetcher: fetchRegistryDocument,\n workspaceStore: app.store.value,\n namespace: item.registry.namespace,\n slug: item.registry.slug,\n })\n\n loadingKeys.value[item.key] = false\n\n if (!result.ok) {\n toast(result.error, 'error')\n return\n }\n\n // After loading, route to the document overview. `syncSidebar` will then\n // mark the document as selected and the template's `:open=\"isDocActive\"`\n // binding drills the sidebar in automatically — no local state needed.\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug: result.documentName,\n })\n}\n\nconst isSelected = (id: string) => sidebarState.isSelected(id)\nconst isExpanded = (id: string) => sidebarState.isExpanded(id)\n\nconst handleSelectItem = (id: string) => {\n app.sidebar.handleSelectItem(id)\n}\n\nconst handleToggleGroup = (id: string) => {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n}\n\n/**\n * Drag-and-drop handlers for sidebar items. The factory reads from the live\n * workspace store and shared sidebar state, so it reflects the latest\n * navigation tree on every drag. Mirrors the behaviour of the old\n * `AppSidebar.vue` (documents, tags, and operations can be reordered, and\n * operations can be moved between tags/documents).\n */\nconst dragHandlers = computed(() =>\n dragHandleFactory({\n store: app.store,\n sidebarState,\n }),\n)\n\nconst handleDragEnd = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => dragHandlers.value.handleDragEnd(draggingItem, hoveredItem)\n\nconst isDroppable = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => dragHandlers.value.isDroppable(draggingItem, hoveredItem)\n\n/**\n * Contextual \"more\" dropdown for tags, operations and examples, together\n * with the shared delete-confirmation modal it triggers.\n */\nconst {\n menuTarget,\n deleteModalState,\n deleteMessage,\n openMenu,\n closeMenu,\n handleDelete,\n} = useSidebarContextMenu({\n eventBus: app.eventBus,\n sidebarState,\n})\n\n/**\n * Create a new operation from an empty folder slot inside a tag or document.\n * If the entry is a tag, the new operation inherits that tag so it stays\n * grouped under the same folder.\n */\nconst handleAddEmptyFolder = (item: TraversedEntry) => {\n const itemWithParent = sidebarState.getEntryById(item.id)\n const documentName = getParentEntry('document', itemWithParent)?.name\n const tagName = getParentEntry('tag', itemWithParent)?.name\n const store = app.store.value\n\n if (!documentName || !store) {\n console.error('Document name not found')\n return\n }\n\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus: app.eventBus,\n tags: tagName ? [tagName] : undefined,\n })\n}\n\nconst handleCreate = () => {\n app.eventBus.emit('ui:open:command-palette', {\n action: 'create-openapi-document',\n payload: undefined,\n })\n}\n\n/**\n * Create a new operation inside the given document and immediately navigate to\n * it. Uses `createTempOperation` so the operation gets a unique `/temp…` path,\n * then the sidebar focuses the new example and the address bar is focused so\n * the user can start typing the real path right away.\n */\nconst handleCreateOperation = (item: SidebarDocumentItem) => {\n const documentName = item.documentName\n const store = app.store.value\n\n if (!documentName || !store) {\n console.warn('Cannot create an operation: no document loaded')\n return\n }\n\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus: app.eventBus,\n })\n}\n\n/**\n * Navigates back to the workspace \"Get started\" page.\n */\nconst handleBack = () => {\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'get-started',\n })\n}\n\n/**\n * True when the user is currently viewing a document (any of its subpages).\n * When inside a document, the sidebar actions are scoped to that document:\n * the gear icon opens the document (collection) settings and the filter icon\n * becomes a search icon that focuses the search input.\n */\nconst isOnDocumentPage = computed(() =>\n Boolean(app.activeEntities.documentSlug.value),\n)\n\nconst handleOpenSettings = () => {\n if (isOnDocumentPage.value) {\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'settings',\n documentSlug: app.activeEntities.documentSlug.value,\n })\n return\n }\n\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'settings',\n })\n}\n\n/**\n * Controls the per-document search modal. Only used when the user is drilled\n * into a single document and clicks the magnifying-glass icon.\n */\nconst searchModal = useModal()\n\n/**\n * The OpenAPI document currently selected in the workspace. The search modal\n * scopes its Fuse index to this document so results never leak across\n * collections.\n */\nconst activeDocument = computed(() => app.store.value?.workspace.activeDocument)\n\nconst handleFilterOrSearch = () => {\n // Inside a document, this icon opens a modal search that is scoped to that\n // single document (similar to the reference search modal), so users can jump\n // to any operation / tag / heading without noise from other documents.\n if (isOnDocumentPage.value) {\n searchModal.show()\n return\n }\n\n // At the top-level documents view, the icon toggles a lightweight filter\n // that narrows the visible documents by title.\n toggleFilter()\n}\n\n/**\n * Handle the `ui:focus:search` event. Dispatch is driven by the shared\n * `handleHotkeys` helper (Cmd/Ctrl+J) or by programmatic callers such as the\n * workspace \"Get started\" page. When a `KeyboardEvent` is included we\n * preventDefault to override the browser's Cmd+J (downloads panel), then\n * delegate to `handleFilterOrSearch`, which already branches on whether the\n * user is viewing a document or the workspace root.\n */\nconst handleSearchHotkey = (payload: { event: KeyboardEvent } | undefined) => {\n payload?.event.preventDefault()\n handleFilterOrSearch()\n}\n\n/**\n * Handle the `ui:open:settings` event (Cmd/Ctrl+I). Same delegation model as\n * `handleSearchHotkey`: preventDefault on the originating keyboard event (if\n * any) and hand off to `handleOpenSettings`, which routes to the document-\n * level settings page when a document is active and the workspace-level\n * settings page otherwise.\n */\nconst handleSettingsHotkey = (\n payload: { event: KeyboardEvent } | undefined,\n) => {\n payload?.event.preventDefault()\n handleOpenSettings()\n}\n\nonBeforeMount(() => {\n app.eventBus.on('ui:focus:search', handleSearchHotkey)\n app.eventBus.on('ui:open:settings', handleSettingsHotkey)\n})\nonBeforeUnmount(() => {\n app.eventBus.off('ui:focus:search', handleSearchHotkey)\n app.eventBus.off('ui:open:settings', handleSettingsHotkey)\n})\n\n/**\n * Navigate to the selected search result. `scroll-to:nav-item` is already\n * wired up through the app event bus to update the sidebar + route, matching\n * the behaviour used elsewhere in the app.\n */\nconst handleSearchSelect = (id: string) => {\n app.eventBus.emit('scroll-to:nav-item', { id })\n}\n\n/** Controls the width of the sidebar */\nconst sidebarWidth = defineModel<number>('sidebarWidth', {\n required: true,\n default: 288,\n})\n</script>\n\n<template>\n <Resize\n v-model:width=\"sidebarWidth\"\n class=\"flex flex-col max-md:inset-y-0 max-md:z-2 max-md:w-full!\"\n :class=\"{\n 'max-md:absolute! max-md:flex!': app.sidebar.isOpen.value,\n 'max-md:hidden!': !app.sidebar.isOpen.value,\n }\">\n <template #default>\n <div class=\"flex flex-1\">\n <ScalarSidebar\n class=\"flex min-h-0 flex-1 flex-col max-md:pt-12\"\n :style=\"{ '--scalar-sidebar-indent': indent + 'px' }\">\n <!-- Top-level sidebar header -->\n <div\n v-if=\"!isOnDocumentPage\"\n class=\"flex flex-col gap-1.5 p-(--scalar-sidebar-padding)\">\n <div class=\"flex items-center gap-1\">\n <ScalarSidebarButton\n is=\"div\"\n class=\"text-sidebar-c-1 font-sidebar-active flex-1\"\n disabled>\n All Documents\n </ScalarSidebarButton>\n <ScalarIconButton\n :icon=\"ScalarIconGearSix\"\n label=\"Workspace settings\"\n size=\"sm\"\n @click=\"handleOpenSettings\" />\n <ScalarIconButton\n :icon=\"ScalarIconFunnel\"\n label=\"Filter documents\"\n size=\"sm\"\n @click=\"handleFilterOrSearch\" />\n <ScalarIconButton\n class=\"rounded-full border\"\n :icon=\"ScalarIconPlus\"\n label=\"Add document\"\n size=\"sm\"\n @click=\"handleCreate\" />\n </div>\n <ScalarSidebarSearchInput\n v-if=\"isFilterVisible\"\n v-model=\"filterQuery\"\n autofocus />\n </div>\n\n <!-- Document list (top-level) -->\n <div class=\"custom-scroll flex flex-1 flex-col\">\n <!--\n Empty state: no documents in the workspace yet. Matches the\n minimal `empty folder` appearance.\n -->\n <div\n v-if=\"isEmpty && !isOnDocumentPage\"\n class=\"text-c-3 flex flex-1 flex-col items-center justify-center gap-2 p-6 text-center select-none\">\n <ScalarIconFolderDashed\n class=\"size-10\"\n weight=\"light\" />\n <p class=\"text-sm font-medium\">No APIs yet</p>\n </div>\n <ScalarSidebarItems v-else>\n <!-- Show pinned documents after we add support for it -->\n <!-- <ScalarSidebarSection v-if=\"pinned.length\">\n Pinned\n <template #items>\n <ScalarSidebarNestedItems\n v-for=\"item in pinned\"\n :key=\"item.key\"\n :active=\"isDocActive(item)\"\n controlled\n :open=\"Boolean(openKeys[item.key])\"\n @back=\"openKeys[item.key] = false\"\n @click=\"handleDocumentClick(item)\">\n <span class=\"truncate\">{{ item.title }}</span>\n <template #back-label>{{ item.title }}</template>\n <template #items>\n <SidebarItem\n v-for=\"child in filterItems(item.navigation?.children ?? [])\"\n :key=\"child.id\"\n :isDraggable=\"false\"\n :isDroppable=\"isDroppable\"\n :isExpanded=\"isExpanded\"\n :isSelected=\"isSelected\"\n :item=\"child\"\n layout=\"client\"\n @selectItem=\"handleSelectItem\"\n @toggleGroup=\"handleToggleGroup\" />\n </template>\n </ScalarSidebarNestedItems>\n </template>\n </ScalarSidebarSection> -->\n\n <ScalarSidebarSection>\n All documents\n <template #items>\n <!--\n Skeleton rows shown while the caller is still fetching\n the registry document list. We only render skeletons in\n the top-level view (when no document is drilled-in) so\n the collection view is never masked by placeholders.\n -->\n <template v-if=\"isLoadingRegistry && !isOnDocumentPage\">\n <li\n v-for=\"n in 4\"\n :key=\"`registry-skeleton-${n}`\"\n aria-hidden=\"true\"\n class=\"sidebar-skeleton-row px-(--scalar-sidebar-padding) py-1\">\n <span class=\"bg-b-3 block h-6 rounded-md\" />\n </li>\n </template>\n <ScalarSidebarNestedItems\n v-for=\"item in filteredRest\"\n :key=\"item.key\"\n :active=\"isDocActive(item)\"\n controlled\n :open=\"isDocActive(item)\"\n @back=\"handleBack\"\n @click=\"handleDocumentClick(item)\">\n <span>{{ item.title }}</span>\n <template\n v-if=\"loadingKeys[item.key]\"\n #aside>\n <span class=\"text-c-3 text-xs\">Loading…</span>\n </template>\n <!-- Document back row -->\n <template #back>\n <div class=\"flex items-center gap-1\">\n <ScalarSidebarButton\n is=\"button\"\n class=\"text-sidebar-c-1 font-sidebar-active hover:text-sidebar-c-1 flex-1\"\n @click=\"handleBack\">\n <template #icon>\n <ScalarIconCaretLeft\n class=\"text-sidebar-c-2 -m-px size-4\" />\n </template>\n Back\n </ScalarSidebarButton>\n <ScalarIconButton\n :icon=\"ScalarIconGearSix\"\n label=\"Collection settings\"\n size=\"sm\"\n @click=\"handleOpenSettings\" />\n <ScalarIconButton\n :icon=\"ScalarIconMagnifyingGlass\"\n label=\"Search collection\"\n size=\"sm\"\n @click=\"handleFilterOrSearch\" />\n <ScalarIconButton\n class=\"rounded-full border\"\n :icon=\"ScalarIconPlus\"\n label=\"Add operation\"\n size=\"sm\"\n @click=\"handleCreateOperation(item)\" />\n </div>\n </template>\n <!-- Document items (operations, tags, examples) -->\n <template #items>\n <template v-if=\"item.navigation?.children?.length\">\n <SidebarItem\n v-for=\"child in filterItems(\n 'client',\n item.navigation.children,\n )\"\n :key=\"child.id\"\n :isDroppable=\"isDroppable\"\n :isExpanded=\"isExpanded\"\n :isSelected=\"isSelected\"\n :item=\"child\"\n layout=\"client\"\n @onDragEnd=\"handleDragEnd\"\n @selectItem=\"handleSelectItem\"\n @toggleGroup=\"handleToggleGroup\">\n <!--\n Per-item \"more\" dropdown for tags, operations and\n examples (add / edit / delete…). The dropdown is\n rendered once at the sidebar root and anchors\n itself to whichever icon button opened it.\n Operation settings live on the operation header\n (next to the environment selector), not here.\n -->\n <template #decorator=\"{ item: entry }\">\n <ScalarIconButton\n aria-expanded=\"false\"\n aria-haspopup=\"menu\"\n class=\"bg-b-2\"\n :icon=\"ScalarIconDotsThree\"\n label=\"More options\"\n size=\"sm\"\n variant=\"ghost\"\n weight=\"bold\"\n @click.stop=\"\n (e: MouseEvent) => openMenu(e, entry)\n \"\n @keydown.down.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \"\n @keydown.enter.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \"\n @keydown.space.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \"\n @keydown.up.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \" />\n </template>\n <!--\n Empty tag / folder slot. Matches the \"Add operation\"\n affordance from the old sidebar so users can create\n an operation directly inside the hovered tag.\n -->\n <template #empty=\"{ item: emptyItem }\">\n <ScalarSidebarItem\n is=\"button\"\n @click=\"handleAddEmptyFolder(emptyItem)\">\n <template #icon>\n <ScalarIconPlus />\n </template>\n <template #default>Add operation</template>\n </ScalarSidebarItem>\n </template>\n </SidebarItem>\n </template>\n <li\n v-else\n class=\"text-c-3 px-3 py-1 text-xs\">\n Empty document\n </li>\n </template>\n </ScalarSidebarNestedItems>\n </template>\n </ScalarSidebarSection>\n </ScalarSidebarItems>\n </div>\n\n <slot name=\"footer\" />\n </ScalarSidebar>\n </div>\n <DocumentSearchModal\n :document=\"activeDocument\"\n :modalState=\"searchModal\"\n @select=\"handleSearchSelect\" />\n <!--\n Contextual dropdown menu for tags, operations and examples. Rendered\n once for the whole sidebar and re-anchored to the triggering icon via\n `menuTarget.el`, so we do not create a dropdown per item.\n -->\n <SidebarItemMenu\n v-if=\"app.store.value && menuTarget?.showMenu\"\n :eventBus=\"app.eventBus\"\n :item=\"menuTarget.item\"\n :sidebarState=\"sidebarState\"\n :target=\"menuTarget.el\"\n :workspaceStore=\"app.store.value\"\n @closeMenu=\"closeMenu\"\n @showDeleteModal=\"deleteModalState.show()\" />\n <!-- Delete confirmation modal, triggered from the dropdown menu above. -->\n <ScalarModal\n v-if=\"menuTarget\"\n size=\"xxs\"\n :state=\"deleteModalState\"\n :title=\"`Delete ${menuTarget.item.title}`\">\n <DeleteSidebarListElement\n :variableName=\"menuTarget.item.title\"\n :warningMessage=\"deleteMessage\"\n @close=\"deleteModalState.hide()\"\n @delete=\"handleDelete\" />\n </ScalarModal>\n </template>\n </Resize>\n</template>\n\n<style scoped>\n/*\n * Gentle pulse for the registry loading skeletons. Matches the existing\n * `LoadingSkeleton.vue` easing/duration used in `@scalar/api-reference` so\n * any skeleton in the app feels consistent.\n */\n.sidebar-skeleton-row > span {\n animation: sidebar-skeleton-pulse 1.5s infinite alternate;\n}\n\n@keyframes sidebar-skeleton-pulse {\n from {\n opacity: 1;\n }\n to {\n opacity: 0.33;\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppSidebar.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AppState } from '@scalar/api-client/v2/features/app'\nimport {\n ScalarIconButton,\n ScalarModal,\n ScalarSidebar,\n ScalarSidebarButton,\n ScalarSidebarItems,\n ScalarSidebarSearchInput,\n ScalarSidebarSection,\n useModal,\n} from '@scalar/components'\nimport {\n ScalarIconFolderDashed,\n ScalarIconFunnel,\n ScalarIconGearSix,\n ScalarIconPlus,\n} from '@scalar/icons'\nimport type { DraggingItem, HoveredItem } from '@scalar/sidebar'\nimport { useToasts } from '@scalar/use-toasts'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, onBeforeMount, onBeforeUnmount, ref } from 'vue'\n\nimport DeleteSidebarListElement from '@/components/Sidebar/Actions/DeleteSidebarListElement.vue'\nimport { Resize } from '@/v2/components/resize'\nimport SidebarDocument from '@/v2/features/app/components/SidebarDocument.vue'\nimport SidebarItemMenu from '@/v2/features/app/components/SidebarItemMenu.vue'\nimport { createTempOperation } from '@/v2/features/app/helpers/create-temp-operation'\nimport { loadRegistryDocument } from '@/v2/features/app/helpers/load-registry-document'\nimport { useDocumentFilter } from '@/v2/features/app/hooks/use-document-filter'\nimport { useSidebarContextMenu } from '@/v2/features/app/hooks/use-sidebar-context-menu'\nimport {\n useSidebarDocuments,\n type RegistryDocumentsState,\n type SidebarDocumentItem,\n} from '@/v2/features/app/hooks/use-sidebar-documents'\nimport { DocumentSearchModal } from '@/v2/features/search'\nimport { dragHandleFactory } from '@/v2/helpers/drag-handle-factory'\nimport { safeRun } from '@/v2/helpers/safe-run'\nimport type { ImportDocumentFromRegistry } from '@/v2/types/configuration'\n\nconst {\n app,\n indent = 20,\n registryDocuments = { status: 'success', documents: [] },\n fetchRegistryDocument,\n} = defineProps<{\n /** The app state from @scalar/api-client. */\n app: AppState\n /** Horizontal indent applied to nested sidebar items, in pixels. */\n indent?: number\n /**\n * The list of all available registry documents, wrapped in a loading state\n * so the sidebar can render skeleton placeholders while the registry is\n * still being fetched.\n */\n registryDocuments?: RegistryDocumentsState\n /** A function to fetch a registry document */\n fetchRegistryDocument?: ImportDocumentFromRegistry\n}>()\n\nconst { toast } = useToasts()\n\n/**\n * Whether the caller is still fetching the list of registry documents. We\n * only surface the loading state on team workspaces because local workspaces\n * never consult the registry\n */\nconst isLoadingRegistry = computed(\n () =>\n registryDocuments.status === 'loading' &&\n app.workspace.isTeamWorkspace.value,\n)\n\nconst { pinned, rest } = useSidebarDocuments({\n app,\n managedDocs: () => registryDocuments.documents ?? [],\n})\n\n/**\n * Whether the workspace truly has no documents to show. Distinct from the\n * filter producing no results: we only surface the \"No APIs yet\" empty state\n * when the workspace is genuinely empty so users see a clear call-to-action\n * instead of a confusing blank space.\n */\nconst isEmpty = computed(\n () => !isLoadingRegistry.value && rest.value.length === 0,\n)\n\n/**\n * Fuzzy filter over the top-level documents. Owns its own input visibility,\n * query string and Fuse index. See `use-document-filter.ts` for details.\n */\nconst {\n isVisible: isFilterVisible,\n query: filterQuery,\n filteredItems: filteredRest,\n toggle: toggleFilter,\n} = useDocumentFilter(rest)\n\nconst sidebarState = app.sidebar.state\n\n/** Which registry documents are currently being fetched. */\nconst loadingKeys = ref<Record<string, boolean>>({})\n\n/**\n * Check if the given {@link SidebarDocumentItem} is the currently active document (from the sidebar state).\n */\nconst isDocActive = (item: SidebarDocumentItem) => {\n if (!item.navigation) {\n return false\n }\n\n return (\n sidebarState.selectedItem.value === item.navigation.id ||\n Boolean(sidebarState.selectedItems.value[item.navigation.id])\n )\n}\n\nconst handleDocumentClick = async (item: SidebarDocumentItem) => {\n if (item.navigation) {\n app.sidebar.handleSelectItem(item.navigation.id)\n return\n }\n\n // Capture the narrowed values into locals so the closure passed to\n // `safeRun` keeps the non-nullable types without needing assertions, and\n // so a later mutation to `item.registry` or `app.store.value` cannot\n // change what we end up loading mid-flight.\n const { registry } = item\n const workspaceStore = app.store.value\n if (!registry || !workspaceStore) {\n console.warn('Document does not have a sidebar navigation, skipping...')\n return\n }\n\n if (!fetchRegistryDocument) {\n console.warn(\n 'You need to provide a fetchRegistryDocument function to load registry documents',\n )\n return\n }\n\n if (loadingKeys.value[item.key]) {\n return\n }\n\n loadingKeys.value[item.key] = true\n\n // Registry items expose every version they advertise on `versions`, ordered\n // with the latest first. Until we surface a version picker on the parent\n // row we always load whichever version is at the top of the list, which is\n // the latest one. Falling back to `undefined` lets the loader default to\n // the registry's `latest` alias when the entry was synthesized from a\n // workspace document that has no advertised versions yet.\n const targetVersion = item.versions?.[0]\n\n // The loader can throw on network errors or malformed payloads. `safeRun`\n // converts a rejection into an `{ ok: false, error }` result so a single\n // failure cannot leave the row's spinner running forever and block\n // subsequent clicks on the same item.\n const outcome = await safeRun(() =>\n loadRegistryDocument({\n fetcher: fetchRegistryDocument,\n workspaceStore,\n namespace: registry.namespace,\n slug: registry.slug,\n version: targetVersion?.version,\n // Forward the registry-advertised hash from the version row. Storing it\n // on the imported document lets us later detect when the registry has\n // moved on and surface upstream changes.\n commitHash: targetVersion?.registryCommitHash,\n }),\n )\n\n loadingKeys.value[item.key] = 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 // After loading, route to the document overview. `syncSidebar` will then\n // mark the document as selected and the template's `:open=\"isDocActive\"`\n // binding drills the sidebar in automatically — no local state needed.\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug: result.documentName,\n })\n}\n\nconst isSelected = (id: string) => sidebarState.isSelected(id)\nconst isExpanded = (id: string) => sidebarState.isExpanded(id)\n\nconst handleSelectItem = (id: string) => {\n app.sidebar.handleSelectItem(id)\n}\n\nconst handleToggleGroup = (id: string) => {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n}\n\n/**\n * Drag-and-drop handlers for sidebar items. The factory reads from the live\n * workspace store and shared sidebar state, so it reflects the latest\n * navigation tree on every drag. Mirrors the behaviour of the old\n * `AppSidebar.vue` (documents, tags, and operations can be reordered, and\n * operations can be moved between tags/documents).\n */\nconst dragHandlers = computed(() =>\n dragHandleFactory({\n store: app.store,\n sidebarState,\n }),\n)\n\nconst handleDragEnd = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => dragHandlers.value.handleDragEnd(draggingItem, hoveredItem)\n\nconst isDroppable = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => dragHandlers.value.isDroppable(draggingItem, hoveredItem)\n\n/**\n * Contextual \"more\" dropdown for tags, operations and examples, together\n * with the shared delete-confirmation modal it triggers.\n */\nconst {\n menuTarget,\n deleteModalState,\n deleteMessage,\n openMenu,\n closeMenu,\n handleDelete,\n} = useSidebarContextMenu({\n eventBus: app.eventBus,\n sidebarState,\n})\n\n/**\n * Create a new operation from an empty folder slot inside a tag or document.\n * If the entry is a tag, the new operation inherits that tag so it stays\n * grouped under the same folder.\n */\nconst handleAddEmptyFolder = (item: TraversedEntry) => {\n const itemWithParent = sidebarState.getEntryById(item.id)\n const documentName = getParentEntry('document', itemWithParent)?.name\n const tagName = getParentEntry('tag', itemWithParent)?.name\n const store = app.store.value\n\n if (!documentName || !store) {\n console.error('Document name not found')\n return\n }\n\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus: app.eventBus,\n tags: tagName ? [tagName] : undefined,\n })\n}\n\nconst handleCreate = () => {\n app.eventBus.emit('ui:open:command-palette', {\n action: 'create-openapi-document',\n payload: undefined,\n })\n}\n\n/**\n * Create a new operation inside the given document and immediately navigate to\n * it. Uses `createTempOperation` so the operation gets a unique `/temp…` path,\n * then the sidebar focuses the new example and the address bar is focused so\n * the user can start typing the real path right away.\n */\nconst handleCreateOperation = (item: SidebarDocumentItem) => {\n const documentName = item.documentName\n const store = app.store.value\n\n if (!documentName || !store) {\n console.warn('Cannot create an operation: no document loaded')\n return\n }\n\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus: app.eventBus,\n })\n}\n\n/**\n * Navigates back to the workspace \"Get started\" page.\n */\nconst handleBack = () => {\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'get-started',\n })\n}\n\n/**\n * True when the user is currently viewing a document (any of its subpages).\n * When inside a document, the sidebar actions are scoped to that document:\n * the gear icon opens the document (collection) settings and the filter icon\n * becomes a search icon that focuses the search input.\n */\nconst isOnDocumentPage = computed(() =>\n Boolean(app.activeEntities.documentSlug.value),\n)\n\nconst handleOpenSettings = () => {\n if (isOnDocumentPage.value) {\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'settings',\n documentSlug: app.activeEntities.documentSlug.value,\n })\n return\n }\n\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'settings',\n })\n}\n\n/**\n * Controls the per-document search modal. Only used when the user is drilled\n * into a single document and clicks the magnifying-glass icon.\n */\nconst searchModal = useModal()\n\n/**\n * The OpenAPI document currently selected in the workspace. The search modal\n * scopes its Fuse index to this document so results never leak across\n * collections.\n */\nconst activeDocument = computed(() => app.store.value?.workspace.activeDocument)\n\nconst handleFilterOrSearch = () => {\n // Inside a document, this icon opens a modal search that is scoped to that\n // single document (similar to the reference search modal), so users can jump\n // to any operation / tag / heading without noise from other documents.\n if (isOnDocumentPage.value) {\n searchModal.show()\n return\n }\n\n // At the top-level documents view, the icon toggles a lightweight filter\n // that narrows the visible documents by title.\n toggleFilter()\n}\n\n/**\n * Handle the `ui:focus:search` event. Dispatch is driven by the shared\n * `handleHotkeys` helper (Cmd/Ctrl+J) or by programmatic callers such as the\n * workspace \"Get started\" page. When a `KeyboardEvent` is included we\n * preventDefault to override the browser's Cmd+J (downloads panel), then\n * delegate to `handleFilterOrSearch`, which already branches on whether the\n * user is viewing a document or the workspace root.\n */\nconst handleSearchHotkey = (payload: { event: KeyboardEvent } | undefined) => {\n payload?.event.preventDefault()\n handleFilterOrSearch()\n}\n\n/**\n * Handle the `ui:open:settings` event (Cmd/Ctrl+I). Same delegation model as\n * `handleSearchHotkey`: preventDefault on the originating keyboard event (if\n * any) and hand off to `handleOpenSettings`, which routes to the document-\n * level settings page when a document is active and the workspace-level\n * settings page otherwise.\n */\nconst handleSettingsHotkey = (\n payload: { event: KeyboardEvent } | undefined,\n) => {\n payload?.event.preventDefault()\n handleOpenSettings()\n}\n\nonBeforeMount(() => {\n app.eventBus.on('ui:focus:search', handleSearchHotkey)\n app.eventBus.on('ui:open:settings', handleSettingsHotkey)\n})\nonBeforeUnmount(() => {\n app.eventBus.off('ui:focus:search', handleSearchHotkey)\n app.eventBus.off('ui:open:settings', handleSettingsHotkey)\n})\n\n/**\n * Navigate to the selected search result. `scroll-to:nav-item` is already\n * wired up through the app event bus to update the sidebar + route, matching\n * the behaviour used elsewhere in the app.\n */\nconst handleSearchSelect = (id: string) => {\n app.eventBus.emit('scroll-to:nav-item', { id })\n}\n\n/** Controls the width of the sidebar */\nconst sidebarWidth = defineModel<number>('sidebarWidth', {\n required: true,\n default: 288,\n})\n</script>\n\n<template>\n <Resize\n v-model:width=\"sidebarWidth\"\n class=\"flex flex-col max-md:inset-y-0 max-md:z-2 max-md:w-full!\"\n :class=\"{\n 'max-md:absolute! max-md:flex!': app.sidebar.isOpen.value,\n 'max-md:hidden!': !app.sidebar.isOpen.value,\n }\">\n <template #default>\n <div class=\"flex flex-1\">\n <ScalarSidebar\n class=\"flex min-h-0 flex-1 flex-col max-md:pt-12\"\n :style=\"{ '--scalar-sidebar-indent': indent + 'px' }\">\n <!-- Top-level sidebar header -->\n <div\n v-if=\"!isOnDocumentPage\"\n class=\"flex flex-col gap-1.5 p-(--scalar-sidebar-padding)\">\n <div class=\"flex items-center gap-1\">\n <ScalarSidebarButton\n is=\"div\"\n class=\"text-sidebar-c-1 font-sidebar-active flex-1\"\n disabled>\n All Documents\n </ScalarSidebarButton>\n <ScalarIconButton\n :icon=\"ScalarIconGearSix\"\n label=\"Workspace settings\"\n size=\"sm\"\n @click=\"handleOpenSettings\" />\n <ScalarIconButton\n :icon=\"ScalarIconFunnel\"\n label=\"Filter documents\"\n size=\"sm\"\n @click=\"handleFilterOrSearch\" />\n <ScalarIconButton\n class=\"rounded-full border\"\n :icon=\"ScalarIconPlus\"\n label=\"Add document\"\n size=\"sm\"\n @click=\"handleCreate\" />\n </div>\n <ScalarSidebarSearchInput\n v-if=\"isFilterVisible\"\n v-model=\"filterQuery\"\n autofocus />\n </div>\n\n <!-- Document list (top-level) -->\n <div class=\"custom-scroll flex flex-1 flex-col\">\n <!--\n Empty state: no documents in the workspace yet. Matches the\n minimal `empty folder` appearance.\n -->\n <div\n v-if=\"isEmpty && !isOnDocumentPage\"\n class=\"text-c-3 flex flex-1 flex-col items-center justify-center gap-2 p-6 text-center select-none\">\n <ScalarIconFolderDashed\n class=\"size-10\"\n weight=\"light\" />\n <p class=\"text-sm font-medium\">Nothing added yet</p>\n </div>\n <ScalarSidebarItems v-else>\n <!-- Show pinned documents after we add support for it -->\n <ScalarSidebarSection v-if=\"pinned.length\">\n <template\n v-if=\"pinned.length && rest.length\"\n #default>\n Pinned\n </template>\n <template #items>\n <SidebarDocument\n v-for=\"item in pinned\"\n :key=\"item.key\"\n :active=\"isDocActive(item)\"\n :isDroppable=\"isDroppable\"\n :isExpanded=\"isExpanded\"\n :isSelected=\"isSelected\"\n :item=\"item\"\n :loading=\"loadingKeys[item.key]\"\n :open=\"isDocActive(item)\"\n @addEmptyFolder=\"handleAddEmptyFolder\"\n @back=\"handleBack\"\n @click=\"handleDocumentClick(item)\"\n @createOperation=\"handleCreateOperation\"\n @dragEnd=\"handleDragEnd\"\n @openMenu=\"openMenu\"\n @openSettings=\"handleOpenSettings\"\n @search=\"handleFilterOrSearch\"\n @selectItem=\"handleSelectItem\"\n @toggleGroup=\"handleToggleGroup\" />\n </template>\n </ScalarSidebarSection>\n\n <ScalarSidebarSection>\n <template\n v-if=\"pinned.length && rest.length\"\n #default>\n All documents\n </template>\n <template #items>\n <!--\n Skeleton rows shown while the caller is still fetching\n the registry document list. We only render skeletons in\n the top-level view (when no document is drilled-in) so\n the collection view is never masked by placeholders.\n -->\n <template v-if=\"isLoadingRegistry && !isOnDocumentPage\">\n <li\n v-for=\"n in 4\"\n :key=\"`registry-skeleton-${n}`\"\n aria-hidden=\"true\"\n class=\"sidebar-skeleton-row px-(--scalar-sidebar-padding) py-1\">\n <span class=\"bg-b-3 block h-6 rounded-md\" />\n </li>\n </template>\n <SidebarDocument\n v-for=\"item in filteredRest\"\n :key=\"item.key\"\n :active=\"isDocActive(item)\"\n :isDroppable=\"isDroppable\"\n :isExpanded=\"isExpanded\"\n :isSelected=\"isSelected\"\n :item=\"item\"\n :loading=\"loadingKeys[item.key]\"\n :open=\"isDocActive(item)\"\n @addEmptyFolder=\"handleAddEmptyFolder\"\n @back=\"handleBack\"\n @click=\"handleDocumentClick(item)\"\n @createOperation=\"handleCreateOperation\"\n @dragEnd=\"handleDragEnd\"\n @openMenu=\"openMenu\"\n @openSettings=\"handleOpenSettings\"\n @search=\"handleFilterOrSearch\"\n @selectItem=\"handleSelectItem\"\n @toggleGroup=\"handleToggleGroup\" />\n </template>\n </ScalarSidebarSection>\n </ScalarSidebarItems>\n </div>\n\n <slot name=\"footer\" />\n </ScalarSidebar>\n </div>\n <DocumentSearchModal\n :document=\"activeDocument\"\n :modalState=\"searchModal\"\n @select=\"handleSearchSelect\" />\n <!--\n Contextual dropdown menu for tags, operations and examples. Rendered\n once for the whole sidebar and re-anchored to the triggering icon via\n `menuTarget.el`, so we do not create a dropdown per item.\n -->\n <SidebarItemMenu\n v-if=\"app.store.value && menuTarget?.showMenu\"\n :eventBus=\"app.eventBus\"\n :item=\"menuTarget.item\"\n :sidebarState=\"sidebarState\"\n :target=\"menuTarget.el\"\n :workspaceStore=\"app.store.value\"\n @closeMenu=\"closeMenu\"\n @showDeleteModal=\"deleteModalState.show()\" />\n <!-- Delete confirmation modal, triggered from the dropdown menu above. -->\n <ScalarModal\n v-if=\"menuTarget\"\n size=\"xxs\"\n :state=\"deleteModalState\"\n :title=\"`Delete ${menuTarget.item.title}`\">\n <DeleteSidebarListElement\n :variableName=\"menuTarget.item.title\"\n :warningMessage=\"deleteMessage\"\n @close=\"deleteModalState.hide()\"\n @delete=\"handleDelete\" />\n </ScalarModal>\n </template>\n </Resize>\n</template>\n\n<style scoped>\n/*\n * Gentle pulse for the registry loading skeletons. Matches the existing\n * `LoadingSkeleton.vue` easing/duration used in `@scalar/api-reference` so\n * any skeleton in the app feels consistent.\n */\n.sidebar-skeleton-row > span {\n animation: sidebar-skeleton-pulse 1.5s infinite alternate;\n}\n\n@keyframes sidebar-skeleton-pulse {\n from {\n opacity: 1;\n }\n to {\n opacity: 0.33;\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ModalState } from '@scalar/components';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Modal lifecycle state from `useModal()`. */
|
|
4
|
+
state: ModalState;
|
|
5
|
+
/**
|
|
6
|
+
* Versions already loaded into the workspace store for the active document
|
|
7
|
+
* group. Submitting a duplicate would silently collide with an existing
|
|
8
|
+
* workspace document, so we reject it up front. Versions that exist only on
|
|
9
|
+
* the registry (not loaded locally) are intentionally allowed - that is the
|
|
10
|
+
* conflict-resolution path the create-draft flow opts into.
|
|
11
|
+
*/
|
|
12
|
+
existingVersions: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Version the new draft will be branched from. Surfaced to the user so it
|
|
15
|
+
* is obvious which document the draft inherits its body from before they
|
|
16
|
+
* commit to a new version string. Optional because the modal can be
|
|
17
|
+
* reused in flows that do not branch from an existing version.
|
|
18
|
+
*/
|
|
19
|
+
sourceVersion?: string;
|
|
20
|
+
};
|
|
21
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
22
|
+
create: (version: string) => any;
|
|
23
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
24
|
+
onCreate?: ((version: string) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
|
+
declare const _default: typeof __VLS_export;
|
|
27
|
+
export default _default;
|
|
28
|
+
//# sourceMappingURL=CreateVersionModal.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateVersionModal.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/CreateVersionModal.vue"],"names":[],"mappings":"AAqHA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAQjE,KAAK,WAAW,GAAG;IACjB,+CAA+C;IAC/C,KAAK,EAAE,UAAU,CAAA;IACjB;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAC;AA2KF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import CreateVersionModal_vue_vue_type_script_setup_true_lang_default from "./CreateVersionModal.vue.script.js";
|
|
2
|
+
//#region src/v2/features/app/components/CreateVersionModal.vue
|
|
3
|
+
var CreateVersionModal_default = CreateVersionModal_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { CreateVersionModal_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=CreateVersionModal.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateVersionModal.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/CreateVersionModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarModal, type ModalState } from '@scalar/components'\nimport { computed, ref, watch, type ComputedRef, type Ref } from 'vue'\n\nimport {\n CommandActionForm,\n CommandActionInput,\n} from '@/v2/features/command-palette'\n\nconst { state, existingVersions, sourceVersion } = defineProps<{\n /** Modal lifecycle state from `useModal()`. */\n state: ModalState\n /**\n * Versions already loaded into the workspace store for the active document\n * group. Submitting a duplicate would silently collide with an existing\n * workspace document, so we reject it up front. Versions that exist only on\n * the registry (not loaded locally) are intentionally allowed - that is the\n * conflict-resolution path the create-draft flow opts into.\n */\n existingVersions: string[]\n /**\n * Version the new draft will be branched from. Surfaced to the user so it\n * is obvious which document the draft inherits its body from before they\n * commit to a new version string. Optional because the modal can be\n * reused in flows that do not branch from an existing version.\n */\n sourceVersion?: string\n}>()\n\nconst emit = defineEmits<{\n /** Fired after the user submits a non-empty, non-duplicate version string. */\n (event: 'create', version: string): void\n}>()\n\n/** Raw input value for the version field. */\nconst version: Ref<string> = ref('')\n\n/** Trimmed input. The form treats whitespace-only input as empty. */\nconst trimmed: ComputedRef<string> = computed(() => version.value.trim())\n\n/**\n * Validation message surfaced under the input.\n *\n * Resolves to `null` when the form is valid; otherwise to a human-readable\n * reason that doubles as the disabled-state explanation.\n */\nconst errorMessage: ComputedRef<string | null> = computed(() => {\n if (trimmed.value.length === 0) {\n // Empty input is the default state, so we keep the field free of error\n // styling and only block submission via `isDisabled` below.\n return null\n }\n if (existingVersions.includes(trimmed.value)) {\n return `Version \"${trimmed.value}\" is already loaded in the workspace.`\n }\n return null\n})\n\n/** Submit is blocked while the input is empty or the version already exists locally. */\nconst isDisabled: ComputedRef<boolean> = computed(\n () => trimmed.value.length === 0 || errorMessage.value !== null,\n)\n\n/** Reset the field whenever the modal opens so stale input never leaks across sessions. */\nwatch(\n () => state.open,\n (isOpen) => {\n if (isOpen) {\n version.value = ''\n }\n },\n)\n\nconst handleSubmit = (): void => {\n if (isDisabled.value) {\n return\n }\n emit('create', trimmed.value)\n state.hide()\n}\n</script>\n\n<template>\n <ScalarModal\n bodyClass=\"border-t-0 rounded-t-lg\"\n size=\"xs\"\n :state=\"state\"\n title=\"Create new version\">\n <CommandActionForm\n :disabled=\"isDisabled\"\n @submit=\"handleSubmit\">\n <CommandActionInput\n v-model=\"version\"\n class=\"-mt-[.5px] !p-0\"\n placeholder=\"Version (e.g. 2.1.0)\" />\n\n <p\n v-if=\"sourceVersion\"\n class=\"text-c-2 text-xs\">\n Branching from version\n <span class=\"text-c-1 font-medium\">{{ sourceVersion }}</span>\n </p>\n\n <p\n v-if=\"errorMessage\"\n class=\"text-red text-xs\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <template #submit>Create version</template>\n </CommandActionForm>\n </ScalarModal>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import CommandActionForm_default from "../../command-palette/components/CommandActionForm.vue.js";
|
|
2
|
+
import CommandActionInput_default from "../../command-palette/components/CommandActionInput.vue.js";
|
|
3
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, ref, toDisplayString, unref, watch, withCtx } from "vue";
|
|
4
|
+
import { ScalarModal } from "@scalar/components";
|
|
5
|
+
//#region src/v2/features/app/components/CreateVersionModal.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
+
var _hoisted_1 = {
|
|
7
|
+
key: 0,
|
|
8
|
+
class: "text-c-2 text-xs"
|
|
9
|
+
};
|
|
10
|
+
var _hoisted_2 = { class: "text-c-1 font-medium" };
|
|
11
|
+
var _hoisted_3 = {
|
|
12
|
+
key: 1,
|
|
13
|
+
class: "text-red text-xs",
|
|
14
|
+
role: "alert"
|
|
15
|
+
};
|
|
16
|
+
var CreateVersionModal_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
17
|
+
__name: "CreateVersionModal",
|
|
18
|
+
props: {
|
|
19
|
+
state: {},
|
|
20
|
+
existingVersions: {},
|
|
21
|
+
sourceVersion: {}
|
|
22
|
+
},
|
|
23
|
+
emits: ["create"],
|
|
24
|
+
setup(__props, { emit: __emit }) {
|
|
25
|
+
const emit = __emit;
|
|
26
|
+
/** Raw input value for the version field. */
|
|
27
|
+
const version = ref("");
|
|
28
|
+
/** Trimmed input. The form treats whitespace-only input as empty. */
|
|
29
|
+
const trimmed = computed(() => version.value.trim());
|
|
30
|
+
/**
|
|
31
|
+
* Validation message surfaced under the input.
|
|
32
|
+
*
|
|
33
|
+
* Resolves to `null` when the form is valid; otherwise to a human-readable
|
|
34
|
+
* reason that doubles as the disabled-state explanation.
|
|
35
|
+
*/
|
|
36
|
+
const errorMessage = computed(() => {
|
|
37
|
+
if (trimmed.value.length === 0) return null;
|
|
38
|
+
if (__props.existingVersions.includes(trimmed.value)) return `Version "${trimmed.value}" is already loaded in the workspace.`;
|
|
39
|
+
return null;
|
|
40
|
+
});
|
|
41
|
+
/** Submit is blocked while the input is empty or the version already exists locally. */
|
|
42
|
+
const isDisabled = computed(() => trimmed.value.length === 0 || errorMessage.value !== null);
|
|
43
|
+
/** Reset the field whenever the modal opens so stale input never leaks across sessions. */
|
|
44
|
+
watch(() => __props.state.open, (isOpen) => {
|
|
45
|
+
if (isOpen) version.value = "";
|
|
46
|
+
});
|
|
47
|
+
const handleSubmit = () => {
|
|
48
|
+
if (isDisabled.value) return;
|
|
49
|
+
emit("create", trimmed.value);
|
|
50
|
+
__props.state.hide();
|
|
51
|
+
};
|
|
52
|
+
return (_ctx, _cache) => {
|
|
53
|
+
return openBlock(), createBlock(unref(ScalarModal), {
|
|
54
|
+
bodyClass: "border-t-0 rounded-t-lg",
|
|
55
|
+
size: "xs",
|
|
56
|
+
state: __props.state,
|
|
57
|
+
title: "Create new version"
|
|
58
|
+
}, {
|
|
59
|
+
default: withCtx(() => [createVNode(unref(CommandActionForm_default), {
|
|
60
|
+
disabled: isDisabled.value,
|
|
61
|
+
onSubmit: handleSubmit
|
|
62
|
+
}, {
|
|
63
|
+
submit: withCtx(() => [..._cache[2] || (_cache[2] = [createTextVNode("Create version", -1)])]),
|
|
64
|
+
default: withCtx(() => [
|
|
65
|
+
createVNode(unref(CommandActionInput_default), {
|
|
66
|
+
modelValue: version.value,
|
|
67
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => version.value = $event),
|
|
68
|
+
class: "-mt-[.5px] !p-0",
|
|
69
|
+
placeholder: "Version (e.g. 2.1.0)"
|
|
70
|
+
}, null, 8, ["modelValue"]),
|
|
71
|
+
__props.sourceVersion ? (openBlock(), createElementBlock("p", _hoisted_1, [_cache[1] || (_cache[1] = createTextVNode(" Branching from version ", -1)), createElementVNode("span", _hoisted_2, toDisplayString(__props.sourceVersion), 1)])) : createCommentVNode("", true),
|
|
72
|
+
errorMessage.value ? (openBlock(), createElementBlock("p", _hoisted_3, toDisplayString(errorMessage.value), 1)) : createCommentVNode("", true)
|
|
73
|
+
]),
|
|
74
|
+
_: 1
|
|
75
|
+
}, 8, ["disabled"])]),
|
|
76
|
+
_: 1
|
|
77
|
+
}, 8, ["state"]);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
//#endregion
|
|
82
|
+
export { CreateVersionModal_vue_vue_type_script_setup_true_lang_default as default };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=CreateVersionModal.vue.script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateVersionModal.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/CreateVersionModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarModal, type ModalState } from '@scalar/components'\nimport { computed, ref, watch, type ComputedRef, type Ref } from 'vue'\n\nimport {\n CommandActionForm,\n CommandActionInput,\n} from '@/v2/features/command-palette'\n\nconst { state, existingVersions, sourceVersion } = defineProps<{\n /** Modal lifecycle state from `useModal()`. */\n state: ModalState\n /**\n * Versions already loaded into the workspace store for the active document\n * group. Submitting a duplicate would silently collide with an existing\n * workspace document, so we reject it up front. Versions that exist only on\n * the registry (not loaded locally) are intentionally allowed - that is the\n * conflict-resolution path the create-draft flow opts into.\n */\n existingVersions: string[]\n /**\n * Version the new draft will be branched from. Surfaced to the user so it\n * is obvious which document the draft inherits its body from before they\n * commit to a new version string. Optional because the modal can be\n * reused in flows that do not branch from an existing version.\n */\n sourceVersion?: string\n}>()\n\nconst emit = defineEmits<{\n /** Fired after the user submits a non-empty, non-duplicate version string. */\n (event: 'create', version: string): void\n}>()\n\n/** Raw input value for the version field. */\nconst version: Ref<string> = ref('')\n\n/** Trimmed input. The form treats whitespace-only input as empty. */\nconst trimmed: ComputedRef<string> = computed(() => version.value.trim())\n\n/**\n * Validation message surfaced under the input.\n *\n * Resolves to `null` when the form is valid; otherwise to a human-readable\n * reason that doubles as the disabled-state explanation.\n */\nconst errorMessage: ComputedRef<string | null> = computed(() => {\n if (trimmed.value.length === 0) {\n // Empty input is the default state, so we keep the field free of error\n // styling and only block submission via `isDisabled` below.\n return null\n }\n if (existingVersions.includes(trimmed.value)) {\n return `Version \"${trimmed.value}\" is already loaded in the workspace.`\n }\n return null\n})\n\n/** Submit is blocked while the input is empty or the version already exists locally. */\nconst isDisabled: ComputedRef<boolean> = computed(\n () => trimmed.value.length === 0 || errorMessage.value !== null,\n)\n\n/** Reset the field whenever the modal opens so stale input never leaks across sessions. */\nwatch(\n () => state.open,\n (isOpen) => {\n if (isOpen) {\n version.value = ''\n }\n },\n)\n\nconst handleSubmit = (): void => {\n if (isDisabled.value) {\n return\n }\n emit('create', trimmed.value)\n state.hide()\n}\n</script>\n\n<template>\n <ScalarModal\n bodyClass=\"border-t-0 rounded-t-lg\"\n size=\"xs\"\n :state=\"state\"\n title=\"Create new version\">\n <CommandActionForm\n :disabled=\"isDisabled\"\n @submit=\"handleSubmit\">\n <CommandActionInput\n v-model=\"version\"\n class=\"-mt-[.5px] !p-0\"\n placeholder=\"Version (e.g. 2.1.0)\" />\n\n <p\n v-if=\"sourceVersion\"\n class=\"text-c-2 text-xs\">\n Branching from version\n <span class=\"text-c-1 font-medium\">{{ sourceVersion }}</span>\n </p>\n\n <p\n v-if=\"errorMessage\"\n class=\"text-red text-xs\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <template #submit>Create version</template>\n </CommandActionForm>\n </ScalarModal>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EA6BA,MAAM,OAAO;;EAMb,MAAM,UAAuB,IAAI,GAAE;;EAGnC,MAAM,UAA+B,eAAe,QAAQ,MAAM,MAAM,CAAA;;;;;;;EAQxE,MAAM,eAA2C,eAAe;AAC9D,OAAI,QAAQ,MAAM,WAAW,EAG3B,QAAO;AAET,OAAI,QAAA,iBAAiB,SAAS,QAAQ,MAAM,CAC1C,QAAO,YAAY,QAAQ,MAAM;AAEnC,UAAO;IACR;;EAGD,MAAM,aAAmC,eACjC,QAAQ,MAAM,WAAW,KAAK,aAAa,UAAU,KAC7D;;AAGA,cACQ,QAAA,MAAM,OACX,WAAW;AACV,OAAI,OACF,SAAQ,QAAQ;IAGtB;EAEA,MAAM,qBAA2B;AAC/B,OAAI,WAAW,MACb;AAEF,QAAK,UAAU,QAAQ,MAAK;AAC5B,WAAA,MAAM,MAAK;;;uBAKX,YA6Bc,MAAA,YAAA,EAAA;IA5BZ,WAAU;IACV,MAAK;IACJ,OAAO,QAAA;IACR,OAAM;;2BAwBc,CAvBpB,YAuBoB,MAAA,0BAAA,EAAA;KAtBjB,UAAU,WAAA;KACV,UAAQ;;KAoBE,QAAM,cAAe,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,gBAAd,kBAAc,GAAA,CAAA,EAAA,CAAA;4BAhBO;MAHvC,YAGuC,MAAA,2BAAA,EAAA;mBAF5B,QAAA;4EAAO,QAAA;OAChB,OAAM;OACN,aAAY;;MAGN,QAAA,iBAAA,WAAA,EADR,mBAKI,KALJ,YAKI,CAAA,OAAA,OAAA,OAAA,KAAA,gBAHuB,4BAEzB,GAAA,GAAA,mBAA6D,QAA7D,YAA6D,gBAAvB,QAAA,cAAa,EAAA,EAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA;MAI7C,aAAA,SAAA,WAAA,EADR,mBAKI,KALJ,YAKI,gBADC,aAAA,MAAY,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AppState } from '../../../../v2/features/app/app-state.js';
|
|
2
|
+
import type { RegistryDocumentsState } from '../../../../v2/features/app/hooks/use-sidebar-documents.js';
|
|
3
|
+
import type { ImportDocumentFromRegistry } from '../../../../v2/types/configuration';
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
/** The app state used to read the active document and emit navigation events. */
|
|
6
|
+
app: AppState;
|
|
7
|
+
/**
|
|
8
|
+
* The list of all available registry documents. Used to merge advertised
|
|
9
|
+
* versions with loaded workspace documents so the picker can offer every
|
|
10
|
+
* known version of the active document.
|
|
11
|
+
*/
|
|
12
|
+
registryDocuments?: RegistryDocumentsState;
|
|
13
|
+
/**
|
|
14
|
+
* Fetcher used to import a version from the registry when the user picks
|
|
15
|
+
* one that has not been loaded into the local workspace store yet.
|
|
16
|
+
*/
|
|
17
|
+
fetchRegistryDocument?: ImportDocumentFromRegistry;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
20
|
+
createWorkspace: () => any;
|
|
21
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
22
|
+
onCreateWorkspace?: (() => any) | undefined;
|
|
23
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
//# sourceMappingURL=DocumentBreadcrumb.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocumentBreadcrumb.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/DocumentBreadcrumb.vue"],"names":[],"mappings":"AAmlBA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,6BAA6B,CAAA;AAMpC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAA;AAG3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAA;AAI1E,KAAK,WAAW,GAAG;IACjB,iFAAiF;IACjF,GAAG,EAAE,QAAQ,CAAA;IACb;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,sBAAsB,CAAA;IAC1C;;;OAGG;IACH,qBAAqB,CAAC,EAAE,0BAA0B,CAAA;CACnD,CAAC;AAwtBF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_export-helper.js";
|
|
2
|
+
import DocumentBreadcrumb_vue_vue_type_script_setup_true_lang_default from "./DocumentBreadcrumb.vue.script.js";
|
|
3
|
+
/* empty css */
|
|
4
|
+
//#region src/v2/features/app/components/DocumentBreadcrumb.vue
|
|
5
|
+
var DocumentBreadcrumb_default = /* @__PURE__ */ _plugin_vue_export_helper_default(DocumentBreadcrumb_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-0fc16a7d"]]);
|
|
6
|
+
//#endregion
|
|
7
|
+
export { DocumentBreadcrumb_default as default };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=DocumentBreadcrumb.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocumentBreadcrumb.vue.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":""}
|