@scalar/api-client 3.3.1 → 3.5.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 +32 -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/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/helpers/oauth-callback.d.ts +69 -0
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-callback.d.ts.map +1 -0
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-callback.js +75 -0
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-callback.js.map +1 -0
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.d.ts +17 -0
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js +43 -44
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.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 +16 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-state.js","names":[],"sources":["../../../../src/v2/features/app/app-state.ts"],"sourcesContent":["import type { ScalarListboxOption, WorkspaceGroup } from '@scalar/components'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport { sortByOrder } from '@scalar/helpers/array/sort-by-order'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { migrateLocalStorageToIndexDb } from '@scalar/oas-utils/migrations'\nimport { createSidebarState, generateReverseIndex } from '@scalar/sidebar'\nimport type { Theme } from '@scalar/themes'\nimport { type WorkspaceStore, createWorkspaceStore } from '@scalar/workspace-store/client'\nimport {\n type OperationExampleMeta,\n type WorkspaceEventBus,\n createWorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { generateUniqueValue } from '@scalar/workspace-store/helpers/generate-unique-value'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport { createWorkspaceStorePersistence, getWorkspaceId } from '@scalar/workspace-store/persistence'\nimport { persistencePlugin } from '@scalar/workspace-store/plugins/client'\nimport { getActiveEnvironment } from '@scalar/workspace-store/request-example'\nimport type { Workspace, WorkspaceDocument } from '@scalar/workspace-store/schemas'\nimport { extensions } from '@scalar/workspace-store/schemas/extensions'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { Tab } from '@scalar/workspace-store/schemas/extensions/workspace/x-scalar-tabs'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport {\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n type ShallowRef,\n computed,\n readonly,\n ref,\n shallowRef,\n watch,\n} from 'vue'\nimport type { RouteLocationNormalizedGeneric, RouteLocationRaw, Router } from 'vue-router'\n\nimport type { ApiClientAppOptions } from '@/v2/features/app/helpers/create-api-client-app'\nimport { getRouteParam } from '@/v2/features/app/helpers/get-route-param'\nimport { groupWorkspacesByTeam } from '@/v2/features/app/helpers/group-workspaces'\nimport { useTheme } from '@/v2/features/app/hooks/use-theme'\nimport { getTabDetails } from '@/v2/helpers/get-tab-details'\nimport { slugify } from '@/v2/helpers/slugify'\nimport { workspaceStorage } from '@/v2/helpers/storage'\n\nimport { initializeAppEventHandlers } from './app-events'\nimport { canLoadWorkspace, filterWorkspacesByTeam } from './helpers/filter-workspaces'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\nexport type GetEntryByLocation = (location: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n}) =>\n | (TraversedEntry & {\n parent?: TraversedEntry | undefined\n })\n | undefined\n\ntype WorkspaceOption = ScalarListboxOption & { teamUid: string; namespace: string; slug: string }\n\n/** Defines the overall application state structure and its main feature modules */\nexport type AppState = {\n /** The workspace store */\n store: ShallowRef<WorkspaceStore | null>\n /** The sidebar management */\n sidebar: {\n /** The sidebar state */\n state: ReturnType<typeof createSidebarState<TraversedEntry>>\n /** The width of the sidebar */\n width: ComputedRef<number>\n /** Whether the sidebar is open */\n isOpen: Ref<boolean>\n /** Handles the selection of an item in the sidebar */\n handleSelectItem: (id: string) => void\n /** Handles the width update of the sidebar */\n handleSidebarWidthUpdate: (width: number) => void\n /** Gets the entry by location */\n getEntryByLocation: GetEntryByLocation\n }\n /** The tabs management */\n tabs: {\n /** The tabs state */\n state: Ref<Tab[]>\n /** The active tab index */\n activeTabIndex: Ref<number>\n /** Copies the URL of the tab at the given index to the clipboard */\n copyTabUrl: (index: number) => Promise<void>\n }\n /** The workspace management */\n workspace: {\n /** Creates a new workspace and navigates to it */\n create: (payload: {\n teamUid?: string\n namespace?: string\n slug?: string\n name: string\n }) => Promise<{ name: string; namespace: string; slug: string; teamUid: string } | undefined>\n /** All workspace list */\n workspaceList: Ref<WorkspaceOption[]>\n /** Filtered workspace list, based on the current teamUid */\n filteredWorkspaceList: ComputedRef<WorkspaceOption[]>\n /**\n * Groups workspaces into team and local categories for display in the workspace picker.\n * Team workspaces are shown first (when not on local team), followed by local workspaces.\n */\n workspaceGroups: ComputedRef<WorkspaceGroup[]>\n /** The currently active workspace */\n activeWorkspace: ShallowRef<{ id: string; label: string } | null>\n /** Navigates to the specified workspace */\n navigateToWorkspace: (namespace?: string, slug?: string) => Promise<void>\n /** Whether the workspace page is open */\n isOpen: ComputedRef<boolean>\n /**\n * Whether the currently active workspace is a team workspace (i.e. has a\n * `teamUid` other than `'local'`). Useful for gating team-only UI such as\n * the registry-backed document list and its loading state.\n */\n isTeamWorkspace: ComputedRef<boolean>\n }\n /** The workspace event bus for handling workspace-level events */\n eventBus: WorkspaceEventBus\n /** The router instance */\n router: Router\n /** The current route derived from the router */\n currentRoute: Ref<RouteLocationNormalizedGeneric | null>\n /** Whether the workspace is currently syncing */\n loading: Ref<boolean>\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n /** The currently active entities */\n activeEntities: {\n /** The namespace of the current entity, e.g. \"default\" or a custom namespace */\n namespace: Ref<string | undefined>\n /** The slug identifying the current workspace */\n workspaceSlug: Ref<string | undefined>\n /** The slug of the currently selected document in the workspace */\n documentSlug: Ref<string | undefined>\n /** The API path currently selected (e.g. \"/users/{id}\") */\n path: Ref<string | undefined>\n /** The HTTP method for the currently selected API path (e.g. GET, POST) */\n method: Ref<HttpMethod | undefined>\n /** The name of the currently selected example (for examples within an endpoint) */\n exampleName: Ref<string | undefined>\n /** The unique identifier for the selected team context (read-only; use setTeamUid to change) */\n teamUid: Readonly<Ref<string>>\n /** Sets the current team context by team UID */\n setTeamUid: (value: string) => void\n }\n /** The currently active environment */\n environment: ComputedRef<XScalarEnvironment>\n /** The currently active document */\n document: ComputedRef<WorkspaceDocument | null>\n /** Whether the current color mode is dark */\n isDarkMode: ComputedRef<boolean>\n /** The currently active theme */\n theme: {\n /** The computed CSS styles for the current theme, as a string */\n styles: ComputedRef<{ themeStyles: string; themeSlug: string }>\n /** The computed value for the <style> tag containing the current theme styles */\n themeStyleTag: ComputedRef<string>\n /** The custom themes to use */\n customThemes: MaybeRefOrGetter<Theme[]>\n }\n telemetry: Ref<boolean>\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n/** Default debounce delay in milliseconds for workspace store persistence. */\nconst DEFAULT_DEBOUNCE_DELAY = 1000\n/** Default sidebar width in pixels. */\nconst DEFAULT_SIDEBAR_WIDTH = 288\n\n// ---------------------------------------------------------------------------\n// App State\n// ---------------------------------------------------------------------------\nexport const createAppState = async ({\n router,\n fileLoader,\n fallbackThemeSlug = () => 'default',\n customThemes = () => [],\n telemetryDefault,\n options,\n}: {\n router: Router\n fileLoader?: LoaderPlugin\n customThemes?: MaybeRefOrGetter<Theme[]>\n fallbackThemeSlug?: MaybeRefOrGetter<string>\n telemetryDefault?: boolean\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n}): Promise<AppState> => {\n /** Workspace event bus for handling workspace-level events. */\n const eventBus = createWorkspaceEventBus({\n debug: import.meta.env.DEV,\n })\n\n const { workspace: persistence } = await createWorkspaceStorePersistence()\n\n /**\n * Run migration from localStorage to IndexedDB if needed\n * This happens once per user and transforms old data structure to new workspace format\n */\n await migrateLocalStorageToIndexDb()\n\n // ---------------------------------------------------------------------------\n // Active entities\n // ---------------------------------------------------------------------------\n const teamUid = ref<string>('local')\n const namespace = ref<string | undefined>(undefined)\n const workspaceSlug = ref<string | undefined>(undefined)\n const documentSlug = ref<string | undefined>(undefined)\n const method = ref<HttpMethod | undefined>(undefined)\n const path = ref<string | undefined>(undefined)\n const exampleName = ref<string | undefined>(undefined)\n\n // ---------------------------------------------------------------------------\n // Loading states\n const isSyncingWorkspace = ref(false)\n\n // ---------------------------------------------------------------------------\n // Router state\n router.afterEach((to) => handleRouteChange(to))\n const currentRoute = computed(() => router.currentRoute.value ?? null)\n\n // ---------------------------------------------------------------------------\n // Workspace persistence state management\n const activeWorkspace = shallowRef<{ id: string; label: string } | null>(null)\n const workspaces = ref<WorkspaceOption[]>([])\n const filteredWorkspaces = computed(() => filterWorkspacesByTeam(workspaces.value, teamUid.value))\n const workspaceGroups = computed(() => groupWorkspacesByTeam(filteredWorkspaces.value, teamUid.value))\n /**\n * `true` when the active workspace is backed by a team (i.e. not the\n * built-in `'local'` team). We look the workspace up in the full\n * `workspaces` list because `activeWorkspace` only stores `{ id, label }`,\n * whereas `WorkspaceOption` carries the `teamUid` we need. Consumers can\n * read this via `app.workspace.isTeamWorkspace` to gate team-only UI.\n */\n const isTeamWorkspace = computed(() => {\n const id = activeWorkspace.value?.id\n if (!id) {\n return false\n }\n const workspace = workspaces.value.find((w) => w.id === id)\n return Boolean(workspace && workspace.teamUid !== 'local')\n })\n const store = shallowRef<WorkspaceStore | null>(null)\n\n // Load persisted telemetry preference, falling back to the provided default\n const persistedTelemetry = workspaceStorage.getTelemetry()\n const telemetry = ref(persistedTelemetry !== null ? persistedTelemetry : Boolean(telemetryDefault))\n watch(telemetry, (value) => workspaceStorage.setTelemetry(value))\n\n const activeDocument = computed(() => {\n return store.value?.workspace.documents[documentSlug.value ?? ''] || null\n })\n\n /**\n * Merged environment variables from workspace and document levels.\n * Variables from both sources are combined, with document variables\n * taking precedence in case of naming conflicts.\n */\n const environment = computed<XScalarEnvironment>(\n () => getActiveEnvironment(store.value, activeDocument.value).environment,\n )\n\n /** Update the workspace list when the component is mounted */\n workspaces.value = await persistence.getAll().then((w) =>\n w.map(({ teamUid, namespace, slug, name }) => ({\n id: getWorkspaceId(namespace, slug),\n teamUid,\n namespace,\n slug,\n label: name,\n })),\n )\n\n /**\n * Renames the currently active workspace.\n * Updates the workspace name in persistence and updates activeWorkspace if successful.\n * Returns early if namespace or workspaceSlug is not set, or if update fails.\n */\n const renameWorkspace = async (name: string) => {\n const namespaceValue = namespace.value\n const slugValue = workspaceSlug.value\n if (!namespaceValue || !slugValue) {\n return\n }\n const workspaceId = getWorkspaceId(namespaceValue, slugValue)\n const updateResult = await persistence.updateName({ namespace: namespaceValue, slug: slugValue }, name)\n\n // If the update fails, return early\n if (updateResult === undefined) {\n return\n }\n\n // Update the workspace list\n workspaces.value = workspaces.value.map((workspace) => {\n // If the workspace is the currently active workspace, update the label\n if (workspace.id === workspaceId) {\n return { ...workspace, label: name }\n }\n return workspace\n })\n activeWorkspace.value = { id: workspaceId, label: name }\n }\n\n /**\n * Creates a client-side workspace store with persistence enabled for the given workspace id.\n */\n const createClientStore = async ({\n namespace,\n slug,\n }: {\n namespace: string\n slug: string\n }): Promise<WorkspaceStore> => {\n return createWorkspaceStore({\n plugins: [\n await persistencePlugin({\n workspaceId: getWorkspaceId(namespace, slug),\n debounceDelay: DEFAULT_DEBOUNCE_DELAY,\n }),\n ],\n fileLoader,\n fetch: options?.customFetch,\n })\n }\n\n /**\n * Attempts to load and activate a workspace by id.\n * Returns true when the workspace was found and activated.\n */\n const loadWorkspace = async (\n namespace: string,\n slug: string,\n ): Promise<{ success: true; workspace: Workspace } | { success: false }> => {\n const workspace = await persistence.getItem({ namespace, slug })\n\n if (!workspace) {\n return {\n success: false,\n }\n }\n\n const client = await createClientStore({ namespace, slug })\n client.loadWorkspace(workspace.workspace)\n activeWorkspace.value = { id: getWorkspaceId(workspace.namespace, workspace.slug), label: workspace.name }\n store.value = client\n\n return {\n success: true,\n workspace: client.workspace,\n }\n }\n\n /**\n * Creates and persists the default workspace with a blank draft document.\n * Used when no workspaces exist yet.\n */\n const createAndPersistWorkspace = async ({\n name,\n teamUid,\n namespace,\n slug,\n }: {\n name: string\n teamUid?: string\n namespace?: string\n slug: string\n }) => {\n const draftStore = createWorkspaceStore()\n await draftStore.addDocument({\n name: 'drafts',\n document: {\n openapi: '3.1.0',\n info: {\n title: 'Drafts',\n version: '1.0.0',\n },\n paths: {\n '/': {\n get: {},\n },\n },\n 'x-scalar-original-document-hash': 'drafts',\n 'x-scalar-icon': 'interface-edit-tool-pencil',\n },\n })\n\n // Persist the workspace\n const workspace = await persistence.setItem(\n { namespace, slug },\n {\n name: name,\n teamUid,\n workspace: draftStore.exportWorkspace(),\n },\n )\n\n // Update the workspace list\n workspaces.value.push({\n id: getWorkspaceId(workspace.namespace, workspace.slug),\n teamUid: workspace.teamUid,\n namespace: workspace.namespace,\n slug: workspace.slug,\n label: workspace.name,\n })\n return workspace\n }\n\n /**\n * Navigates to the overview page of the specified workspace.\n *\n * @param namespace - The workspace namespace.\n * @param slug - The unique workspace slug (identifier).\n */\n const navigateToWorkspace = async (namespace?: string, slug?: string): Promise<void> => {\n if (!namespace || !slug) {\n await router.push('/')\n return\n }\n\n // We should always have this drafts document available in a new workspace\n await router.push({\n name: 'example',\n params: {\n namespace,\n workspaceSlug: slug,\n documentSlug: 'drafts',\n pathEncoded: encodeURIComponent('/'),\n method: 'get',\n exampleName: 'default',\n },\n })\n }\n\n /**\n * Creates a new workspace with the provided name.\n * - Generates a unique slug for the workspace (uses the provided slug if it is unique, otherwise generates a unique slug).\n * - Adds a default blank document (\"drafts\") to the workspace.\n * - Persists the workspace and navigates to it.\n *\n * Example usage:\n * await createWorkspace({ name: 'My Awesome API' })\n * // -> Navigates to /workspace/my-awesome-api (if available)\n */\n const createWorkspace = async ({\n teamUid,\n namespace,\n slug,\n name,\n }: {\n teamUid?: string\n namespace?: string\n slug?: string\n name: string\n }) => {\n // Clear up the current store, in order to show the loading state\n store.value = null\n\n // Generate a unique slug/id for the workspace, based on the name.\n const newWorkspaceSlug = await generateUniqueValue({\n defaultValue: slug ?? name, // Use the provided id if it exists, otherwise use the name\n validation: async (value) => !(await persistence.has({ namespace: namespace ?? 'local', slug: value })),\n maxRetries: 100,\n transformation: slugify,\n })\n\n // Failed to generate a unique workspace id, so we can't create the workspace.\n if (!newWorkspaceSlug) {\n return undefined\n }\n\n const newWorkspaceDetails = {\n teamUid,\n namespace,\n slug: newWorkspaceSlug,\n name,\n }\n\n // Create a new client store with the workspace ID and add a default document.\n const createdWorkspace = await createAndPersistWorkspace(newWorkspaceDetails)\n\n // Navigate to the newly created workspace.\n await navigateToWorkspace(createdWorkspace.namespace, createdWorkspace.slug)\n return createdWorkspace\n }\n\n /**\n * Handles changing the active workspace when the workspace slug changes in the route.\n * This function:\n * - Clears the current workspace store and sets loading state.\n * - Attempts to load the workspace by slug.\n * - If found, navigates to the active tab path (if available).\n * - If not found, creates the default workspace and navigates to it.\n */\n const changeWorkspace = async (namespace: string, slug: string, to?: RouteLocationNormalizedGeneric) => {\n /** For initial load we want to fall through to our router default behaviour */\n const isInitialLoad = activeWorkspace.value === null\n\n // Clear the current store and set loading to true before loading new workspace.\n store.value = null\n isSyncingWorkspace.value = true\n\n // Try to load the workspace\n const result = await loadWorkspace(namespace, slug)\n\n if (result.success) {\n // Navigate to the correct tab if the workspace has a tab already\n const index = result.workspace['x-scalar-active-tab'] ?? 0\n const tabs = result.workspace['x-scalar-tabs']\n const tab = tabs?.[index]\n\n // On initial load let the URL-based routing (catch-all → getLastPath) take precedence\n if (tab && !isInitialLoad) {\n // Preserve query parameters when navigating to the active tab\n await router.replace({\n path: tab.path,\n query: currentRoute.value?.query ?? {},\n })\n }\n\n // Heal the active tab index if it is out of bounds\n if (tabs && index >= tabs.length) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-active-tab': 0,\n })\n }\n\n // Initialize the tabs if they does not exist\n if (!tabs) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-tabs': [createTabFromRoute(currentRoute.value)],\n 'x-scalar-active-tab': 0,\n })\n }\n\n // On initial load the router.replace above is skipped, so syncTabs/syncSidebar\n // are never reached via handleRouteChange's normal flow. Call them here to\n // align the tab bar and sidebar with the URL-based route.\n if (isInitialLoad && to) {\n syncTabs(to)\n syncSidebar(to)\n }\n\n isSyncingWorkspace.value = false\n return\n }\n\n // Navigate to the default workspace, or fall back to the first available workspace\n const targetWorkspace =\n filteredWorkspaces.value.find((workspace) => workspace.teamUid === 'local' && workspace.slug === 'default') ??\n filteredWorkspaces.value[0]\n\n if (targetWorkspace) {\n return navigateToWorkspace(targetWorkspace.namespace, targetWorkspace.slug)\n }\n\n // If loading failed (workspace does not exist), create the default workspace and navigate to it.\n const createResult = await createWorkspace({\n name: 'Default Workspace',\n slug: 'default',\n })\n\n isSyncingWorkspace.value = false\n\n if (!createResult) {\n return console.error('Failed to create the default workspace, something went wrong, can not load the workspace')\n }\n\n // Must reset the sidebar state when the workspace changes\n sidebarState.reset()\n }\n\n /**\n * Sets the current team context. If the active workspace is not accessible\n * with the new team, navigates to the default workspace for that team.\n */\n const setTeamUid = (value: string) => {\n // Update the new teamUid\n teamUid.value = value\n\n // Find the current workspace\n const workspace = filteredWorkspaces.value.find(\n (w) => w.namespace === namespace.value && w.slug === workspaceSlug.value,\n )\n\n // Check if new teamUid is accessible to the current workspace\n if (workspace && canLoadWorkspace(workspace.teamUid, value)) {\n return\n }\n\n return navigateToWorkspace('local', 'default')\n }\n\n // ---------------------------------------------------------------------------\n // Sidebar state management\n\n const entries = computed(() => {\n const activeStore = store.value\n if (!activeStore) {\n return []\n }\n\n const order = activeStore.workspace['x-scalar-order'] ?? Object.keys(activeStore.workspace.documents)\n\n return sortByOrder(Object.keys(activeStore.workspace.documents), order, (item) => item)\n .map((doc) => activeStore.workspace.documents[doc]?.['x-scalar-navigation'])\n .filter(isDefined) as TraversedEntry[]\n })\n\n const sidebarState = createSidebarState(entries)\n\n /**\n * Generates a unique string ID for an API location, based on the document, path, method, and example.\n * Filters out undefined values and serializes the composite array into a stable string.\n *\n * @param params - An object containing document, path, method, and optional example name.\n * @returns A stringified array representing the unique location identifier.\n *\n * Example:\n * generateId({ document: 'mydoc', path: '/users', method: 'get', example: 'default' })\n * // => '[\"mydoc\",\"/users\",\"get\",\"default\"]'\n */\n const generateId = ({\n document,\n path,\n method,\n example,\n }: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n }) => {\n return JSON.stringify([document, path, method, example].filter(isDefined))\n }\n\n /**\n * Computed index for fast lookup of sidebar nodes by their unique API location.\n *\n * - Only indexes nodes of type 'document', 'operation', or 'example'.\n * - The lookup key is a serialized array of: [documentName, operationPath, operationMethod, exampleName?].\n * - Supports precise resolution of sidebar entries given an API \"location\".\n */\n const locationIndex = computed(() =>\n generateReverseIndex({\n items: entries.value,\n nestedKey: 'children',\n filter: (node) => node.type === 'document' || node.type === 'operation' || node.type === 'example',\n getId: (node) => {\n const document = getParentEntry('document', node)\n const operation = getParentEntry('operation', node)\n return generateId({\n document: document?.name ?? '',\n path: operation?.path,\n method: operation?.method,\n example: node.type === 'example' ? node.name : undefined,\n })\n },\n }),\n )\n\n /**\n * Looks up a sidebar entry by its unique API location.\n * - First tries to find an entry matching all provided properties (including example).\n * - If not found, falls back to matching only the operation (ignores example field).\n * This allows resolving either examples, operations, or documents as appropriate.\n *\n * @param location - Object specifying the document name, path, method, and optional example name.\n * @returns The matching sidebar entry, or undefined if none found.\n *\n * Example:\n * const entry = getEntryByLocation({\n * document: 'pets',\n * path: '/pets',\n * method: 'get',\n * example: 'default',\n * })\n */\n const getEntryByLocation: GetEntryByLocation = (location) => {\n // Try to find an entry with the most-specific location (including example)\n const entryWithExample = locationIndex.value.get(generateId(location))\n\n if (entryWithExample) {\n return entryWithExample\n }\n\n // Fallback to the operation (ignoring example) if an example wasn't found or specified\n return locationIndex.value.get(\n generateId({\n document: location.document,\n path: location.path,\n method: location.method,\n }),\n )\n }\n\n /**\n * Handles item selection from the sidebar and routes navigation accordingly.\n *\n * Example:\n * handleSelectItem('id-of-entry')\n */\n const handleSelectItem = (id: string) => {\n const entry = sidebarState.getEntryById(id)\n\n if (!entry) {\n console.warn(`Could not find sidebar entry with id ${id} to select`)\n return\n }\n\n /** Close sidebar and navigate. Used for every branch that performs navigation. */\n const navigate = (route: RouteLocationRaw) => {\n isSidebarOpen.value = false\n return router.push(route)\n }\n\n // Navigate to the document overview page\n if (entry.type === 'document') {\n // If we are already in the document, just toggle expansion\n if (sidebarState.selectedItem.value === id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select it\n sidebarState.setSelected(id)\n sidebarState.setExpanded(id, true)\n return navigate({\n name: 'document.overview',\n params: { documentSlug: entry.name },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'operation') {\n // If we are already in an operation child we just want to toggle the explanstion\n if (sidebarState.isSelected(id) && sidebarState.selectedItem.value !== id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select the first example\n const firstExample = entry.children?.find((child) => child.type === 'example')\n\n if (firstExample) {\n sidebarState.setSelected(firstExample.id)\n sidebarState.setExpanded(firstExample.id, true)\n } else {\n sidebarState.setSelected(id)\n }\n\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(entry.path),\n method: entry.method,\n exampleName: firstExample?.name ?? 'default',\n },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'example') {\n sidebarState.setSelected(id)\n const operation = getParentEntry('operation', entry)\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(operation?.path ?? ''),\n method: operation?.method,\n exampleName: entry.name,\n },\n })\n }\n\n if (entry.type === 'text') {\n return navigate({\n name: 'document.overview',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n },\n })\n }\n\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n /**\n * Navigates to the currently active tab's path using the router.\n * Returns early if the workspace store or active tab is unavailable.\n */\n const navigateToCurrentTab = async (): Promise<void> => {\n if (!store.value) {\n return\n }\n\n const activeTabIndex = store.value.workspace['x-scalar-active-tab'] ?? 0\n const activeTab = store.value.workspace['x-scalar-tabs']?.[activeTabIndex]\n if (!activeTab) {\n return\n }\n\n await router.replace(activeTab.path)\n }\n\n /**\n * Rebuilds the sidebar for the given document.\n * This is used to refresh the sidebar state after structural changes (e.g. after adding or removing items).\n *\n * @param documentName - The name (id) of the document for which to rebuild the sidebar\n */\n const rebuildSidebar = (documentName: string | undefined) => {\n if (documentName) {\n store.value?.buildSidebar(documentName)\n }\n }\n\n /**\n * Ensures the sidebar is refreshed after a new example is created.\n *\n * If the sidebar entry for the new example does not exist or is of a different type,\n * this will rebuild the sidebar for the current document. This helps keep the sidebar state\n * consistent (e.g., after adding a new example via the UI).\n */\n const refreshSidebarAfterExampleCreation = (payload: OperationExampleMeta & { documentName?: string }) => {\n const documentName = payload.documentName ?? activeDocument.value?.['x-scalar-navigation']?.name\n if (!documentName) {\n return\n }\n\n const entry = getEntryByLocation({\n document: documentName,\n path: payload.path,\n method: payload.method,\n example: payload.exampleKey,\n })\n\n if (!entry || entry.type !== 'example') {\n // Sidebar entry for this example doesn't exist, so rebuild sidebar for consistency.\n rebuildSidebar(documentName)\n if (currentRoute.value) {\n syncSidebar(currentRoute.value)\n }\n }\n return\n }\n\n /** Width of the sidebar, with fallback to default. */\n const sidebarWidth = computed(() => store.value?.workspace?.['x-scalar-sidebar-width'] ?? DEFAULT_SIDEBAR_WIDTH)\n\n /** Handler for sidebar width changes. */\n const handleSidebarWidthUpdate = (width: number) => store.value?.update('x-scalar-sidebar-width', width)\n\n /** Controls the visibility of the sidebar. */\n const isSidebarOpen = ref(false)\n // ---------------------------------------------------------------------------\n // Tab Management\n\n /** Constants for workspace store keys */\n const TABS_KEY = 'x-scalar-tabs' as const\n const ACTIVE_TAB_KEY = 'x-scalar-active-tab' as const\n\n /**\n * Creates a tab object based on the current route and workspace state.\n * Used as a fallback when no tabs exist or when creating new tabs.\n */\n const createTabFromRoute = (to: RouteLocationNormalizedGeneric | null): Tab => {\n const method = getRouteParam('method', to)\n const path = getRouteParam('pathEncoded', to)\n const document = getRouteParam('documentSlug', to)\n const workspace = getRouteParam('workspaceSlug', to)\n return {\n ...getTabDetails({\n workspace,\n document,\n path,\n method,\n getEntryByLocation,\n }),\n path: currentRoute.value?.path ?? '',\n }\n }\n\n const tabs = computed(() => {\n return store.value?.workspace[TABS_KEY] ?? [createTabFromRoute(currentRoute.value)]\n })\n\n const activeTabIndex = computed(() => {\n return store.value?.workspace[ACTIVE_TAB_KEY] ?? 0\n })\n\n /**\n * Copies the URL of the tab at the given index to the clipboard.\n * Constructs the full URL using the current origin and the tab path.\n *\n * Note: Will silently fail if clipboard API is unavailable or the tab does not exist.\n */\n const copyTabUrl = async (index: number): Promise<void> => {\n const tab = tabs.value[index]\n\n if (!tab) {\n console.warn(`Cannot copy URL: tab at index ${index} does not exist`)\n return\n }\n\n const url = `${window.location.origin}${tab.path}`\n\n try {\n await navigator.clipboard.writeText(url)\n } catch (error) {\n console.error('Failed to copy URL to clipboard:', error)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Path syncing\n\n /** When the route changes we need to update the active entities in the store */\n const handleRouteChange = (to: RouteLocationNormalizedGeneric) => {\n const slug = getRouteParam('workspaceSlug', to)\n const document = getRouteParam('documentSlug', to)\n const namespaceValue = getRouteParam('namespace', to)\n\n // Must have an active workspace to syncs\n if (!namespaceValue || !slug) {\n return\n }\n\n // Try to see if the user can load this workspace based on the teamUid and namespace\n const workspace = workspaces.value.find(\n (workspace) => workspace.slug === slug && workspace.namespace === namespaceValue,\n )\n\n // If the workspace is not found or the teamUid does not match, try to redirect to the default workspace\n if (workspace && !canLoadWorkspace(workspace.teamUid, teamUid.value)) {\n // try to redirect to the default workspace\n return navigateToWorkspace('local', 'default')\n }\n\n namespace.value = namespaceValue\n workspaceSlug.value = slug\n documentSlug.value = document\n method.value = getRouteParam('method', to)\n path.value = getRouteParam('pathEncoded', to)\n exampleName.value = getRouteParam('exampleName', to)\n\n // Save the current path to the persistence storage\n if (to.path !== '') {\n workspaceStorage.setCurrentPath(to.path)\n }\n\n if (getWorkspaceId(namespace.value, slug) !== activeWorkspace.value?.id) {\n return changeWorkspace(namespace.value, slug, to)\n }\n\n // Update the active document if the document slug has changes\n if (document && document !== store.value?.workspace[extensions.workspace.activeDocument]) {\n store?.value?.update('x-scalar-active-document', document)\n }\n\n syncTabs(to)\n syncSidebar(to)\n return\n }\n\n /** Aligns the tabs with any potential slug changes */\n const syncTabs = (to: RouteLocationNormalizedGeneric) => {\n const tabs = store.value?.workspace['x-scalar-tabs'] ?? []\n const index = store.value?.workspace['x-scalar-active-tab'] ?? 0\n\n const tab = tabs[index]\n\n // If there is no tab or the tab path is the same we leave the tab state alone\n if (!tab || tab.path === to.path) {\n // Already on the correct path, do nothing\n return\n }\n\n // Otherwise we replace the tab content with the new route\n tabs[index] = createTabFromRoute(to)\n }\n\n /** Aligns the sidebar state with any potential slug changes */\n const syncSidebar = (to: RouteLocationNormalizedGeneric) => {\n const document = getRouteParam('documentSlug', to)\n\n if (!document) {\n // Reset selection if no document is selected\n sidebarState.setSelected(null)\n return\n }\n\n const entry = getEntryByLocation({\n document,\n path: getRouteParam('pathEncoded', to),\n method: getRouteParam('method', to),\n example: getRouteParam('exampleName', to),\n })\n\n if (entry) {\n sidebarState.setSelected(entry.id)\n sidebarState.setExpanded(entry.id, true)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Events handling\n\n initializeAppEventHandlers({\n eventBus,\n router,\n store,\n navigateToCurrentTab,\n rebuildSidebar,\n onAfterExampleCreation: refreshSidebarAfterExampleCreation,\n onSelectSidebarItem: handleSelectItem,\n onCopyTabUrl: (index) => copyTabUrl(index),\n onToggleSidebar: () => (isSidebarOpen.value = !isSidebarOpen.value),\n closeSidebar: () => (isSidebarOpen.value = false),\n renameWorkspace,\n })\n\n const theme = useTheme({\n fallbackThemeSlug,\n customThemes,\n store: store,\n })\n\n const isDarkMode = computed(() => {\n const colorMode = store.value?.workspace['x-scalar-color-mode'] ?? 'system'\n if (colorMode === 'system') {\n return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false\n }\n return colorMode === 'dark'\n })\n\n return {\n /** Active workspace store */\n store,\n sidebar: {\n state: sidebarState,\n width: sidebarWidth,\n isOpen: isSidebarOpen,\n handleSelectItem,\n handleSidebarWidthUpdate,\n getEntryByLocation,\n },\n tabs: {\n state: tabs,\n activeTabIndex,\n copyTabUrl,\n },\n workspace: {\n create: createWorkspace,\n workspaceList: workspaces,\n filteredWorkspaceList: filteredWorkspaces,\n workspaceGroups,\n activeWorkspace,\n navigateToWorkspace,\n isOpen: computed(() => Boolean(workspaceSlug.value && !documentSlug.value)),\n isTeamWorkspace,\n },\n eventBus,\n router,\n currentRoute,\n loading: isSyncingWorkspace,\n activeEntities: {\n namespace,\n workspaceSlug,\n documentSlug,\n path,\n method,\n exampleName,\n teamUid: readonly(teamUid),\n setTeamUid,\n },\n environment,\n document: activeDocument,\n isDarkMode,\n theme: {\n styles: theme.themeStyles,\n themeStyleTag: theme.themeStyleTag,\n customThemes,\n },\n telemetry,\n options,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-state.js","names":[],"sources":["../../../../src/v2/features/app/app-state.ts"],"sourcesContent":["import type { ScalarListboxOption, WorkspaceGroup } from '@scalar/components'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport { sortByOrder } from '@scalar/helpers/array/sort-by-order'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { migrateLocalStorageToIndexDb } from '@scalar/oas-utils/migrations'\nimport { createSidebarState, generateReverseIndex } from '@scalar/sidebar'\nimport type { Theme } from '@scalar/themes'\nimport { type WorkspaceStore, createWorkspaceStore } from '@scalar/workspace-store/client'\nimport {\n type OperationExampleMeta,\n type WorkspaceEventBus,\n createWorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { generateUniqueValue } from '@scalar/workspace-store/helpers/generate-unique-value'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport { createWorkspaceStorePersistence, getWorkspaceId } from '@scalar/workspace-store/persistence'\nimport { persistencePlugin } from '@scalar/workspace-store/plugins/client'\nimport { getActiveEnvironment } from '@scalar/workspace-store/request-example'\nimport type { Workspace, WorkspaceDocument } from '@scalar/workspace-store/schemas'\nimport { extensions } from '@scalar/workspace-store/schemas/extensions'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { Tab } from '@scalar/workspace-store/schemas/extensions/workspace/x-scalar-tabs'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport {\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n type ShallowRef,\n computed,\n readonly,\n ref,\n shallowRef,\n watch,\n} from 'vue'\nimport type { RouteLocationNormalizedGeneric, RouteLocationRaw, Router } from 'vue-router'\n\nimport type { ApiClientAppOptions } from '@/v2/features/app/helpers/create-api-client-app'\nimport { getRouteParam } from '@/v2/features/app/helpers/get-route-param'\nimport { groupWorkspacesByTeam } from '@/v2/features/app/helpers/group-workspaces'\nimport { useTheme } from '@/v2/features/app/hooks/use-theme'\nimport { getTabDetails } from '@/v2/helpers/get-tab-details'\nimport { slugify } from '@/v2/helpers/slugify'\nimport { workspaceStorage } from '@/v2/helpers/storage'\n\nimport { initializeAppEventHandlers } from './app-events'\nimport { canLoadWorkspace, filterWorkspacesByTeam } from './helpers/filter-workspaces'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\nexport type GetEntryByLocation = (location: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n}) =>\n | (TraversedEntry & {\n parent?: TraversedEntry | undefined\n })\n | undefined\n\ntype WorkspaceOption = ScalarListboxOption & { teamSlug: string; slug: string }\n\n/** Defines the overall application state structure and its main feature modules */\nexport type AppState = {\n /** The workspace store */\n store: ShallowRef<WorkspaceStore | null>\n /** The sidebar management */\n sidebar: {\n /** The sidebar state */\n state: ReturnType<typeof createSidebarState<TraversedEntry>>\n /** The width of the sidebar */\n width: ComputedRef<number>\n /** Whether the sidebar is open */\n isOpen: Ref<boolean>\n /** Handles the selection of an item in the sidebar */\n handleSelectItem: (id: string) => void\n /** Handles the width update of the sidebar */\n handleSidebarWidthUpdate: (width: number) => void\n /** Gets the entry by location */\n getEntryByLocation: GetEntryByLocation\n }\n /** The tabs management */\n tabs: {\n /** The tabs state */\n state: Ref<Tab[]>\n /** The active tab index */\n activeTabIndex: Ref<number>\n /** Copies the URL of the tab at the given index to the clipboard */\n copyTabUrl: (index: number) => Promise<void>\n }\n /** The workspace management */\n workspace: {\n /** Creates a new workspace and navigates to it */\n create: (payload: {\n teamSlug?: string\n slug?: string\n name: string\n }) => Promise<{ name: string; slug: string; teamSlug: string } | undefined>\n /** All workspace list */\n workspaceList: Ref<WorkspaceOption[]>\n /** Filtered workspace list, based on the current teamSlug */\n filteredWorkspaceList: ComputedRef<WorkspaceOption[]>\n /**\n * Groups workspaces into team and local categories for display in the workspace picker.\n * Team workspaces are shown first (when not on local team), followed by local workspaces.\n */\n workspaceGroups: ComputedRef<WorkspaceGroup[]>\n /** The currently active workspace */\n activeWorkspace: ShallowRef<{ id: string; label: string } | null>\n /** Navigates to the specified workspace */\n navigateToWorkspace: (teamSlug?: string, slug?: string) => Promise<void>\n /** Whether the workspace page is open */\n isOpen: ComputedRef<boolean>\n /**\n * Whether the currently active workspace is a team workspace (i.e. has a\n * `teamUid` other than `'local'`). Useful for gating team-only UI such as\n * the registry-backed document list and its loading state.\n */\n isTeamWorkspace: ComputedRef<boolean>\n }\n /** The workspace event bus for handling workspace-level events */\n eventBus: WorkspaceEventBus\n /** The router instance */\n router: Router\n /** The current route derived from the router */\n currentRoute: Ref<RouteLocationNormalizedGeneric | null>\n /** Whether the workspace is currently syncing */\n loading: Ref<boolean>\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n /** The currently active entities */\n activeEntities: {\n /** The slug identifying the current workspace */\n workspaceSlug: Ref<string | undefined>\n /** The slug of the currently selected document in the workspace */\n documentSlug: Ref<string | undefined>\n /** The API path currently selected (e.g. \"/users/{id}\") */\n path: Ref<string | undefined>\n /** The HTTP method for the currently selected API path (e.g. GET, POST) */\n method: Ref<HttpMethod | undefined>\n /** The name of the currently selected example (for examples within an endpoint) */\n exampleName: Ref<string | undefined>\n /** The slug of the selected team context (read-only; use setTeamSlug to change) */\n teamSlug: Readonly<Ref<string>>\n /** Sets the current team context by team slug */\n setTeamSlug: (value: string) => void\n }\n /** The currently active environment */\n environment: ComputedRef<XScalarEnvironment>\n /** The currently active document */\n document: ComputedRef<WorkspaceDocument | null>\n /** Whether the current color mode is dark */\n isDarkMode: ComputedRef<boolean>\n /** The currently active theme */\n theme: {\n /** The computed CSS styles for the current theme, as a string */\n styles: ComputedRef<{ themeStyles: string; themeSlug: string }>\n /** The computed value for the <style> tag containing the current theme styles */\n themeStyleTag: ComputedRef<string>\n /** The custom themes to use */\n customThemes: MaybeRefOrGetter<Theme[]>\n }\n telemetry: Ref<boolean>\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n/** Default debounce delay in milliseconds for workspace store persistence. */\nconst DEFAULT_DEBOUNCE_DELAY = 1000\n/** Default sidebar width in pixels. */\nconst DEFAULT_SIDEBAR_WIDTH = 288\n/** Default slug used when auto-creating a team workspace on demand. */\nexport const DEFAULT_TEAM_WORKSPACE_SLUG = 'default'\n/** Default display name used when auto-creating a team workspace on demand. */\nconst DEFAULT_TEAM_WORKSPACE_NAME = 'Workspace'\n/**\n * Temporary kill switch for team workspace functionality.\n *\n * While the team workspace experience is still being polished we hide the\n * \"Team Workspaces\" group from the picker and refuse to create new team\n * workspaces. Existing team workspaces remain in storage and can be restored\n * by flipping this flag back to `true`.\n */\nexport const TEAM_WORKSPACES_ENABLED = false\n\n// ---------------------------------------------------------------------------\n// App State\n// ---------------------------------------------------------------------------\nexport const createAppState = async ({\n router,\n fileLoader,\n fallbackThemeSlug = () => 'default',\n customThemes = () => [],\n telemetryDefault,\n options,\n}: {\n router: Router\n fileLoader?: LoaderPlugin\n customThemes?: MaybeRefOrGetter<Theme[]>\n fallbackThemeSlug?: MaybeRefOrGetter<string>\n telemetryDefault?: boolean\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n}): Promise<AppState> => {\n /** Workspace event bus for handling workspace-level events. */\n const eventBus = createWorkspaceEventBus({\n debug: import.meta.env.DEV,\n })\n\n const { workspace: persistence } = await createWorkspaceStorePersistence()\n\n /**\n * Run migration from localStorage to IndexedDB if needed\n * This happens once per user and transforms old data structure to new workspace format\n */\n await migrateLocalStorageToIndexDb()\n\n // ---------------------------------------------------------------------------\n // Active entities\n // ---------------------------------------------------------------------------\n // Currently selected team context. Drives workspace filtering and team switching.\n const teamSlug = ref<string>('local')\n // Team slug parsed from the current URL (the `@teamSlug` segment). Stays in sync with the route.\n const routeTeamSlug = ref<string | undefined>(undefined)\n const workspaceSlug = ref<string | undefined>(undefined)\n const documentSlug = ref<string | undefined>(undefined)\n const method = ref<HttpMethod | undefined>(undefined)\n const path = ref<string | undefined>(undefined)\n const exampleName = ref<string | undefined>(undefined)\n\n // ---------------------------------------------------------------------------\n // Loading states\n const isSyncingWorkspace = ref(false)\n\n // ---------------------------------------------------------------------------\n // Router state\n router.afterEach((to) => handleRouteChange(to))\n const currentRoute = computed(() => router.currentRoute.value ?? null)\n\n // ---------------------------------------------------------------------------\n // Workspace persistence state management\n const activeWorkspace = shallowRef<{ id: string; label: string } | null>(null)\n const workspaces = ref<WorkspaceOption[]>([])\n const filteredWorkspaces = computed(() => filterWorkspacesByTeam(workspaces.value, teamSlug.value))\n const workspaceGroups = computed(() => {\n // While team workspaces are disabled we render the picker as if the user\n // were always on the local team. This hides the \"Team Workspaces\" section\n // (and any placeholder option) without removing the underlying data, so\n // re-enabling the feature is a one-line change.\n if (!TEAM_WORKSPACES_ENABLED) {\n return groupWorkspacesByTeam(filteredWorkspaces.value, 'local')\n }\n\n return groupWorkspacesByTeam(filteredWorkspaces.value, teamSlug.value, {\n // Surface a fake default workspace for non-local teams so logged-in\n // users always see a team workspace entry in the picker. Clicking it\n // navigates to a normal workspace route; the route handler creates the\n // workspace on demand when it does not yet exist.\n placeholder: {\n slug: DEFAULT_TEAM_WORKSPACE_SLUG,\n label: DEFAULT_TEAM_WORKSPACE_NAME,\n },\n })\n })\n /**\n * `true` when the active workspace is backed by a team (i.e. not the\n * built-in `'local'` team). We look the workspace up in the full\n * `workspaces` list because `activeWorkspace` only stores `{ id, label }`,\n * whereas `WorkspaceOption` carries the `teamUid` we need. Consumers can\n * read this via `app.workspace.isTeamWorkspace` to gate team-only UI.\n */\n const isTeamWorkspace = computed(() => {\n const id = activeWorkspace.value?.id\n if (!id) {\n return false\n }\n const workspace = workspaces.value.find((w) => w.id === id)\n return Boolean(workspace && workspace.teamSlug !== 'local')\n })\n const store = shallowRef<WorkspaceStore | null>(null)\n\n // Load persisted telemetry preference, falling back to the provided default\n const persistedTelemetry = workspaceStorage.getTelemetry()\n const telemetry = ref(persistedTelemetry !== null ? persistedTelemetry : Boolean(telemetryDefault))\n watch(telemetry, (value) => workspaceStorage.setTelemetry(value))\n\n const activeDocument = computed(() => {\n return store.value?.workspace.documents[documentSlug.value ?? ''] || null\n })\n\n /**\n * Merged environment variables from workspace and document levels.\n * Variables from both sources are combined, with document variables\n * taking precedence in case of naming conflicts.\n */\n const environment = computed<XScalarEnvironment>(\n () => getActiveEnvironment(store.value, activeDocument.value).environment,\n )\n\n /** Update the workspace list when the component is mounted */\n workspaces.value = await persistence.getAll().then((w) =>\n w.map(({ teamSlug, slug, name }) => ({\n id: getWorkspaceId(teamSlug, slug),\n teamSlug,\n slug,\n label: name,\n })),\n )\n\n /**\n * Renames the currently active workspace.\n * Updates the workspace name in persistence and updates activeWorkspace if successful.\n * Returns early if team slug or workspaceSlug is not set, or if update fails.\n */\n const renameWorkspace = async (name: string) => {\n const teamSlugValue = routeTeamSlug.value\n const slugValue = workspaceSlug.value\n if (!teamSlugValue || !slugValue) {\n return\n }\n const workspaceId = getWorkspaceId(teamSlugValue, slugValue)\n const updateResult = await persistence.updateName({ teamSlug: teamSlugValue, slug: slugValue }, name)\n\n // If `the update fails, return early\n if (updateResult === undefined) {\n return\n }\n\n // Update the workspace list\n workspaces.value = workspaces.value.map((workspace) => {\n // If the workspace is the currently active workspace, update the label\n if (workspace.id === workspaceId) {\n return { ...workspace, label: name }\n }\n return workspace\n })\n activeWorkspace.value = { id: workspaceId, label: name }\n }\n\n /**\n * Creates a client-side workspace store with persistence enabled for the given workspace id.\n */\n const createClientStore = async ({ teamSlug, slug }: { teamSlug: string; slug: string }): Promise<WorkspaceStore> => {\n return createWorkspaceStore({\n plugins: [\n await persistencePlugin({\n workspaceId: getWorkspaceId(teamSlug, slug),\n debounceDelay: DEFAULT_DEBOUNCE_DELAY,\n }),\n ],\n fileLoader,\n fetch: options?.customFetch,\n })\n }\n\n /**\n * Attempts to load and activate a workspace by id.\n * Returns true when the workspace was found and activated.\n */\n const loadWorkspace = async (\n teamSlug: string,\n slug: string,\n ): Promise<{ success: true; workspace: Workspace } | { success: false }> => {\n const workspace = await persistence.getItem({ teamSlug, slug })\n\n if (!workspace) {\n return {\n success: false,\n }\n }\n\n const client = await createClientStore({ teamSlug, slug })\n client.loadWorkspace(workspace.workspace)\n activeWorkspace.value = { id: getWorkspaceId(workspace.teamSlug, workspace.slug), label: workspace.name }\n store.value = client\n\n return {\n success: true,\n workspace: client.workspace,\n }\n }\n\n /**\n * Creates and persists the default workspace with a blank draft document.\n * Used when no workspaces exist yet.\n */\n const createAndPersistWorkspace = async ({\n name,\n teamSlug,\n slug,\n }: {\n name: string\n teamSlug?: string\n slug: string\n }) => {\n const draftStore = createWorkspaceStore()\n await draftStore.addDocument({\n name: 'drafts',\n document: {\n openapi: '3.1.0',\n info: {\n title: 'Drafts',\n version: '1.0.0',\n },\n paths: {\n '/': {\n get: {\n summary: 'First Request',\n },\n },\n },\n 'x-scalar-original-document-hash': 'drafts',\n 'x-scalar-icon': 'interface-edit-tool-pencil',\n },\n })\n\n // Persist the workspace\n const workspace = await persistence.setItem(\n { teamSlug, slug },\n {\n name: name,\n workspace: draftStore.exportWorkspace(),\n },\n )\n\n // Update the workspace list\n workspaces.value.push({\n id: getWorkspaceId(workspace.teamSlug, workspace.slug),\n teamSlug: workspace.teamSlug,\n slug: workspace.slug,\n label: workspace.name,\n })\n return workspace\n }\n\n /**\n * Navigates to the overview page of the specified workspace.\n *\n * @param teamSlug - The slug of the team that owns the workspace (used as the URL `@teamSlug` segment).\n * @param slug - The unique workspace slug (identifier).\n */\n const navigateToWorkspace = async (teamSlug?: string, slug?: string): Promise<void> => {\n if (!teamSlug || !slug) {\n await router.push('/')\n return\n }\n\n // We should always have this drafts document available in a new workspace\n await router.push({\n name: 'example',\n params: {\n teamSlug,\n workspaceSlug: slug,\n documentSlug: 'drafts',\n pathEncoded: encodeURIComponent('/'),\n method: 'get',\n exampleName: 'default',\n },\n })\n }\n\n /**\n * Creates a new workspace with the provided name.\n * - Generates a unique slug for the workspace (uses the provided slug if it is unique, otherwise generates a unique slug).\n * - Adds a default blank document (\"drafts\") to the workspace.\n * - Persists the workspace and navigates to it.\n *\n * Example usage:\n * await createWorkspace({ name: 'My Awesome API' })\n * // -> Navigates to /workspace/my-awesome-api (if available)\n */\n const createWorkspace = async ({ teamSlug, slug, name }: { teamSlug?: string; slug?: string; name: string }) => {\n // Block team workspace creation while the feature is disabled. If a team\n // workspace already exists we silently navigate to it (e.g. when the route\n // handler tries to auto-create on demand); otherwise we fall back to the\n // local default so the user lands somewhere usable.\n if (!TEAM_WORKSPACES_ENABLED && teamSlug && teamSlug !== 'local') {\n const existing = workspaces.value.find((w) => w.teamSlug === teamSlug)\n if (existing) {\n await navigateToWorkspace(existing.teamSlug, existing.slug)\n return { teamSlug: existing.teamSlug, slug: existing.slug, name: existing.label }\n }\n console.warn('Team workspace creation is currently disabled. Falling back to the local default workspace.')\n await navigateToWorkspace('local', 'default')\n return undefined\n }\n\n // Restrict users to a single workspace per team. Local workspaces remain\n // unrestricted. This guard is temporary while multi-workspace support for\n // teams is being designed. When a team workspace already exists, navigate\n // to it instead of creating a duplicate.\n if (teamSlug && teamSlug !== 'local') {\n const existing = workspaces.value.find((w) => w.teamSlug === teamSlug)\n if (existing) {\n console.warn(`A workspace already exists for team \"${teamSlug}\". Navigating to the existing workspace instead.`)\n await navigateToWorkspace(existing.teamSlug, existing.slug)\n return { teamSlug: existing.teamSlug, slug: existing.slug, name: existing.label }\n }\n }\n\n // Clear up the current store, in order to show the loading state\n store.value = null\n\n // Generate a unique slug/id for the workspace, based on the name.\n const newWorkspaceSlug = await generateUniqueValue({\n defaultValue: slug ?? name, // Use the provided id if it exists, otherwise use the name\n validation: async (value) => !(await persistence.has({ teamSlug: teamSlug ?? 'local', slug: value })),\n maxRetries: 100,\n transformation: slugify,\n })\n\n // Failed to generate a unique workspace id, so we can't create the workspace.\n if (!newWorkspaceSlug) {\n return undefined\n }\n\n const newWorkspaceDetails = {\n teamSlug,\n slug: newWorkspaceSlug,\n name,\n }\n\n // Create a new client store with the workspace ID and add a default document.\n const createdWorkspace = await createAndPersistWorkspace(newWorkspaceDetails)\n\n // Navigate to the newly created workspace.\n await navigateToWorkspace(createdWorkspace.teamSlug, createdWorkspace.slug)\n return createdWorkspace\n }\n\n /**\n * Handles changing the active workspace when the workspace slug changes in the route.\n * This function:\n * - Clears the current workspace store and sets loading state.\n * - Attempts to load the workspace by slug.\n * - If found, navigates to the active tab path (if available).\n * - If not found, creates the default workspace and navigates to it.\n */\n const changeWorkspace = async (teamSlug: string, slug: string, to?: RouteLocationNormalizedGeneric) => {\n /** For initial load we want to fall through to our router default behaviour */\n const isInitialLoad = activeWorkspace.value === null\n\n // Clear the current store and set loading to true before loading new workspace.\n store.value = null\n isSyncingWorkspace.value = true\n\n // Try to load the workspace\n const result = await loadWorkspace(teamSlug, slug)\n\n if (result.success) {\n // Navigate to the correct tab if the workspace has a tab already\n const index = result.workspace['x-scalar-active-tab'] ?? 0\n const tabs = result.workspace['x-scalar-tabs']\n const tab = tabs?.[index]\n\n // On initial load let the URL-based routing (catch-all → getLastPath) take precedence\n if (tab && !isInitialLoad) {\n // Preserve query parameters when navigating to the active tab\n await router.replace({\n path: tab.path,\n query: currentRoute.value?.query ?? {},\n })\n }\n\n // Heal the active tab index if it is out of bounds\n if (tabs && index >= tabs.length) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-active-tab': 0,\n })\n }\n\n // Initialize the tabs if they does not exist\n if (!tabs) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-tabs': [createTabFromRoute(currentRoute.value)],\n 'x-scalar-active-tab': 0,\n })\n }\n\n // On initial load the router.replace above is skipped, so syncTabs/syncSidebar\n // are never reached via handleRouteChange's normal flow. Call them here to\n // align the tab bar and sidebar with the URL-based route.\n if (isInitialLoad && to) {\n syncTabs(to)\n syncSidebar(to)\n }\n\n isSyncingWorkspace.value = false\n return\n }\n\n // Navigate to the default workspace, or fall back to the first available workspace\n const targetWorkspace =\n filteredWorkspaces.value.find((workspace) => workspace.teamSlug === 'local' && workspace.slug === 'default') ??\n filteredWorkspaces.value[0]\n\n if (targetWorkspace) {\n return navigateToWorkspace(targetWorkspace.teamSlug, targetWorkspace.slug)\n }\n\n // If loading failed (workspace does not exist), create the default workspace and navigate to it.\n const createResult = await createWorkspace({\n name: 'Default Workspace',\n slug: 'default',\n })\n\n isSyncingWorkspace.value = false\n\n if (!createResult) {\n return console.error('Failed to create the default workspace, something went wrong, can not load the workspace')\n }\n\n // Must reset the sidebar state when the workspace changes\n sidebarState.reset()\n }\n\n /**\n * Sets the current team context. If the active workspace is not accessible\n * with the new team, navigates to the default workspace for that team.\n */\n const setTeamSlug = (value: string) => {\n // Update the active team slug.\n teamSlug.value = value\n\n // Find the workspace that matches the current route.\n const workspace = filteredWorkspaces.value.find(\n (w) => w.teamSlug === routeTeamSlug.value && w.slug === workspaceSlug.value,\n )\n\n // Check if the new team slug is accessible to the current workspace.\n if (workspace && canLoadWorkspace(workspace.teamSlug, value)) {\n return\n }\n\n // When the user is on a workspace on another team or the workspace is not accessible, redirect to the default workspace.\n return navigateToWorkspace('local', 'default')\n }\n\n // ---------------------------------------------------------------------------\n // Sidebar state management\n\n const entries = computed(() => {\n const activeStore = store.value\n if (!activeStore) {\n return []\n }\n\n const order = activeStore.workspace['x-scalar-order'] ?? Object.keys(activeStore.workspace.documents)\n\n return sortByOrder(Object.keys(activeStore.workspace.documents), order, (item) => item)\n .map((doc) => activeStore.workspace.documents[doc]?.['x-scalar-navigation'])\n .filter(isDefined) as TraversedEntry[]\n })\n\n const sidebarState = createSidebarState(entries)\n\n /**\n * Generates a unique string ID for an API location, based on the document, path, method, and example.\n * Filters out undefined values and serializes the composite array into a stable string.\n *\n * @param params - An object containing document, path, method, and optional example name.\n * @returns A stringified array representing the unique location identifier.\n *\n * Example:\n * generateId({ document: 'mydoc', path: '/users', method: 'get', example: 'default' })\n * // => '[\"mydoc\",\"/users\",\"get\",\"default\"]'\n */\n const generateId = ({\n document,\n path,\n method,\n example,\n }: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n }) => {\n return JSON.stringify([document, path, method, example].filter(isDefined))\n }\n\n /**\n * Computed index for fast lookup of sidebar nodes by their unique API location.\n *\n * - Only indexes nodes of type 'document', 'operation', or 'example'.\n * - The lookup key is a serialized array of: [documentName, operationPath, operationMethod, exampleName?].\n * - Supports precise resolution of sidebar entries given an API \"location\".\n */\n const locationIndex = computed(() =>\n generateReverseIndex({\n items: entries.value,\n nestedKey: 'children',\n filter: (node) => node.type === 'document' || node.type === 'operation' || node.type === 'example',\n getId: (node) => {\n const document = getParentEntry('document', node)\n const operation = getParentEntry('operation', node)\n return generateId({\n document: document?.name ?? '',\n path: operation?.path,\n method: operation?.method,\n example: node.type === 'example' ? node.name : undefined,\n })\n },\n }),\n )\n\n /**\n * Looks up a sidebar entry by its unique API location.\n * - First tries to find an entry matching all provided properties (including example).\n * - If not found, falls back to matching only the operation (ignores example field).\n * This allows resolving either examples, operations, or documents as appropriate.\n *\n * @param location - Object specifying the document name, path, method, and optional example name.\n * @returns The matching sidebar entry, or undefined if none found.\n *\n * Example:\n * const entry = getEntryByLocation({\n * document: 'pets',\n * path: '/pets',\n * method: 'get',\n * example: 'default',\n * })\n */\n const getEntryByLocation: GetEntryByLocation = (location) => {\n // Try to find an entry with the most-specific location (including example)\n const entryWithExample = locationIndex.value.get(generateId(location))\n\n if (entryWithExample) {\n return entryWithExample\n }\n\n // Fallback to the operation (ignoring example) if an example wasn't found or specified\n return locationIndex.value.get(\n generateId({\n document: location.document,\n path: location.path,\n method: location.method,\n }),\n )\n }\n\n /**\n * Handles item selection from the sidebar and routes navigation accordingly.\n *\n * Example:\n * handleSelectItem('id-of-entry')\n */\n const handleSelectItem = (id: string) => {\n const entry = sidebarState.getEntryById(id)\n\n if (!entry) {\n console.warn(`Could not find sidebar entry with id ${id} to select`)\n return\n }\n\n /** Close sidebar and navigate. Used for every branch that performs navigation. */\n const navigate = (route: RouteLocationRaw) => {\n isSidebarOpen.value = false\n return router.push(route)\n }\n\n // Navigate to the document overview page\n if (entry.type === 'document') {\n // If we are already in the document, just toggle expansion\n if (sidebarState.selectedItem.value === id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select it\n sidebarState.setSelected(id)\n sidebarState.setExpanded(id, true)\n return navigate({\n name: 'document.overview',\n params: { documentSlug: entry.name },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'operation') {\n // If we are already in an operation child we just want to toggle the explanstion\n if (sidebarState.isSelected(id) && sidebarState.selectedItem.value !== id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select the first example\n const firstExample = entry.children?.find((child) => child.type === 'example')\n\n if (firstExample) {\n sidebarState.setSelected(firstExample.id)\n sidebarState.setExpanded(firstExample.id, true)\n } else {\n sidebarState.setSelected(id)\n }\n\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(entry.path),\n method: entry.method,\n exampleName: firstExample?.name ?? 'default',\n },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'example') {\n sidebarState.setSelected(id)\n const operation = getParentEntry('operation', entry)\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(operation?.path ?? ''),\n method: operation?.method,\n exampleName: entry.name,\n },\n })\n }\n\n if (entry.type === 'text') {\n return navigate({\n name: 'document.overview',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n },\n })\n }\n\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n /**\n * Navigates to the currently active tab's path using the router.\n * Returns early if the workspace store or active tab is unavailable.\n */\n const navigateToCurrentTab = async (): Promise<void> => {\n if (!store.value) {\n return\n }\n\n const activeTabIndex = store.value.workspace['x-scalar-active-tab'] ?? 0\n const activeTab = store.value.workspace['x-scalar-tabs']?.[activeTabIndex]\n if (!activeTab) {\n return\n }\n\n await router.replace(activeTab.path)\n }\n\n /**\n * Rebuilds the sidebar for the given document.\n * This is used to refresh the sidebar state after structural changes (e.g. after adding or removing items).\n *\n * @param documentName - The name (id) of the document for which to rebuild the sidebar\n */\n const rebuildSidebar = (documentName: string | undefined) => {\n if (documentName) {\n store.value?.buildSidebar(documentName)\n }\n }\n\n /**\n * Ensures the sidebar is refreshed after a new example is created.\n *\n * If the sidebar entry for the new example does not exist or is of a different type,\n * this will rebuild the sidebar for the current document. This helps keep the sidebar state\n * consistent (e.g., after adding a new example via the UI).\n */\n const refreshSidebarAfterExampleCreation = (payload: OperationExampleMeta & { documentName?: string }) => {\n const documentName = payload.documentName ?? activeDocument.value?.['x-scalar-navigation']?.name\n if (!documentName) {\n return\n }\n\n const entry = getEntryByLocation({\n document: documentName,\n path: payload.path,\n method: payload.method,\n example: payload.exampleKey,\n })\n\n if (!entry || entry.type !== 'example') {\n // Sidebar entry for this example doesn't exist, so rebuild sidebar for consistency.\n rebuildSidebar(documentName)\n if (currentRoute.value) {\n syncSidebar(currentRoute.value)\n }\n }\n return\n }\n\n /** Width of the sidebar, with fallback to default. */\n const sidebarWidth = computed(() => store.value?.workspace?.['x-scalar-sidebar-width'] ?? DEFAULT_SIDEBAR_WIDTH)\n\n /** Handler for sidebar width changes. */\n const handleSidebarWidthUpdate = (width: number) => store.value?.update('x-scalar-sidebar-width', width)\n\n /** Controls the visibility of the sidebar. */\n const isSidebarOpen = ref(false)\n // ---------------------------------------------------------------------------\n // Tab Management\n\n /** Constants for workspace store keys */\n const TABS_KEY = 'x-scalar-tabs' as const\n const ACTIVE_TAB_KEY = 'x-scalar-active-tab' as const\n\n /**\n * Creates a tab object based on the current route and workspace state.\n * Used as a fallback when no tabs exist or when creating new tabs.\n */\n const createTabFromRoute = (to: RouteLocationNormalizedGeneric | null): Tab => {\n const method = getRouteParam('method', to)\n const path = getRouteParam('pathEncoded', to)\n const document = getRouteParam('documentSlug', to)\n const workspace = getRouteParam('workspaceSlug', to)\n return {\n ...getTabDetails({\n workspace,\n document,\n path,\n method,\n getEntryByLocation,\n }),\n path: currentRoute.value?.path ?? '',\n }\n }\n\n const tabs = computed(() => {\n return store.value?.workspace[TABS_KEY] ?? [createTabFromRoute(currentRoute.value)]\n })\n\n const activeTabIndex = computed(() => {\n return store.value?.workspace[ACTIVE_TAB_KEY] ?? 0\n })\n\n /**\n * Copies the URL of the tab at the given index to the clipboard.\n * Constructs the full URL using the current origin and the tab path.\n *\n * Note: Will silently fail if clipboard API is unavailable or the tab does not exist.\n */\n const copyTabUrl = async (index: number): Promise<void> => {\n const tab = tabs.value[index]\n\n if (!tab) {\n console.warn(`Cannot copy URL: tab at index ${index} does not exist`)\n return\n }\n\n const url = `${window.location.origin}${tab.path}`\n\n try {\n await navigator.clipboard.writeText(url)\n } catch (error) {\n console.error('Failed to copy URL to clipboard:', error)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Path syncing\n\n /** When the route changes we need to update the active entities in the store */\n const handleRouteChange = (to: RouteLocationNormalizedGeneric) => {\n const slug = getRouteParam('workspaceSlug', to)\n const document = getRouteParam('documentSlug', to)\n const nextTeamSlug = getRouteParam('teamSlug', to)\n\n // Must have an active workspace to syncs\n if (!nextTeamSlug || !slug) {\n return\n }\n\n // Try to see if the user can load this workspace based on the team slug.\n const workspace = workspaces.value.find(\n (workspace) => workspace.slug === slug && workspace.teamSlug === nextTeamSlug,\n )\n\n // If the workspace exists but is not accessible by the current team, redirect to the default workspace.\n if (workspace && !canLoadWorkspace(workspace.teamSlug, teamSlug.value)) {\n return navigateToWorkspace('local', 'default')\n }\n\n routeTeamSlug.value = nextTeamSlug\n workspaceSlug.value = slug\n documentSlug.value = document\n method.value = getRouteParam('method', to)\n path.value = getRouteParam('pathEncoded', to)\n exampleName.value = getRouteParam('exampleName', to)\n\n // Save the current path to the persistence storage\n if (to.path !== '') {\n workspaceStorage.setCurrentPath(to.path)\n }\n\n if (getWorkspaceId(nextTeamSlug, slug) !== activeWorkspace.value?.id) {\n // If the user is navigating into their team context but the team\n // workspace does not exist yet (e.g. they clicked the picker placeholder\n // or are being redirected on login), create it on demand before letting\n // the workspace switcher take over. Otherwise `changeWorkspace` would\n // fall back to the local default and silently swallow the navigation.\n const isUnknownTeamWorkspace = nextTeamSlug !== 'local' && nextTeamSlug === teamSlug.value && !workspace\n if (isUnknownTeamWorkspace) {\n return createWorkspace({\n teamSlug: nextTeamSlug,\n slug,\n name: DEFAULT_TEAM_WORKSPACE_NAME,\n })\n }\n return changeWorkspace(nextTeamSlug, slug, to)\n }\n\n // Update the active document if the document slug has changes\n if (document && document !== store.value?.workspace[extensions.workspace.activeDocument]) {\n store?.value?.update('x-scalar-active-document', document)\n }\n\n syncTabs(to)\n syncSidebar(to)\n return\n }\n\n /** Aligns the tabs with any potential slug changes */\n const syncTabs = (to: RouteLocationNormalizedGeneric) => {\n const tabs = store.value?.workspace['x-scalar-tabs'] ?? []\n const index = store.value?.workspace['x-scalar-active-tab'] ?? 0\n\n const tab = tabs[index]\n\n // If there is no tab or the tab path is the same we leave the tab state alone\n if (!tab || tab.path === to.path) {\n // Already on the correct path, do nothing\n return\n }\n\n // Otherwise we replace the tab content with the new route\n tabs[index] = createTabFromRoute(to)\n }\n\n /** Aligns the sidebar state with any potential slug changes */\n const syncSidebar = (to: RouteLocationNormalizedGeneric) => {\n const document = getRouteParam('documentSlug', to)\n\n if (!document) {\n // Reset selection if no document is selected\n sidebarState.setSelected(null)\n return\n }\n\n const entry = getEntryByLocation({\n document,\n path: getRouteParam('pathEncoded', to),\n method: getRouteParam('method', to),\n example: getRouteParam('exampleName', to),\n })\n\n if (entry) {\n sidebarState.setSelected(entry.id)\n sidebarState.setExpanded(entry.id, true)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Events handling\n\n initializeAppEventHandlers({\n eventBus,\n router,\n store,\n navigateToCurrentTab,\n rebuildSidebar,\n onAfterExampleCreation: refreshSidebarAfterExampleCreation,\n onSelectSidebarItem: handleSelectItem,\n onCopyTabUrl: (index) => copyTabUrl(index),\n onToggleSidebar: () => (isSidebarOpen.value = !isSidebarOpen.value),\n closeSidebar: () => (isSidebarOpen.value = false),\n renameWorkspace,\n })\n\n const theme = useTheme({\n fallbackThemeSlug,\n customThemes,\n store: store,\n })\n\n const isDarkMode = computed(() => {\n const colorMode = store.value?.workspace['x-scalar-color-mode'] ?? 'system'\n if (colorMode === 'system') {\n return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false\n }\n return colorMode === 'dark'\n })\n\n return {\n /** Active workspace store */\n store,\n sidebar: {\n state: sidebarState,\n width: sidebarWidth,\n isOpen: isSidebarOpen,\n handleSelectItem,\n handleSidebarWidthUpdate,\n getEntryByLocation,\n },\n tabs: {\n state: tabs,\n activeTabIndex,\n copyTabUrl,\n },\n workspace: {\n create: createWorkspace,\n workspaceList: workspaces,\n filteredWorkspaceList: filteredWorkspaces,\n workspaceGroups,\n activeWorkspace,\n navigateToWorkspace,\n isOpen: computed(() => Boolean(workspaceSlug.value && !documentSlug.value)),\n isTeamWorkspace,\n },\n eventBus,\n router,\n currentRoute,\n loading: isSyncingWorkspace,\n activeEntities: {\n workspaceSlug,\n documentSlug,\n path,\n method,\n exampleName,\n teamSlug: readonly(teamSlug),\n setTeamSlug,\n },\n environment,\n document: activeDocument,\n isDarkMode,\n theme: {\n styles: theme.themeStyles,\n themeStyleTag: theme.themeStyleTag,\n customThemes,\n },\n telemetry,\n options,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/**
|
|
3
|
+
* Inline label rendered inside the menu trigger between the logo and the
|
|
4
|
+
* caret. Used to surface the active scope ("Team" / "Local") so the menu
|
|
5
|
+
* trigger doubles as the leading breadcrumb segment. Pass a plain string
|
|
6
|
+
* so consumers do not have to pierce through a slot pipeline just to
|
|
7
|
+
* tell the header which workspace type is active.
|
|
8
|
+
*/
|
|
9
|
+
menuTitle?: string;
|
|
10
|
+
};
|
|
1
11
|
type __VLS_Slots = {
|
|
12
|
+
/**
|
|
13
|
+
* Replaces the Scalar logo rendered inside the menu button. Typically used
|
|
14
|
+
* by team-aware consumers to surface a team avatar or workspace logo so the
|
|
15
|
+
* header reads as "this team / this workspace" at a glance.
|
|
16
|
+
*/
|
|
17
|
+
logo?(): unknown;
|
|
2
18
|
/** Slot for customizing the menu items */
|
|
3
19
|
menuItems?(): unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Slot rendered directly after the menu button in the start section of
|
|
22
|
+
* the header. Typically used for a document breadcrumb / version-picker
|
|
23
|
+
* combination that sits alongside the menu rather than floating in the
|
|
24
|
+
* middle of the header.
|
|
25
|
+
*/
|
|
26
|
+
breadcrumb?(): unknown;
|
|
4
27
|
/** Slot for customizing the end of the header */
|
|
5
28
|
end?(): unknown;
|
|
6
29
|
};
|
|
7
|
-
declare const __VLS_base: import("vue").DefineComponent<
|
|
30
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
8
31
|
"navigate:to:settings": () => any;
|
|
9
|
-
}, string, import("vue").PublicProps, Readonly<
|
|
32
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
33
|
"onNavigate:to:settings"?: (() => any) | undefined;
|
|
11
|
-
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions,
|
|
34
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
35
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
13
36
|
declare const _default: typeof __VLS_export;
|
|
14
37
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeader.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/AppHeader.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AppHeader.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/AppHeader.vue"],"names":[],"mappings":"AA4GA,KAAK,WAAW,GAAG;IACjB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAC;AASF,KAAK,WAAW,GAAG;IACjB;;;;OAIG;IACH,IAAI,CAAC,IAAI,OAAO,CAAA;IAChB,0CAA0C;IAC1C,SAAS,CAAC,IAAI,OAAO,CAAA;IACrB;;;;;OAKG;IACH,UAAU,CAAC,IAAI,OAAO,CAAA;IACtB,iDAAiD;IACjD,GAAG,CAAC,IAAI,OAAO,CAAA;CAChB,CAAC;AA2KF,QAAA,MAAM,UAAU;;;;kFAGd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeader.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarHeader,\n ScalarMenu,\n ScalarMenuLink,\n ScalarMenuProducts,\n ScalarMenuResources,\n ScalarMenuSection,\n} from '@scalar/components'\nimport { ScalarIconGear } from '@scalar/icons'\n\nconst emit = defineEmits<{\n /** Emitted when the user wants to open the workspace settings */\n (e: 'navigate:to:settings'): void\n}>()\n\nconst slots = defineSlots<{\n /** Slot for customizing the menu items */\n menuItems?(): unknown\n /** Slot for customizing the end of the header */\n end?(): unknown\n}>()\n</script>\n\n<template>\n <ScalarHeader class=\"w-full pl-3 *:first:flex-none\">\n <template #start>\n <ScalarMenu>\n <template #products>\n <ScalarMenuProducts selected=\"client\" />\n </template>\n <template #sections=\"{ close }\">\n <ScalarMenuSection>\n <slot name=\"menuItems\"
|
|
1
|
+
{"version":3,"file":"AppHeader.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarHeader,\n ScalarMenu,\n ScalarMenuLink,\n ScalarMenuProducts,\n ScalarMenuResources,\n ScalarMenuSection,\n} from '@scalar/components'\nimport { ScalarIconGear } from '@scalar/icons'\n\ndefineProps<{\n /**\n * Inline label rendered inside the menu trigger between the logo and the\n * caret. Used to surface the active scope (\"Team\" / \"Local\") so the menu\n * trigger doubles as the leading breadcrumb segment. Pass a plain string\n * so consumers do not have to pierce through a slot pipeline just to\n * tell the header which workspace type is active.\n */\n menuTitle?: string\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the user wants to open the workspace settings */\n (e: 'navigate:to:settings'): void\n}>()\n\nconst slots = defineSlots<{\n /**\n * Replaces the Scalar logo rendered inside the menu button. Typically used\n * by team-aware consumers to surface a team avatar or workspace logo so the\n * header reads as \"this team / this workspace\" at a glance.\n */\n logo?(): unknown\n /** Slot for customizing the menu items */\n menuItems?(): unknown\n /**\n * Slot rendered directly after the menu button in the start section of\n * the header. Typically used for a document breadcrumb / version-picker\n * combination that sits alongside the menu rather than floating in the\n * middle of the header.\n */\n breadcrumb?(): unknown\n /** Slot for customizing the end of the header */\n end?(): unknown\n}>()\n</script>\n\n<template>\n <ScalarHeader class=\"w-full pl-3 *:first:flex-none\">\n <template #start>\n <ScalarMenu>\n <template\n v-if=\"slots.logo\"\n #logo>\n <slot name=\"logo\" />\n </template>\n <template\n v-if=\"menuTitle\"\n #title>\n {{ menuTitle }}\n </template>\n <template #products>\n <ScalarMenuProducts selected=\"client\" />\n </template>\n <template #sections=\"{ close }\">\n <ScalarMenuSection>\n <slot name=\"menuItems\">\n <ScalarMenuLink\n is=\"button\"\n :icon=\"ScalarIconGear\"\n @click=\"\n () => {\n close()\n emit('navigate:to:settings')\n }\n \">\n Settings\n </ScalarMenuLink>\n </slot>\n </ScalarMenuSection>\n <ScalarMenuResources />\n </template>\n </ScalarMenu>\n <slot\n v-if=\"slots.breadcrumb\"\n name=\"breadcrumb\" />\n </template>\n <template\n v-if=\"slots.end\"\n #end>\n <slots.end />\n </template>\n </ScalarHeader>\n</template>\n"],"mappings":""}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { createBlock, createSlots, createTextVNode, createVNode, defineComponent, openBlock, renderSlot, unref, useSlots, withCtx } from "vue";
|
|
1
|
+
import { createBlock, createCommentVNode, createSlots, createTextVNode, createVNode, defineComponent, openBlock, renderSlot, toDisplayString, unref, useSlots, withCtx } from "vue";
|
|
2
2
|
import { ScalarHeader, ScalarMenu, ScalarMenuLink, ScalarMenuProducts, ScalarMenuResources, ScalarMenuSection } from "@scalar/components";
|
|
3
3
|
import { ScalarIconGear } from "@scalar/icons";
|
|
4
4
|
//#region src/v2/features/app/components/AppHeader.vue?vue&type=script&setup=true&lang.ts
|
|
5
5
|
var AppHeader_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
6
6
|
__name: "AppHeader",
|
|
7
|
+
props: { menuTitle: {} },
|
|
7
8
|
emits: ["navigate:to:settings"],
|
|
8
9
|
setup(__props, { emit: __emit }) {
|
|
9
10
|
const emit = __emit;
|
|
10
11
|
const slots = useSlots();
|
|
11
12
|
return (_ctx, _cache) => {
|
|
12
13
|
return openBlock(), createBlock(unref(ScalarHeader), { class: "w-full pl-3 *:first:flex-none" }, createSlots({
|
|
13
|
-
start: withCtx(() => [createVNode(unref(ScalarMenu), null, {
|
|
14
|
+
start: withCtx(() => [createVNode(unref(ScalarMenu), null, createSlots({
|
|
14
15
|
products: withCtx(() => [createVNode(unref(ScalarMenuProducts), { selected: "client" })]),
|
|
15
16
|
sections: withCtx(({ close }) => [createVNode(unref(ScalarMenuSection), null, {
|
|
16
|
-
default: withCtx(() => [renderSlot(_ctx.$slots, "menuItems"
|
|
17
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "menuItems", {}, () => [createVNode(unref(ScalarMenuLink), {
|
|
17
18
|
is: "button",
|
|
18
19
|
icon: unref(ScalarIconGear),
|
|
19
20
|
onClick: () => {
|
|
@@ -23,11 +24,19 @@ var AppHeader_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
|
|
|
23
24
|
}, {
|
|
24
25
|
default: withCtx(() => [..._cache[0] || (_cache[0] = [createTextVNode(" Settings ", -1)])]),
|
|
25
26
|
_: 1
|
|
26
|
-
}, 8, ["icon", "onClick"])]),
|
|
27
|
+
}, 8, ["icon", "onClick"])])]),
|
|
27
28
|
_: 2
|
|
28
29
|
}, 1024), createVNode(unref(ScalarMenuResources))]),
|
|
29
|
-
_:
|
|
30
|
-
}
|
|
30
|
+
_: 2
|
|
31
|
+
}, [slots.logo ? {
|
|
32
|
+
name: "logo",
|
|
33
|
+
fn: withCtx(() => [renderSlot(_ctx.$slots, "logo")]),
|
|
34
|
+
key: "0"
|
|
35
|
+
} : void 0, __props.menuTitle ? {
|
|
36
|
+
name: "title",
|
|
37
|
+
fn: withCtx(() => [createTextVNode(toDisplayString(__props.menuTitle), 1)]),
|
|
38
|
+
key: "1"
|
|
39
|
+
} : void 0]), 1024), slots.breadcrumb ? renderSlot(_ctx.$slots, "breadcrumb", { key: 0 }) : createCommentVNode("", true)]),
|
|
31
40
|
_: 2
|
|
32
41
|
}, [slots.end ? {
|
|
33
42
|
name: "end",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeader.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarHeader,\n ScalarMenu,\n ScalarMenuLink,\n ScalarMenuProducts,\n ScalarMenuResources,\n ScalarMenuSection,\n} from '@scalar/components'\nimport { ScalarIconGear } from '@scalar/icons'\n\nconst emit = defineEmits<{\n /** Emitted when the user wants to open the workspace settings */\n (e: 'navigate:to:settings'): void\n}>()\n\nconst slots = defineSlots<{\n /** Slot for customizing the menu items */\n menuItems?(): unknown\n /** Slot for customizing the end of the header */\n end?(): unknown\n}>()\n</script>\n\n<template>\n <ScalarHeader class=\"w-full pl-3 *:first:flex-none\">\n <template #start>\n <ScalarMenu>\n <template #products>\n <ScalarMenuProducts selected=\"client\" />\n </template>\n <template #sections=\"{ close }\">\n <ScalarMenuSection>\n <slot name=\"menuItems\"
|
|
1
|
+
{"version":3,"file":"AppHeader.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarHeader,\n ScalarMenu,\n ScalarMenuLink,\n ScalarMenuProducts,\n ScalarMenuResources,\n ScalarMenuSection,\n} from '@scalar/components'\nimport { ScalarIconGear } from '@scalar/icons'\n\ndefineProps<{\n /**\n * Inline label rendered inside the menu trigger between the logo and the\n * caret. Used to surface the active scope (\"Team\" / \"Local\") so the menu\n * trigger doubles as the leading breadcrumb segment. Pass a plain string\n * so consumers do not have to pierce through a slot pipeline just to\n * tell the header which workspace type is active.\n */\n menuTitle?: string\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the user wants to open the workspace settings */\n (e: 'navigate:to:settings'): void\n}>()\n\nconst slots = defineSlots<{\n /**\n * Replaces the Scalar logo rendered inside the menu button. Typically used\n * by team-aware consumers to surface a team avatar or workspace logo so the\n * header reads as \"this team / this workspace\" at a glance.\n */\n logo?(): unknown\n /** Slot for customizing the menu items */\n menuItems?(): unknown\n /**\n * Slot rendered directly after the menu button in the start section of\n * the header. Typically used for a document breadcrumb / version-picker\n * combination that sits alongside the menu rather than floating in the\n * middle of the header.\n */\n breadcrumb?(): unknown\n /** Slot for customizing the end of the header */\n end?(): unknown\n}>()\n</script>\n\n<template>\n <ScalarHeader class=\"w-full pl-3 *:first:flex-none\">\n <template #start>\n <ScalarMenu>\n <template\n v-if=\"slots.logo\"\n #logo>\n <slot name=\"logo\" />\n </template>\n <template\n v-if=\"menuTitle\"\n #title>\n {{ menuTitle }}\n </template>\n <template #products>\n <ScalarMenuProducts selected=\"client\" />\n </template>\n <template #sections=\"{ close }\">\n <ScalarMenuSection>\n <slot name=\"menuItems\">\n <ScalarMenuLink\n is=\"button\"\n :icon=\"ScalarIconGear\"\n @click=\"\n () => {\n close()\n emit('navigate:to:settings')\n }\n \">\n Settings\n </ScalarMenuLink>\n </slot>\n </ScalarMenuSection>\n <ScalarMenuResources />\n </template>\n </ScalarMenu>\n <slot\n v-if=\"slots.breadcrumb\"\n name=\"breadcrumb\" />\n </template>\n <template\n v-if=\"slots.end\"\n #end>\n <slots.end />\n </template>\n </ScalarHeader>\n</template>\n"],"mappings":";;;;;;;;;EAsBA,MAAM,OAAO;EAKb,MAAM,QAAQ,UAAA;;uBAsBZ,YA4Ce,MAAA,aAAA,EAAA,EA5CD,OAAM,iCAA+B,EAAA,YAAA;IACtC,OAAK,cAiCD,CAhCb,YAgCa,MAAA,WAAA,EAAA,MAAA,YAAA;KArBA,UAAQ,cACuB,CAAxC,YAAwC,MAAA,mBAAA,EAAA,EAApB,UAAS,UAAQ,CAAA,CAAA,CAAA;KAE5B,UAAQ,SAeG,EAfC,YAAK,CAC1B,YAcoB,MAAA,kBAAA,EAAA,MAAA;6BADX,CAZP,WAYO,KAAA,QAAA,aAAA,EAAA,QAAA,CAXL,YAUiB,MAAA,eAAA,EAAA;OATf,IAAG;OACF,MAAM,MAAA,eAAc;OACpB,eAAA;AAAsD,eAAK;AAAuB,aAAI,uBAAA;;;8BAOzF,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,gBAFI,cAEJ,GAAA,CAAA,EAAA,CAAA;;;;eAGJ,YAAuB,MAAA,oBAAA,CAAA,CAAA,CAAA;;QA5BjB,MAAM,OAAA;WACX;uBACmB,CAApB,WAAoB,KAAA,QAAA,OAAA,CAAA,CAAA;;gBAGd,QAAA,YAAA;WACL;uBACc,CAAA,gBAAA,gBAAZ,QAAA,UAAS,EAAA,EAAA,CAAA,CAAA;;yBAyBR,MAAM,aADd,WAEsB,KAAA,QAAA,cAAA,EAAA,KAAA,GAAA,CAAA,GAAA,mBAAA,IAAA,KAAA,CAAA,CAAA;;OAGhB,MAAM,MAAA;UACX;sBACY,CAAb,YAAa,MAAA,IAAA,CAAA,CAAA"}
|
|
@@ -20,9 +20,9 @@ type __VLS_ModelProps = {
|
|
|
20
20
|
'sidebarWidth': number;
|
|
21
21
|
};
|
|
22
22
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
23
|
-
declare var
|
|
23
|
+
declare var __VLS_106: {};
|
|
24
24
|
type __VLS_Slots = {} & {
|
|
25
|
-
footer?: (props: typeof
|
|
25
|
+
footer?: (props: typeof __VLS_106) => any;
|
|
26
26
|
};
|
|
27
27
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
28
28
|
"update:sidebarWidth": (value: number) => any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppSidebar.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/AppSidebar.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AppSidebar.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/AppSidebar.vue"],"names":[],"mappings":"AA2mBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AA+BlE,OAAO,EAEL,KAAK,sBAAsB,EAE5B,MAAM,+CAA+C,CAAA;AAItD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAA;AAE1E,KAAK,WAAW,GAAG;IACjB,6CAA6C;IAC7C,GAAG,EAAE,QAAQ,CAAA;IACb,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,sBAAsB,CAAA;IAC1C,8CAA8C;IAC9C,qBAAqB,CAAC,EAAE,0BAA0B,CAAA;CACnD,CAAC;AAkXF,KAAK,gBAAgB,GAAG;IACxB,wCAAwC;IACxC,cAAc,EAAE,MAAM,CAAC;CACtB,CAAC;AAKF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AA2oBxD,QAAA,IAAI,SAAS,IAAa,CAAE;AAC5B,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,SAAS,KAAK,GAAG,CAAA;CAAE,CAAC;AAKhD,QAAA,MAAM,UAAU;;;;kFAGd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_
|
|
|
2
2
|
import AppSidebar_vue_vue_type_script_setup_true_lang_default from "./AppSidebar.vue.script.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/v2/features/app/components/AppSidebar.vue
|
|
5
|
-
var AppSidebar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(AppSidebar_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var AppSidebar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(AppSidebar_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-1181f741"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { AppSidebar_default as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppSidebar.vue.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":""}
|
|
1
|
+
{"version":3,"file":"AppSidebar.vue.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":""}
|