@scalar/api-client 3.6.0 → 3.6.1
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 +11 -0
- package/dist/style.css +31 -14
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -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 +30 -28
- package/dist/v2/features/app/App.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.script.js +3 -2
- package/dist/v2/features/app/components/AppHeader.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/AppHeaderActions.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/AppHeaderActions.vue.js.map +1 -1
- package/dist/v2/features/app/components/AppHeaderActions.vue.script.js +12 -10
- package/dist/v2/features/app/components/AppHeaderActions.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts +0 -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 +3 -7
- package/dist/v2/features/app/components/AppSidebar.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DesktopTabs.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/DesktopTabs.vue.js.map +1 -1
- package/dist/v2/features/app/components/DesktopTabs.vue.script.js +2 -2
- package/dist/v2/features/app/components/DesktopTabs.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.script.js +35 -17
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js +15 -13
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.js +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.script.js +51 -39
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js +5 -5
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.script.js +44 -42
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.script.js +33 -17
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/helpers/load-document-from-source.d.ts.map +1 -1
- package/dist/v2/features/command-palette/helpers/load-document-from-source.js +3 -2
- package/dist/v2/features/command-palette/helpers/load-document-from-source.js.map +1 -1
- package/dist/v2/helpers/is-url.d.ts.map +1 -1
- package/dist/v2/helpers/is-url.js +2 -1
- package/dist/v2/helpers/is-url.js.map +1 -1
- package/dist/vue-styles.css +6 -6
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.vue.script.js","names":["$slots"],"sources":["../../../../src/v2/features/app/App.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Main entry point for the API client for electron and web.\n *\n * This component handles all events and store business logic for the application.\n */\nexport default {}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarModal, ScalarTeleportRoot, useModal } from '@scalar/components'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport { ScalarToasts } from '@scalar/use-toasts'\nimport { extensions } from '@scalar/workspace-store/schemas/extensions'\nimport { computed, onBeforeUnmount, toValue, watch } from 'vue'\nimport { RouterView } from 'vue-router'\n\nimport { SidebarToggle } from '@/v2/components/sidebar'\nimport AppHeader from '@/v2/features/app/components/AppHeader.vue'\nimport AppHeaderActions from '@/v2/features/app/components/AppHeaderActions.vue'\nimport AppSidebar from '@/v2/features/app/components/AppSidebar.vue'\nimport CreateWorkspaceModal from '@/v2/features/app/components/CreateWorkspaceModal.vue'\nimport DocumentBreadcrumb from '@/v2/features/app/components/DocumentBreadcrumb.vue'\nimport PublishDocumentModal from '@/v2/features/app/components/PublishDocumentModal.vue'\nimport SplashScreen from '@/v2/features/app/components/SplashScreen.vue'\nimport SyncConflictResolutionEditor from '@/v2/features/app/components/SyncConflictResolutionEditor.vue'\nimport type { RouteProps } from '@/v2/features/app/helpers/routes'\nimport { useDocumentSync } from '@/v2/features/app/hooks/use-document-sync'\nimport { useDocumentWatcher } from '@/v2/features/app/hooks/use-document-watcher'\nimport type { CommandPaletteState } from '@/v2/features/command-palette/hooks/use-command-palette-state'\nimport TheCommandPalette from '@/v2/features/command-palette/TheCommandPalette.vue'\nimport { useMonacoEditorConfiguration } from '@/v2/features/editor'\nimport { useColorMode } from '@/v2/hooks/use-color-mode'\nimport { useGlobalHotKeys } from '@/v2/hooks/use-global-hot-keys'\nimport type {\n RegistryAdapter,\n RegistryDocumentsState,\n} from '@/v2/types/configuration'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nimport type { AppState } from './app-state'\nimport DesktopTabs from './components/DesktopTabs.vue'\n\nconst {\n layout,\n plugins = [],\n getAppState,\n getCommandPaletteState,\n registry,\n} = defineProps<{\n layout: Exclude<ClientLayout, 'modal'>\n plugins?: ClientPlugin[]\n getAppState: () => AppState\n getCommandPaletteState: () => CommandPaletteState\n /**\n * Adapter wiring the API client up to an external registry (Scalar\n * Cloud or a custom self-hosted setup). The adapter itself is optional\n * - omit it to opt out of registry features entirely - but every\n * field on it (`documents`, `namespaces`, `fetchDocument`,\n * `publishDocument`, `publishVersion`) is required when provided so\n * the client can rely on the full surface.\n */\n registry?: RegistryAdapter\n}>()\n\n/**\n * Reactive view of the registry documents list with a sane default for\n * setups that did not wire an adapter up. The sidebar and breadcrumb\n * read this getter so they keep rendering skeletons / empty states even\n * when the host application has not provided a `registry` prop.\n */\nconst registryDocuments = computed<RegistryDocumentsState>(\n () => registry?.documents ?? { status: 'success', documents: [] },\n)\n\ndefineSlots<{\n /**\n * Replaces the Scalar logo inside the header menu button. Typically used by\n * team-aware consumers (e.g. Scalar Cloud) to render a team avatar so the\n * left-most chrome reads as \"this team's workspace\" rather than the\n * generic Scalar wordmark.\n *\n * Receives `isTeamWorkspace` so consumers can opt into rendering a team\n * image only when the active workspace actually belongs to a team, while\n * keeping the default Scalar logo for local workspaces.\n */\n 'header-logo'?: (payload: { isTeamWorkspace: boolean }) => unknown\n /**\n * Slot for customizing the menu items section of the app header.\n * Defaults to a workspace picker bound to the current app state. Overriding this slot\n * replaces the default picker entirely, so the consumer is responsible for rendering\n * any workspace switcher (or other menu content) they need.\n */\n 'header-menu-items'?: () => unknown\n /**\n * Slot for customizing the end section of the app header.\n * Typically used for user menus, action buttons, or other trailing controls.\n */\n 'header-end'?: () => unknown\n}>()\n\ndefineExpose({\n openCreateWorkspace: () => createWorkspaceModalState.show(),\n})\n\nconst app = getAppState()\nconst paletteState = getCommandPaletteState()\n\n/** Expose workspace store to window for debugging purposes. */\nif (typeof window !== 'undefined') {\n window.dataDumpWorkspace = () => app.store.value\n window.dumpAppState = () => app\n}\n\n/** Call lifecycle hooks on plugins and subscribe to event bus events */\nconst pluginUnsubscribes: (() => void)[] = []\n\nfor (const plugin of plugins) {\n plugin.lifecycle?.onInit?.({ config: { telemetry: app.telemetry.value } })\n\n if (plugin.on) {\n for (const [event, handler] of Object.entries(plugin.on)) {\n pluginUnsubscribes.push(app.eventBus.on(event as any, handler as any))\n }\n }\n}\n\n/** Notify plugins when telemetry config changes */\nwatch(app.telemetry, () => {\n for (const plugin of plugins) {\n plugin.lifecycle?.onConfigChange?.({\n config: { telemetry: app.telemetry.value },\n })\n }\n})\n\nonBeforeUnmount(() => {\n for (const unsub of pluginUnsubscribes) {\n unsub()\n }\n for (const plugin of plugins) {\n plugin.lifecycle?.onDestroy?.()\n }\n})\n\n/** Register global hotkeys for the app, passing the workspace event bus and layout state */\nuseGlobalHotKeys(app.eventBus, layout)\n\nconst DEFAULT_DOCUMENT_WATCH_TIMEOUT = 5000\n\n/** Watch the active document for changes and rebase it with its remote source */\nuseDocumentWatcher({\n documentName: () =>\n app.store.value?.workspace[extensions.workspace.activeDocument],\n store: app.store,\n initialTimeout: DEFAULT_DOCUMENT_WATCH_TIMEOUT,\n})\n\n/** Color mode */\nuseColorMode({ workspaceStore: app.store })\n\nconst currentTheme = computed(() => app.theme.styles.value.themeStyles)\nconst isDarkMode = computed(() => app.isDarkMode.value)\n\n/** Setup monaco editor configuration */\nuseMonacoEditorConfiguration({\n theme: currentTheme,\n darkMode: isDarkMode,\n})\n\nconst createWorkspaceModalState = useModal()\n\n/**\n * Owns the document-level Save / Revert / Pull / Push / Publish flow.\n * Keeping it in a dedicated hook leaves this component focused on\n * routing, layout, and slot composition.\n */\nconst {\n showLocalSaveActions,\n showTeamSyncActions,\n showTeamPublishAction,\n hasHeaderActionCluster,\n isActiveDocumentDirty,\n isOffline,\n canPullActiveDocument,\n canPushActiveDocument,\n publishDocumentModalState,\n syncConflictModalState,\n pendingPullState,\n publishDefaultSlug,\n publishDefaultVersion,\n registryNamespaces,\n handleSaveDocument,\n handleRevertDocument,\n handlePullDocument,\n handlePushDocument,\n handlePublishDocument,\n handlePublishDocumentSubmit,\n handleSyncConflictApplyChanges,\n handleSyncConflictModalClose,\n} = useDocumentSync({\n app,\n registry,\n registryDocuments: () => registryDocuments.value,\n})\n\n/** Props to pass to the RouterView component. */\nconst routerViewProps = computed<RouteProps>(() => {\n return {\n documentSlug: app.activeEntities.documentSlug.value ?? '',\n document: app.store.value?.workspace.activeDocument ?? null,\n environment: app.environment.value,\n eventBus: app.eventBus,\n exampleName: app.activeEntities.exampleName.value,\n fetchRegistryDocument: registry?.fetchDocument,\n layout,\n method: app.activeEntities.method.value,\n path: app.activeEntities.path.value,\n workspaceStore: app.store.value!,\n activeWorkspace: app.workspace.activeWorkspace.value!,\n isTeamWorkspace: app.workspace.isTeamWorkspace.value,\n plugins,\n isDarkMode: app.isDarkMode.value,\n currentTheme: app.theme.styles.value.themeStyles,\n customThemes: toValue(app.theme.customThemes),\n telemetry: app.telemetry.value,\n onUpdateTelemetry: (value: boolean) => {\n app.telemetry.value = value\n },\n options: app.options,\n }\n})\n</script>\n\n<template>\n <ScalarTeleportRoot>\n <!-- Theme style tag -->\n <div v-html=\"app.theme.themeStyleTag.value\" />\n\n <!-- Toasts -->\n <ScalarToasts />\n\n <!-- Main content -->\n <main\n v-if=\"\n app.store.value !== null &&\n app.workspace.activeWorkspace.value !== null &&\n !app.loading.value\n \">\n <div class=\"relative flex h-dvh w-dvw flex-col\">\n <SidebarToggle\n v-model=\"app.sidebar.isOpen.value\"\n class=\"absolute z-60 md:hidden\"\n :class=\"layout === 'desktop' ? 'top-14 left-4' : 'top-4 left-4'\" />\n <AppHeader\n :menuTitle=\"app.workspace.isTeamWorkspace.value ? 'Team' : 'Local'\"\n @navigate:to:settings=\"\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'settings',\n })\n \">\n <!--\n Only forward the consumer-provided logo (typically a team\n avatar from Scalar Cloud) while the user is actually inside a\n team workspace. Outside of a team context the avatar would be\n misleading, so we omit the `#logo` template entirely and let\n `ScalarMenuButton` fall back to its default Scalar wordmark.\n -->\n <template\n v-if=\"$slots['header-logo'] && app.workspace.isTeamWorkspace.value\"\n #logo>\n <slot\n :isTeamWorkspace=\"app.workspace.isTeamWorkspace.value\"\n name=\"header-logo\" />\n </template>\n <template #menuItems>\n <!--\n The workspace picker used to live here as a submenu. It is now\n surfaced inline in the breadcrumb so the user reaches it in a\n single click. Consumers that want extra menu rows can still\n inject them through the `header-menu-items` slot.\n -->\n <slot name=\"header-menu-items\" />\n </template>\n <template #breadcrumb>\n <DocumentBreadcrumb\n :app=\"app\"\n :fetchRegistryDocument=\"registry?.fetchDocument\"\n :registryDocuments=\"registryDocuments\"\n @createWorkspace=\"createWorkspaceModalState.show()\" />\n </template>\n <!--\n Only forward the trailing `#end` cluster when it has actual\n content. The action clusters and the consumer slots all gate\n independently, so we mirror those conditions on the wrapper to\n avoid mounting an empty cluster that would otherwise leak a\n stray divider.\n -->\n <template\n v-if=\"\n hasHeaderActionCluster ||\n $slots['header-actions'] ||\n $slots['header-end']\n \"\n #end>\n <div class=\"flex items-center gap-2\">\n <AppHeaderActions\n :canPullActiveDocument=\"canPullActiveDocument\"\n :canPushActiveDocument=\"canPushActiveDocument\"\n :isActiveDocumentDirty=\"isActiveDocumentDirty\"\n :isOffline=\"isOffline\"\n :showLocalSaveActions=\"showLocalSaveActions\"\n :showTeamPublishAction=\"showTeamPublishAction\"\n :showTeamSyncActions=\"showTeamSyncActions\"\n @publish=\"handlePublishDocument\"\n @pull=\"handlePullDocument\"\n @push=\"handlePushDocument\"\n @revert=\"handleRevertDocument\"\n @save=\"handleSaveDocument\" />\n <!--\n Vertical divider between the document-scoped action cluster\n (workspace-mode buttons + `header-actions`) and the trailing\n `header-end` cluster. Only rendered when both sides have\n content so single-cluster headers do not get an orphaned\n separator.\n -->\n <span\n v-if=\"$slots['header-end']\"\n aria-hidden=\"true\"\n class=\"bg-border h-4 w-px shrink-0\" />\n <slot\n v-if=\"$slots['header-end']\"\n name=\"header-end\" />\n </div>\n </template>\n </AppHeader>\n <div class=\"flex min-h-0 flex-1 flex-row\">\n <!-- App sidebar -->\n <AppSidebar\n :app=\"app\"\n :fetchRegistryDocument=\"registry?.fetchDocument\"\n :registryDocuments=\"registryDocuments\"\n :sidebarWidth=\"app.sidebar.width.value\"\n @update:sidebarWidth=\"app.sidebar.handleSidebarWidthUpdate\" />\n\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <!-- App Tabs -->\n <DesktopTabs\n v-if=\"layout === 'desktop'\"\n :activeTabIndex=\"app.tabs.activeTabIndex.value\"\n :eventBus=\"app.eventBus\"\n :tabs=\"app.tabs.state.value\" />\n\n <!-- Router view min-h-0 is required for scrolling, do not remove it -->\n <div class=\"bg-b-1 relative min-h-0 flex-1\">\n <RouterView v-bind=\"routerViewProps\" />\n </div>\n </div>\n </div>\n </div>\n <!-- Create workspace modal -->\n <CreateWorkspaceModal\n :state=\"createWorkspaceModalState\"\n @create:workspace=\"(payload) => app.workspace.create(payload)\" />\n\n <!--\n First-time publish modal. Only mounted when a registry adapter\n is wired up - without one there is nothing meaningful to send,\n and the modal would never be opened anyway.\n -->\n <PublishDocumentModal\n v-if=\"registry\"\n :defaultSlug=\"publishDefaultSlug\"\n :defaultVersion=\"publishDefaultVersion\"\n :namespaces=\"registryNamespaces\"\n :state=\"publishDocumentModalState\"\n @submit=\"handlePublishDocumentSubmit\" />\n <!--\n Three-way merge editor for the Pull flow. We mount it lazily on\n `pendingPullState` so the heavy Monaco editors only spin up when\n a pull actually has conflicts to walk through. The full-size\n layout mirrors `DocumentCollection.vue`'s sync modal so the\n editor has enough room to render the local / remote / result\n panes side-by-side.\n -->\n <ScalarModal\n v-if=\"pendingPullState\"\n bodyClass=\"sync-conflict-modal-root flex h-dvh flex-col p-4\"\n maxWidth=\"calc(100dvw - 32px)\"\n size=\"full\"\n :state=\"syncConflictModalState\"\n @close=\"handleSyncConflictModalClose\">\n <div class=\"flex h-full w-full flex-col gap-4 overflow-hidden\">\n <SyncConflictResolutionEditor\n :baseDocument=\"pendingPullState.rebaseResult.originalDocument\"\n :conflicts=\"pendingPullState.rebaseResult.conflicts\"\n :resolvedDocument=\"pendingPullState.rebaseResult.resolvedDocument\"\n @applyChanges=\"handleSyncConflictApplyChanges\" />\n </div>\n </ScalarModal>\n <!-- Popup command palette to add resources from anywhere -->\n <TheCommandPalette\n :eventBus=\"app.eventBus\"\n :paletteState=\"paletteState\"\n :workspaceStore=\"app.store.value!\" />\n </main>\n <!-- Splash screen -->\n <main v-else>\n <SplashScreen />\n </main>\n </ScalarTeleportRoot>\n</template>\n\n<style>\n#scalar-client {\n position: relative;\n background-color: var(--scalar-background-2);\n}\n.dark-mode #scalar-client {\n background-color: color-mix(in srgb, var(--scalar-background-1) 65%, black);\n}\n\n/*\n * The three-way merge editor needs the full viewport to fit its three\n * Monaco panes. `DocumentCollection.vue` ships the same override for\n * its in-page Sync modal, but the pull flow can run without that view\n * being mounted, so we duplicate the rule here to keep the modal\n * full-bleed.\n */\n.full-size-styles:has(.sync-conflict-modal-root) {\n width: 100dvw !important;\n max-width: 100dvw !important;\n border-right: none !important;\n}\n\n.full-size-styles:has(.sync-conflict-modal-root)::after {\n display: none;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuEA,MAAM,oBAAoB,eAClB,QAAA,UAAU,aAAa;GAAE,QAAQ;GAAW,WAAW,EAAE;GAAE,CACnE;AA4BA,WAAa,EACX,2BAA2B,0BAA0B,MAAM,EAC5D,CAAA;EAED,MAAM,MAAM,QAAA,aAAY;EACxB,MAAM,eAAe,QAAA,wBAAuB;;AAG5C,MAAI,OAAO,WAAW,aAAa;AACjC,UAAO,0BAA0B,IAAI,MAAM;AAC3C,UAAO,qBAAqB;;;EAI9B,MAAM,qBAAqC,EAAC;AAE5C,OAAK,MAAM,UAAU,QAAA,SAAS;AAC5B,UAAO,WAAW,SAAS,EAAE,QAAQ,EAAE,WAAW,IAAI,UAAU,OAAO,EAAE,CAAA;AAEzE,OAAI,OAAO,GACT,MAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,OAAO,GAAG,CACtD,oBAAmB,KAAK,IAAI,SAAS,GAAG,OAAc,QAAe,CAAA;;;AAM3E,QAAM,IAAI,iBAAiB;AACzB,QAAK,MAAM,UAAU,QAAA,QACnB,QAAO,WAAW,iBAAiB,EACjC,QAAQ,EAAE,WAAW,IAAI,UAAU,OAAO,EAC3C,CAAA;IAEJ;AAED,wBAAsB;AACpB,QAAK,MAAM,SAAS,mBAClB,QAAM;AAER,QAAK,MAAM,UAAU,QAAA,QACnB,QAAO,WAAW,aAAY;IAEjC;;AAGD,mBAAiB,IAAI,UAAU,QAAA,OAAM;;AAKrC,qBAAmB;GACjB,oBACE,IAAI,MAAM,OAAO,UAAU,WAAW,UAAU;GAClD,OAAO,IAAI;GACX,gBAPqC;GAQtC,CAAA;;AAGD,eAAa,EAAE,gBAAgB,IAAI,OAAO,CAAA;;AAM1C,+BAA6B;GAC3B,OALmB,eAAe,IAAI,MAAM,OAAO,MAAM,YAAW;GAMpE,UALiB,eAAe,IAAI,WAAW,MAAK;GAMrD,CAAA;EAED,MAAM,4BAA4B,UAAS;;;;;;EAO3C,MAAM,EACJ,sBACA,qBACA,uBACA,wBACA,uBACA,WACA,uBACA,uBACA,2BACA,wBACA,kBACA,oBACA,uBACA,oBACA,oBACA,sBACA,oBACA,oBACA,uBACA,6BACA,gCACA,iCACE,gBAAgB;GAClB;GACA,UAAO,QAAA;GACP,yBAAyB,kBAAkB;GAC5C,CAAA;;EAGD,MAAM,kBAAkB,eAA2B;AACjD,UAAO;IACL,cAAc,IAAI,eAAe,aAAa,SAAS;IACvD,UAAU,IAAI,MAAM,OAAO,UAAU,kBAAkB;IACvD,aAAa,IAAI,YAAY;IAC7B,UAAU,IAAI;IACd,aAAa,IAAI,eAAe,YAAY;IAC5C,uBAAuB,QAAA,UAAU;IACjC,QAAK,QAAA;IACL,QAAQ,IAAI,eAAe,OAAO;IAClC,MAAM,IAAI,eAAe,KAAK;IAC9B,gBAAgB,IAAI,MAAM;IAC1B,iBAAiB,IAAI,UAAU,gBAAgB;IAC/C,iBAAiB,IAAI,UAAU,gBAAgB;IAC/C,SAAM,QAAA;IACN,YAAY,IAAI,WAAW;IAC3B,cAAc,IAAI,MAAM,OAAO,MAAM;IACrC,cAAc,QAAQ,IAAI,MAAM,aAAa;IAC7C,WAAW,IAAI,UAAU;IACzB,oBAAoB,UAAmB;AACrC,SAAI,UAAU,QAAQ;;IAExB,SAAS,IAAI;IACf;IACD;;uBAIC,YAgLqB,MAAA,mBAAA,EAAA,MAAA;2BA9K2B;KAA9C,mBAA8C,OAAA,EAAzC,WAAQ,MAAA,IAAG,CAAC,MAAM,cAAc,OAAA,EAAA,MAAA,GAAA,WAAA;KAGrC,YAAgB,MAAA,aAAA,CAAA;KAIC,MAAA,IAAG,CAAC,MAAM,UAAK,QAAqB,MAAA,IAAG,CAAC,UAAU,gBAAgB,UAAK,QAAA,CAAsB,MAAA,IAAG,CAAC,QAAQ,SAAA,WAAA,EAD1H,mBAmKO,QAAA,YAAA;MA7JL,mBA+GM,OA/GN,YA+GM;OA9GJ,YAGqE,MAAA,sBAAA,EAAA;oBAF1D,MAAA,IAAG,CAAC,QAAQ,OAAO;2EAAnB,IAAG,CAAC,QAAQ,OAAO,QAAK;QACjC,OAAK,eAAA,CAAC,2BACE,QAAA,WAAM,YAAA,kBAAA,eAAA,CAAA;;OAChB,YAkFY,mBAAA;QAjFT,WAAW,MAAA,IAAG,CAAC,UAAU,gBAAgB,QAAK,SAAA;QAC9C,0BAAoB,OAAA,OAAA,OAAA,MAAA,WAAe,MAAA,IAAG,CAAC,SAAS,KAAI,eAAA;;;;;QAoB1C,WAAS,cAOe,CAAjC,WAAiC,KAAA,QAAA,oBAAA,CAAA,CAAA;QAExB,YAAU,cAKqC,CAJxD,YAIwD,4BAAA;SAHrD,KAAK,MAAA,IAAG;SACR,uBAAuB,QAAA,UAAU;SACjC,mBAAmB,kBAAA;SACnB,mBAAe,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,0BAAyB,CAAC,MAAI;;;;;;;WApB5CA,KAAAA,OAAM,kBAAmB,MAAA,IAAG,CAAC,UAAU,gBAAgB,QAAA;cAC5D;0BAGsB,CAFvB,WAEuB,KAAA,QAAA,eAAA,EADpB,iBAAiB,MAAA,IAAG,CAAC,UAAU,gBAAgB,OAAA,CAAA,CAAA,CAAA;;mBA2B7B,MAAA,uBAAsB,IAAkBA,KAAAA,OAAM,qBAAoCA,KAAAA,OAAM,gBAAA;cAK5G;0BA6BK,CA5BN,mBA4BM,OA5BN,YA4BM;SA3BJ,YAY+B,0BAAA;UAX5B,uBAAuB,MAAA,sBAAqB;UAC5C,uBAAuB,MAAA,sBAAqB;UAC5C,uBAAuB,MAAA,sBAAqB;UAC5C,WAAW,MAAA,UAAS;UACpB,sBAAsB,MAAA,qBAAoB;UAC1C,uBAAuB,MAAA,sBAAqB;UAC5C,qBAAqB,MAAA,oBAAmB;UACxC,WAAS,MAAA,sBAAqB;UAC9B,QAAM,MAAA,mBAAkB;UACxB,QAAM,MAAA,mBAAkB;UACxB,UAAQ,MAAA,qBAAoB;UAC5B,QAAM,MAAA,mBAAkB;;;;;;;;;;;;;;;SASnBA,KAAAA,OAAM,iBAAA,WAAA,EADd,mBAGwC,QAHxC,WAGwC,IAAA,mBAAA,IAAA,KAAA;SAEhCA,KAAAA,OAAM,gBADd,WAEsB,KAAA,QAAA,cAAA,EAAA,KAAA,GAAA,CAAA,GAAA,mBAAA,IAAA,KAAA;;;;OAI5B,mBAsBM,OAtBN,YAsBM,CApBJ,YAKgE,oBAAA;QAJ7D,KAAK,MAAA,IAAG;QACR,uBAAuB,QAAA,UAAU;QACjC,mBAAmB,kBAAA;QACnB,cAAc,MAAA,IAAG,CAAC,QAAQ,MAAM;QAChC,yBAAqB,MAAA,IAAG,CAAC,QAAQ;;;;;;;WAEpC,mBAYM,OAZN,YAYM,CATI,QAAA,WAAM,aAAA,WAAA,EADd,YAIiC,qBAAA;;QAF9B,gBAAgB,MAAA,IAAG,CAAC,KAAK,eAAe;QACxC,UAAU,MAAA,IAAG,CAAC;QACd,MAAM,MAAA,IAAG,CAAC,KAAK,MAAM;;;;;2CAGxB,mBAEM,OAFN,YAEM,CADJ,YAAuC,MAAA,WAAA,EAAA,eAAA,mBAAnB,gBAAA,MAAe,CAAA,EAAA,MAAA,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;MAM3C,YAEmE,8BAAA;OADhE,OAAO,MAAA,0BAAyB;OAChC,sBAAgB,OAAA,OAAA,OAAA,MAAG,YAAY,MAAA,IAAG,CAAC,UAAU,OAAO,QAAO;;MAQtD,QAAA,YAAA,WAAA,EADR,YAM0C,8BAAA;;OAJvC,aAAa,MAAA,mBAAkB;OAC/B,gBAAgB,MAAA,sBAAqB;OACrC,YAAY,MAAA,mBAAkB;OAC9B,OAAO,MAAA,0BAAyB;OAChC,UAAQ,MAAA,4BAA2B;;;;;;;;MAU9B,MAAA,iBAAgB,IAAA,WAAA,EADxB,YAcc,MAAA,YAAA,EAAA;;OAZZ,WAAU;OACV,UAAS;OACT,MAAK;OACJ,OAAO,MAAA,uBAAsB;OAC7B,SAAO,MAAA,6BAA4B;;8BAO9B,CANN,mBAMM,OANN,YAMM,CALJ,YAImD,sCAAA;QAHhD,cAAc,MAAA,iBAAgB,CAAC,aAAa;QAC5C,WAAW,MAAA,iBAAgB,CAAC,aAAa;QACzC,kBAAkB,MAAA,iBAAgB,CAAC,aAAa;QAChD,gBAAc,MAAA,+BAA8B;;;;;;;;;MAInD,YAGuC,2BAAA;OAFpC,UAAU,MAAA,IAAG,CAAC;OACd,cAAc,MAAA,aAAY;OAC1B,gBAAgB,MAAA,IAAG,CAAC,MAAM;;;;;;yBAG/B,mBAEO,QAAA,aAAA,CADL,YAAgB,qBAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"App.vue.script.js","names":["$slots"],"sources":["../../../../src/v2/features/app/App.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Main entry point for the API client for electron and web.\n *\n * This component handles all events and store business logic for the application.\n */\nexport default {}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarModal, ScalarTeleportRoot, useModal } from '@scalar/components'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport { ScalarToasts } from '@scalar/use-toasts'\nimport { extensions } from '@scalar/workspace-store/schemas/extensions'\nimport { computed, onBeforeUnmount, toValue, watch } from 'vue'\nimport { RouterView } from 'vue-router'\n\nimport { SidebarToggle } from '@/v2/components/sidebar'\nimport AppHeader from '@/v2/features/app/components/AppHeader.vue'\nimport AppHeaderActions from '@/v2/features/app/components/AppHeaderActions.vue'\nimport AppSidebar from '@/v2/features/app/components/AppSidebar.vue'\nimport CreateWorkspaceModal from '@/v2/features/app/components/CreateWorkspaceModal.vue'\nimport DocumentBreadcrumb from '@/v2/features/app/components/DocumentBreadcrumb.vue'\nimport PublishDocumentModal from '@/v2/features/app/components/PublishDocumentModal.vue'\nimport SplashScreen from '@/v2/features/app/components/SplashScreen.vue'\nimport SyncConflictResolutionEditor from '@/v2/features/app/components/SyncConflictResolutionEditor.vue'\nimport type { RouteProps } from '@/v2/features/app/helpers/routes'\nimport { useDocumentSync } from '@/v2/features/app/hooks/use-document-sync'\nimport { useDocumentWatcher } from '@/v2/features/app/hooks/use-document-watcher'\nimport type { CommandPaletteState } from '@/v2/features/command-palette/hooks/use-command-palette-state'\nimport TheCommandPalette from '@/v2/features/command-palette/TheCommandPalette.vue'\nimport { useMonacoEditorConfiguration } from '@/v2/features/editor'\nimport { useColorMode } from '@/v2/hooks/use-color-mode'\nimport { useGlobalHotKeys } from '@/v2/hooks/use-global-hot-keys'\nimport type {\n RegistryAdapter,\n RegistryDocumentsState,\n} from '@/v2/types/configuration'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nimport type { AppState } from './app-state'\nimport DesktopTabs from './components/DesktopTabs.vue'\n\nconst {\n layout,\n plugins = [],\n getAppState,\n getCommandPaletteState,\n registry,\n} = defineProps<{\n layout: Exclude<ClientLayout, 'modal'>\n plugins?: ClientPlugin[]\n getAppState: () => AppState\n getCommandPaletteState: () => CommandPaletteState\n /**\n * Adapter wiring the API client up to an external registry (Scalar\n * Cloud or a custom self-hosted setup). The adapter itself is optional\n * - omit it to opt out of registry features entirely - but every\n * field on it (`documents`, `namespaces`, `fetchDocument`,\n * `publishDocument`, `publishVersion`) is required when provided so\n * the client can rely on the full surface.\n */\n registry?: RegistryAdapter\n}>()\n\n/**\n * Reactive view of the registry documents list with a sane default for\n * setups that did not wire an adapter up. The sidebar and breadcrumb\n * read this getter so they keep rendering skeletons / empty states even\n * when the host application has not provided a `registry` prop.\n */\nconst registryDocuments = computed<RegistryDocumentsState>(\n () => registry?.documents ?? { status: 'success', documents: [] },\n)\n\ndefineSlots<{\n /**\n * Replaces the Scalar logo inside the header menu button. Typically used by\n * team-aware consumers (e.g. Scalar Cloud) to render a team avatar so the\n * left-most chrome reads as \"this team's workspace\" rather than the\n * generic Scalar wordmark.\n *\n * Receives `isTeamWorkspace` so consumers can opt into rendering a team\n * image only when the active workspace actually belongs to a team, while\n * keeping the default Scalar logo for local workspaces.\n */\n 'header-logo'?: (payload: { isTeamWorkspace: boolean }) => unknown\n /**\n * Slot for customizing the menu items section of the app header.\n * Defaults to a workspace picker bound to the current app state. Overriding this slot\n * replaces the default picker entirely, so the consumer is responsible for rendering\n * any workspace switcher (or other menu content) they need.\n */\n 'header-menu-items'?: () => unknown\n /**\n * Slot for customizing the end section of the app header.\n * Typically used for user menus, action buttons, or other trailing controls.\n */\n 'header-end'?: () => unknown\n}>()\n\ndefineExpose({\n openCreateWorkspace: () => createWorkspaceModalState.show(),\n})\n\nconst app = getAppState()\nconst paletteState = getCommandPaletteState()\n\n/** Expose workspace store to window for debugging purposes. */\nif (typeof window !== 'undefined') {\n window.dataDumpWorkspace = () => app.store.value\n window.dumpAppState = () => app\n}\n\n/** Call lifecycle hooks on plugins and subscribe to event bus events */\nconst pluginUnsubscribes: (() => void)[] = []\n\nfor (const plugin of plugins) {\n plugin.lifecycle?.onInit?.({ config: { telemetry: app.telemetry.value } })\n\n if (plugin.on) {\n for (const [event, handler] of Object.entries(plugin.on)) {\n pluginUnsubscribes.push(app.eventBus.on(event as any, handler as any))\n }\n }\n}\n\n/** Notify plugins when telemetry config changes */\nwatch(app.telemetry, () => {\n for (const plugin of plugins) {\n plugin.lifecycle?.onConfigChange?.({\n config: { telemetry: app.telemetry.value },\n })\n }\n})\n\nonBeforeUnmount(() => {\n for (const unsub of pluginUnsubscribes) {\n unsub()\n }\n for (const plugin of plugins) {\n plugin.lifecycle?.onDestroy?.()\n }\n})\n\n/** Register global hotkeys for the app, passing the workspace event bus and layout state */\nuseGlobalHotKeys(app.eventBus, layout)\n\nconst DEFAULT_DOCUMENT_WATCH_TIMEOUT = 5000\n\n/** Watch the active document for changes and rebase it with its remote source */\nuseDocumentWatcher({\n documentName: () =>\n app.store.value?.workspace[extensions.workspace.activeDocument],\n store: app.store,\n initialTimeout: DEFAULT_DOCUMENT_WATCH_TIMEOUT,\n})\n\n/** Color mode */\nuseColorMode({ workspaceStore: app.store })\n\nconst currentTheme = computed(() => app.theme.styles.value.themeStyles)\nconst isDarkMode = computed(() => app.isDarkMode.value)\n\n/** Setup monaco editor configuration */\nuseMonacoEditorConfiguration({\n theme: currentTheme,\n darkMode: isDarkMode,\n})\n\nconst createWorkspaceModalState = useModal()\n\n/**\n * Owns the document-level Save / Revert / Pull / Push / Publish flow.\n * Keeping it in a dedicated hook leaves this component focused on\n * routing, layout, and slot composition.\n */\nconst {\n showLocalSaveActions,\n showTeamSyncActions,\n showTeamPublishAction,\n hasHeaderActionCluster,\n isActiveDocumentDirty,\n isOffline,\n canPullActiveDocument,\n canPushActiveDocument,\n publishDocumentModalState,\n syncConflictModalState,\n pendingPullState,\n publishDefaultSlug,\n publishDefaultVersion,\n registryNamespaces,\n handleSaveDocument,\n handleRevertDocument,\n handlePullDocument,\n handlePushDocument,\n handlePublishDocument,\n handlePublishDocumentSubmit,\n handleSyncConflictApplyChanges,\n handleSyncConflictModalClose,\n} = useDocumentSync({\n app,\n registry,\n registryDocuments: () => registryDocuments.value,\n})\n\n/** Props to pass to the RouterView component. */\nconst routerViewProps = computed<RouteProps>(() => {\n return {\n documentSlug: app.activeEntities.documentSlug.value ?? '',\n document: app.store.value?.workspace.activeDocument ?? null,\n environment: app.environment.value,\n eventBus: app.eventBus,\n exampleName: app.activeEntities.exampleName.value,\n fetchRegistryDocument: registry?.fetchDocument,\n layout,\n method: app.activeEntities.method.value,\n path: app.activeEntities.path.value,\n workspaceStore: app.store.value!,\n activeWorkspace: app.workspace.activeWorkspace.value!,\n isTeamWorkspace: app.workspace.isTeamWorkspace.value,\n plugins,\n isDarkMode: app.isDarkMode.value,\n currentTheme: app.theme.styles.value.themeStyles,\n customThemes: toValue(app.theme.customThemes),\n telemetry: app.telemetry.value,\n onUpdateTelemetry: (value: boolean) => {\n app.telemetry.value = value\n },\n options: app.options,\n }\n})\n</script>\n\n<template>\n <ScalarTeleportRoot>\n <!-- Theme style tag -->\n <div v-html=\"app.theme.themeStyleTag.value\" />\n\n <!-- Toasts -->\n <ScalarToasts />\n\n <!-- Main content -->\n <main\n v-if=\"\n app.store.value !== null &&\n app.workspace.activeWorkspace.value !== null &&\n !app.loading.value\n \">\n <div\n class=\"relative flex h-dvh w-dvw flex-col\"\n :style=\"{\n '--app-desktop-tabs-height': layout === 'desktop' ? '2.5rem' : '0px',\n }\">\n <!--\n Sits in the same visual slot as the operation Header gutter (size-8).\n Offset = desktop tab strip (layout desktop only) + ScalarHeader (min-h-header)\n + OperationBlock outer p-2 + Header pt-2 (1rem total) so we clear app chrome.\n `--app-desktop-tabs-height` is set on this shell so the mobile sidebar inset\n matches (see AppSidebar).\n -->\n <SidebarToggle\n v-model=\"app.sidebar.isOpen.value\"\n class=\"app-no-drag-region absolute top-[calc(var(--app-desktop-tabs-height)+var(--spacing-header,48px)+1rem)] left-4 z-60 md:hidden\" />\n <!-- App Tabs -->\n <DesktopTabs\n v-if=\"layout === 'desktop'\"\n :activeTabIndex=\"app.tabs.activeTabIndex.value\"\n :eventBus=\"app.eventBus\"\n :tabs=\"app.tabs.state.value\" />\n <AppHeader\n :menuTitle=\"app.workspace.isTeamWorkspace.value ? 'Team' : 'Local'\"\n @navigate:to:settings=\"\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'settings',\n })\n \">\n <!--\n Only forward the consumer-provided logo (typically a team\n avatar from Scalar Cloud) while the user is actually inside a\n team workspace. Outside of a team context the avatar would be\n misleading, so we omit the `#logo` template entirely and let\n `ScalarMenuButton` fall back to its default Scalar wordmark.\n -->\n <template\n v-if=\"$slots['header-logo'] && app.workspace.isTeamWorkspace.value\"\n #logo>\n <slot\n :isTeamWorkspace=\"app.workspace.isTeamWorkspace.value\"\n name=\"header-logo\" />\n </template>\n <template #menuItems>\n <!--\n The workspace picker used to live here as a submenu. It is now\n surfaced inline in the breadcrumb so the user reaches it in a\n single click. Consumers that want extra menu rows can still\n inject them through the `header-menu-items` slot.\n -->\n <slot name=\"header-menu-items\" />\n </template>\n <template #breadcrumb>\n <DocumentBreadcrumb\n :app=\"app\"\n :fetchRegistryDocument=\"registry?.fetchDocument\"\n :registryDocuments=\"registryDocuments\"\n @createWorkspace=\"createWorkspaceModalState.show()\" />\n </template>\n <!--\n Only forward the trailing `#end` cluster when it has actual\n content. The action clusters and the consumer slots all gate\n independently, so we mirror those conditions on the wrapper to\n avoid mounting an empty cluster that would otherwise leak a\n stray divider.\n -->\n <template\n v-if=\"\n hasHeaderActionCluster ||\n $slots['header-actions'] ||\n $slots['header-end']\n \"\n #end>\n <div class=\"flex items-center gap-2\">\n <AppHeaderActions\n :canPullActiveDocument=\"canPullActiveDocument\"\n :canPushActiveDocument=\"canPushActiveDocument\"\n :isActiveDocumentDirty=\"isActiveDocumentDirty\"\n :isOffline=\"isOffline\"\n :showLocalSaveActions=\"showLocalSaveActions\"\n :showTeamPublishAction=\"showTeamPublishAction\"\n :showTeamSyncActions=\"showTeamSyncActions\"\n @publish=\"handlePublishDocument\"\n @pull=\"handlePullDocument\"\n @push=\"handlePushDocument\"\n @revert=\"handleRevertDocument\"\n @save=\"handleSaveDocument\" />\n <!--\n Vertical divider\n -->\n <span\n v-if=\"$slots['header-end']\"\n aria-hidden=\"true\"\n class=\"bg-border h-4 w-px shrink-0\" />\n <slot\n v-if=\"$slots['header-end']\"\n name=\"header-end\" />\n </div>\n </template>\n </AppHeader>\n <div class=\"flex min-h-0 flex-1 flex-row\">\n <!-- App sidebar -->\n <AppSidebar\n :app=\"app\"\n :fetchRegistryDocument=\"registry?.fetchDocument\"\n :registryDocuments=\"registryDocuments\"\n :sidebarWidth=\"app.sidebar.width.value\"\n @update:sidebarWidth=\"app.sidebar.handleSidebarWidthUpdate\" />\n\n <!-- Router view min-h-0 is required for scrolling, do not remove it -->\n <div class=\"bg-b-1 relative min-h-0 flex-1\">\n <RouterView v-bind=\"routerViewProps\" />\n </div>\n </div>\n </div>\n <!-- Create workspace modal -->\n <CreateWorkspaceModal\n :state=\"createWorkspaceModalState\"\n @create:workspace=\"(payload) => app.workspace.create(payload)\" />\n\n <!--\n First-time publish modal. Only mounted when a registry adapter\n is wired up - without one there is nothing meaningful to send,\n and the modal would never be opened anyway.\n -->\n <PublishDocumentModal\n v-if=\"registry\"\n :defaultSlug=\"publishDefaultSlug\"\n :defaultVersion=\"publishDefaultVersion\"\n :namespaces=\"registryNamespaces\"\n :state=\"publishDocumentModalState\"\n @submit=\"handlePublishDocumentSubmit\" />\n <!--\n Three-way merge editor for the Pull flow. We mount it lazily on\n `pendingPullState` so the heavy Monaco editors only spin up when\n a pull actually has conflicts to walk through. The full-size\n layout mirrors `DocumentCollection.vue`'s sync modal so the\n editor has enough room to render the local / remote / result\n panes side-by-side.\n -->\n <ScalarModal\n v-if=\"pendingPullState\"\n bodyClass=\"sync-conflict-modal-root flex h-dvh flex-col p-4\"\n maxWidth=\"calc(100dvw - 32px)\"\n size=\"full\"\n :state=\"syncConflictModalState\"\n @close=\"handleSyncConflictModalClose\">\n <div class=\"flex h-full w-full flex-col gap-4 overflow-hidden\">\n <SyncConflictResolutionEditor\n :baseDocument=\"pendingPullState.rebaseResult.originalDocument\"\n :conflicts=\"pendingPullState.rebaseResult.conflicts\"\n :resolvedDocument=\"pendingPullState.rebaseResult.resolvedDocument\"\n @applyChanges=\"handleSyncConflictApplyChanges\" />\n </div>\n </ScalarModal>\n <!-- Popup command palette to add resources from anywhere -->\n <TheCommandPalette\n :eventBus=\"app.eventBus\"\n :paletteState=\"paletteState\"\n :workspaceStore=\"app.store.value!\" />\n </main>\n <!-- Splash screen -->\n <main v-else>\n <SplashScreen />\n </main>\n </ScalarTeleportRoot>\n</template>\n\n<style>\n#scalar-client {\n position: relative;\n background-color: var(--scalar-background-2);\n}\n.dark-mode #scalar-client {\n background-color: color-mix(in srgb, var(--scalar-background-1) 65%, black);\n}\n\n/*\n * The three-way merge editor needs the full viewport to fit its three\n * Monaco panes. `DocumentCollection.vue` ships the same override for\n * its in-page Sync modal, but the pull flow can run without that view\n * being mounted, so we duplicate the rule here to keep the modal\n * full-bleed.\n */\n.full-size-styles:has(.sync-conflict-modal-root) {\n width: 100dvw !important;\n max-width: 100dvw !important;\n border-right: none !important;\n}\n\n.full-size-styles:has(.sync-conflict-modal-root)::after {\n display: none;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -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":"AA8GA,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\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 <slot name=\"end\" />\n </template>\n </ScalarHeader>\n</template>\n"],"mappings":""}
|
|
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 <span class=\"max-md:hidden\">\n {{ menuTitle }}\n </span>\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 <slot name=\"end\" />\n </template>\n </ScalarHeader>\n</template>\n"],"mappings":""}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { createBlock, createCommentVNode, createSlots, createTextVNode, createVNode, defineComponent, openBlock, renderSlot, toDisplayString, unref, useSlots, withCtx } from "vue";
|
|
1
|
+
import { createBlock, createCommentVNode, createElementVNode, 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
|
+
var _hoisted_1 = { class: "max-md:hidden" };
|
|
5
6
|
var AppHeader_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
6
7
|
__name: "AppHeader",
|
|
7
8
|
props: { menuTitle: {} },
|
|
@@ -34,7 +35,7 @@ var AppHeader_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
|
|
|
34
35
|
key: "0"
|
|
35
36
|
} : void 0, __props.menuTitle ? {
|
|
36
37
|
name: "title",
|
|
37
|
-
fn: withCtx(() => [
|
|
38
|
+
fn: withCtx(() => [createElementVNode("span", _hoisted_1, toDisplayString(__props.menuTitle), 1)]),
|
|
38
39
|
key: "1"
|
|
39
40
|
} : void 0]), 1024), slots.breadcrumb ? renderSlot(_ctx.$slots, "breadcrumb", { key: 0 }) : createCommentVNode("", true)]),
|
|
40
41
|
_: 2
|
|
@@ -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\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 <slot name=\"end\" />\n </template>\n </ScalarHeader>\n</template>\n"],"mappings":"
|
|
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 <span class=\"max-md:hidden\">\n {{ menuTitle }}\n </span>\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 <slot name=\"end\" />\n </template>\n </ScalarHeader>\n</template>\n"],"mappings":";;;;;;;;;;EAsBA,MAAM,OAAO;EAKb,MAAM,QAAQ,UAAA;;uBAsBZ,YA8Ce,MAAA,aAAA,EAAA,EA9CD,OAAM,iCAA+B,EAAA,YAAA;IACtC,OAAK,cAmCD,CAlCb,YAkCa,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;;QA9BjB,MAAM,OAAA;WACX;uBACmB,CAApB,WAAoB,KAAA,QAAA,OAAA,CAAA,CAAA;;gBAGd,QAAA,YAAA;WACL;uBAGM,CAFP,mBAEO,QAFP,YAEO,gBADF,QAAA,UAAS,EAAA,EAAA,CAAA,CAAA;;yBA0BV,MAAM,aADd,WAEsB,KAAA,QAAA,cAAA,EAAA,KAAA,GAAA,CAAA,GAAA,mBAAA,IAAA,KAAA,CAAA,CAAA;;OAGhB,MAAM,MAAA;UACX;sBACkB,CAAnB,WAAmB,KAAA,QAAA,MAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeaderActions.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/AppHeaderActions.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AppHeaderActions.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/app/components/AppHeaderActions.vue"],"names":[],"mappings":"AAwLA,KAAK,WAAW,GAAG;IACjB,sEAAsE;IACtE,oBAAoB,EAAE,OAAO,CAAA;IAC7B,mEAAmE;IACnE,mBAAmB,EAAE,OAAO,CAAA;IAC5B,+DAA+D;IAC/D,qBAAqB,EAAE,OAAO,CAAA;IAC9B,2DAA2D;IAC3D,qBAAqB,EAAE,OAAO,CAAA;IAC9B,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAA;IAClB,4CAA4C;IAC5C,qBAAqB,EAAE,OAAO,CAAA;IAC9B,4CAA4C;IAC5C,qBAAqB,EAAE,OAAO,CAAA;CAC/B,CAAC;AAgdF,QAAA,MAAM,YAAY;;;;;;;;;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeaderActions.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeaderActions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components'\nimport {\n ScalarIconArrowCounterClockwise,\n ScalarIconCloudArrowDown,\n ScalarIconCloudArrowUp,\n ScalarIconCloudSlash,\n ScalarIconFloppyDisk,\n} from '@scalar/icons'\n\ndefineProps<{\n /** Whether the local-workspace Save / Revert cluster should mount. */\n showLocalSaveActions: boolean\n /** Whether the team-workspace Pull / Push cluster should mount. */\n showTeamSyncActions: boolean\n /** Whether the team-workspace Publish cluster should mount. */\n showTeamPublishAction: boolean\n /** Whether the active document has unsaved local edits. */\n isActiveDocumentDirty: boolean\n /** Whether the browser currently reports as offline. */\n isOffline: boolean\n /** Whether the Pull button is clickable. */\n canPullActiveDocument: boolean\n /** Whether the Push button is clickable. */\n canPushActiveDocument: boolean\n}>()\n\nconst emit = defineEmits<{\n save: []\n revert: []\n pull: []\n push: []\n publish: []\n}>()\n</script>\n\n<template>\n <!--\n Local workspace cluster: Save is always mounted while a document is\n active so the affordance does not jump around, and gets disabled\n when the document is clean. Revert only joins it once there is\n something to revert.\n -->\n <template v-if=\"showLocalSaveActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-save-button\"\n :disabled=\"!isActiveDocumentDirty\"\n size=\"xs\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('save')\">\n <ScalarIconFloppyDisk\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span>Save</span>\n </ScalarButton>\n </template>\n <!--\n Team workspace cluster for registry-backed documents. The same Revert\n affordance as local workspaces sits in front of the Pull / Push pair\n so dirty edits can be discarded without going through the registry.\n Pull / Push enablement tracks the cached `VersionStatus` so only one\n of them is actionable at a time.\n -->\n <template v-if=\"showTeamSyncActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n :aria-label=\"isOffline ? 'Pull (offline)' :
|
|
1
|
+
{"version":3,"file":"AppHeaderActions.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeaderActions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components'\nimport {\n ScalarIconArrowCounterClockwise,\n ScalarIconCloudArrowDown,\n ScalarIconCloudArrowUp,\n ScalarIconCloudSlash,\n ScalarIconFloppyDisk,\n} from '@scalar/icons'\n\ndefineProps<{\n /** Whether the local-workspace Save / Revert cluster should mount. */\n showLocalSaveActions: boolean\n /** Whether the team-workspace Pull / Push cluster should mount. */\n showTeamSyncActions: boolean\n /** Whether the team-workspace Publish cluster should mount. */\n showTeamPublishAction: boolean\n /** Whether the active document has unsaved local edits. */\n isActiveDocumentDirty: boolean\n /** Whether the browser currently reports as offline. */\n isOffline: boolean\n /** Whether the Pull button is clickable. */\n canPullActiveDocument: boolean\n /** Whether the Push button is clickable. */\n canPushActiveDocument: boolean\n}>()\n\nconst emit = defineEmits<{\n save: []\n revert: []\n pull: []\n push: []\n publish: []\n}>()\n</script>\n\n<template>\n <!--\n Local workspace cluster: Save is always mounted while a document is\n active so the affordance does not jump around, and gets disabled\n when the document is clean. Revert only joins it once there is\n something to revert.\n -->\n <template v-if=\"showLocalSaveActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n aria-label=\"Save\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-save-button\"\n :disabled=\"!isActiveDocumentDirty\"\n size=\"xs\"\n title=\"Save\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('save')\">\n <ScalarIconFloppyDisk\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Save</span>\n </ScalarButton>\n </template>\n <!--\n Team workspace cluster for registry-backed documents. The same Revert\n affordance as local workspaces sits in front of the Pull / Push pair\n so dirty edits can be discarded without going through the registry.\n Pull / Push enablement tracks the cached `VersionStatus` so only one\n of them is actionable at a time.\n -->\n <template v-if=\"showTeamSyncActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n :aria-label=\"isOffline ? 'Pull (offline)' : 'Pull'\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-pull-button\"\n :disabled=\"!canPullActiveDocument\"\n size=\"xs\"\n :title=\"isOffline ? 'You are offline.' : 'Pull'\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('pull')\">\n <ScalarIconCloudSlash\n v-if=\"isOffline\"\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <ScalarIconCloudArrowDown\n v-else\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Pull</span>\n </ScalarButton>\n <ScalarButton\n :aria-label=\"isOffline ? 'Push (offline)' : 'Push'\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-push-button\"\n :disabled=\"!canPushActiveDocument\"\n size=\"xs\"\n :title=\"isOffline ? 'You are offline.' : 'Push'\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('push')\">\n <ScalarIconCloudSlash\n v-if=\"isOffline\"\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <ScalarIconCloudArrowUp\n v-else\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Push</span>\n </ScalarButton>\n </template>\n <!--\n Team workspace cluster for documents that have not been published\n yet. A single Publish button kicks off the first-time push to the\n registry; once that succeeds the document gets a registry meta and\n switches over to the Pull / Push cluster above on the next render.\n -->\n <ScalarButton\n v-if=\"showTeamPublishAction\"\n :aria-label=\"isOffline ? 'Publish (offline)' : 'Publish'\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-publish-button\"\n :disabled=\"isOffline\"\n size=\"xs\"\n :title=\"isOffline ? 'You are offline.' : 'Publish'\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('publish')\">\n <ScalarIconCloudSlash\n v-if=\"isOffline\"\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <ScalarIconCloudArrowUp\n v-else\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Publish</span>\n </ScalarButton>\n</template>\n"],"mappings":""}
|
|
@@ -41,10 +41,12 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
41
41
|
})]),
|
|
42
42
|
_: 1
|
|
43
43
|
})) : createCommentVNode("", true), createVNode(unref(ScalarButton), {
|
|
44
|
+
"aria-label": "Save",
|
|
44
45
|
class: "shrink-0 gap-1.5",
|
|
45
46
|
"data-testid": "app-header-save-button",
|
|
46
47
|
disabled: !__props.isActiveDocumentDirty,
|
|
47
48
|
size: "xs",
|
|
49
|
+
title: "Save",
|
|
48
50
|
type: "button",
|
|
49
51
|
variant: "solid",
|
|
50
52
|
onClick: _cache[1] || (_cache[1] = ($event) => emit("save"))
|
|
@@ -53,7 +55,7 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
53
55
|
class: "size-3.5",
|
|
54
56
|
size: "sm",
|
|
55
57
|
thickness: "1.5"
|
|
56
|
-
}), _cache[6] || (_cache[6] = createElementVNode("span",
|
|
58
|
+
}), _cache[6] || (_cache[6] = createElementVNode("span", { class: "max-md:hidden" }, "Save", -1))]),
|
|
57
59
|
_: 1
|
|
58
60
|
}, 8, ["disabled"])], 64)) : createCommentVNode("", true),
|
|
59
61
|
__props.showTeamSyncActions ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
@@ -75,12 +77,12 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
75
77
|
_: 1
|
|
76
78
|
})) : createCommentVNode("", true),
|
|
77
79
|
createVNode(unref(ScalarButton), {
|
|
78
|
-
"aria-label": __props.isOffline ? "Pull (offline)" :
|
|
80
|
+
"aria-label": __props.isOffline ? "Pull (offline)" : "Pull",
|
|
79
81
|
class: "shrink-0 gap-1.5",
|
|
80
82
|
"data-testid": "app-header-pull-button",
|
|
81
83
|
disabled: !__props.canPullActiveDocument,
|
|
82
84
|
size: "xs",
|
|
83
|
-
title: __props.isOffline ? "You are offline." :
|
|
85
|
+
title: __props.isOffline ? "You are offline." : "Pull",
|
|
84
86
|
type: "button",
|
|
85
87
|
variant: "solid",
|
|
86
88
|
onClick: _cache[3] || (_cache[3] = ($event) => emit("pull"))
|
|
@@ -95,7 +97,7 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
95
97
|
class: "size-3.5",
|
|
96
98
|
size: "sm",
|
|
97
99
|
thickness: "1.5"
|
|
98
|
-
})), _cache[7] || (_cache[7] = createElementVNode("span",
|
|
100
|
+
})), _cache[7] || (_cache[7] = createElementVNode("span", { class: "max-md:hidden" }, "Pull", -1))]),
|
|
99
101
|
_: 1
|
|
100
102
|
}, 8, [
|
|
101
103
|
"aria-label",
|
|
@@ -103,12 +105,12 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
103
105
|
"title"
|
|
104
106
|
]),
|
|
105
107
|
createVNode(unref(ScalarButton), {
|
|
106
|
-
"aria-label": __props.isOffline ? "Push (offline)" :
|
|
108
|
+
"aria-label": __props.isOffline ? "Push (offline)" : "Push",
|
|
107
109
|
class: "shrink-0 gap-1.5",
|
|
108
110
|
"data-testid": "app-header-push-button",
|
|
109
111
|
disabled: !__props.canPushActiveDocument,
|
|
110
112
|
size: "xs",
|
|
111
|
-
title: __props.isOffline ? "You are offline." :
|
|
113
|
+
title: __props.isOffline ? "You are offline." : "Push",
|
|
112
114
|
type: "button",
|
|
113
115
|
variant: "solid",
|
|
114
116
|
onClick: _cache[4] || (_cache[4] = ($event) => emit("push"))
|
|
@@ -123,7 +125,7 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
123
125
|
class: "size-3.5",
|
|
124
126
|
size: "sm",
|
|
125
127
|
thickness: "1.5"
|
|
126
|
-
})), _cache[8] || (_cache[8] = createElementVNode("span",
|
|
128
|
+
})), _cache[8] || (_cache[8] = createElementVNode("span", { class: "max-md:hidden" }, "Push", -1))]),
|
|
127
129
|
_: 1
|
|
128
130
|
}, 8, [
|
|
129
131
|
"aria-label",
|
|
@@ -133,12 +135,12 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
133
135
|
], 64)) : createCommentVNode("", true),
|
|
134
136
|
__props.showTeamPublishAction ? (openBlock(), createBlock(unref(ScalarButton), {
|
|
135
137
|
key: 2,
|
|
136
|
-
"aria-label": __props.isOffline ? "Publish (offline)" :
|
|
138
|
+
"aria-label": __props.isOffline ? "Publish (offline)" : "Publish",
|
|
137
139
|
class: "shrink-0 gap-1.5",
|
|
138
140
|
"data-testid": "app-header-publish-button",
|
|
139
141
|
disabled: __props.isOffline,
|
|
140
142
|
size: "xs",
|
|
141
|
-
title: __props.isOffline ? "You are offline." :
|
|
143
|
+
title: __props.isOffline ? "You are offline." : "Publish",
|
|
142
144
|
type: "button",
|
|
143
145
|
variant: "solid",
|
|
144
146
|
onClick: _cache[5] || (_cache[5] = ($event) => emit("publish"))
|
|
@@ -153,7 +155,7 @@ var AppHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
153
155
|
class: "size-3.5",
|
|
154
156
|
size: "sm",
|
|
155
157
|
thickness: "1.5"
|
|
156
|
-
})), _cache[9] || (_cache[9] = createElementVNode("span",
|
|
158
|
+
})), _cache[9] || (_cache[9] = createElementVNode("span", { class: "max-md:hidden" }, "Publish", -1))]),
|
|
157
159
|
_: 1
|
|
158
160
|
}, 8, [
|
|
159
161
|
"aria-label",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeaderActions.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeaderActions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components'\nimport {\n ScalarIconArrowCounterClockwise,\n ScalarIconCloudArrowDown,\n ScalarIconCloudArrowUp,\n ScalarIconCloudSlash,\n ScalarIconFloppyDisk,\n} from '@scalar/icons'\n\ndefineProps<{\n /** Whether the local-workspace Save / Revert cluster should mount. */\n showLocalSaveActions: boolean\n /** Whether the team-workspace Pull / Push cluster should mount. */\n showTeamSyncActions: boolean\n /** Whether the team-workspace Publish cluster should mount. */\n showTeamPublishAction: boolean\n /** Whether the active document has unsaved local edits. */\n isActiveDocumentDirty: boolean\n /** Whether the browser currently reports as offline. */\n isOffline: boolean\n /** Whether the Pull button is clickable. */\n canPullActiveDocument: boolean\n /** Whether the Push button is clickable. */\n canPushActiveDocument: boolean\n}>()\n\nconst emit = defineEmits<{\n save: []\n revert: []\n pull: []\n push: []\n publish: []\n}>()\n</script>\n\n<template>\n <!--\n Local workspace cluster: Save is always mounted while a document is\n active so the affordance does not jump around, and gets disabled\n when the document is clean. Revert only joins it once there is\n something to revert.\n -->\n <template v-if=\"showLocalSaveActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-save-button\"\n :disabled=\"!isActiveDocumentDirty\"\n size=\"xs\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('save')\">\n <ScalarIconFloppyDisk\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span>Save</span>\n </ScalarButton>\n </template>\n <!--\n Team workspace cluster for registry-backed documents. The same Revert\n affordance as local workspaces sits in front of the Pull / Push pair\n so dirty edits can be discarded without going through the registry.\n Pull / Push enablement tracks the cached `VersionStatus` so only one\n of them is actionable at a time.\n -->\n <template v-if=\"showTeamSyncActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n :aria-label=\"isOffline ? 'Pull (offline)' :
|
|
1
|
+
{"version":3,"file":"AppHeaderActions.vue.script.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppHeaderActions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components'\nimport {\n ScalarIconArrowCounterClockwise,\n ScalarIconCloudArrowDown,\n ScalarIconCloudArrowUp,\n ScalarIconCloudSlash,\n ScalarIconFloppyDisk,\n} from '@scalar/icons'\n\ndefineProps<{\n /** Whether the local-workspace Save / Revert cluster should mount. */\n showLocalSaveActions: boolean\n /** Whether the team-workspace Pull / Push cluster should mount. */\n showTeamSyncActions: boolean\n /** Whether the team-workspace Publish cluster should mount. */\n showTeamPublishAction: boolean\n /** Whether the active document has unsaved local edits. */\n isActiveDocumentDirty: boolean\n /** Whether the browser currently reports as offline. */\n isOffline: boolean\n /** Whether the Pull button is clickable. */\n canPullActiveDocument: boolean\n /** Whether the Push button is clickable. */\n canPushActiveDocument: boolean\n}>()\n\nconst emit = defineEmits<{\n save: []\n revert: []\n pull: []\n push: []\n publish: []\n}>()\n</script>\n\n<template>\n <!--\n Local workspace cluster: Save is always mounted while a document is\n active so the affordance does not jump around, and gets disabled\n when the document is clean. Revert only joins it once there is\n something to revert.\n -->\n <template v-if=\"showLocalSaveActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n aria-label=\"Save\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-save-button\"\n :disabled=\"!isActiveDocumentDirty\"\n size=\"xs\"\n title=\"Save\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('save')\">\n <ScalarIconFloppyDisk\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Save</span>\n </ScalarButton>\n </template>\n <!--\n Team workspace cluster for registry-backed documents. The same Revert\n affordance as local workspaces sits in front of the Pull / Push pair\n so dirty edits can be discarded without going through the registry.\n Pull / Push enablement tracks the cached `VersionStatus` so only one\n of them is actionable at a time.\n -->\n <template v-if=\"showTeamSyncActions\">\n <ScalarButton\n v-if=\"isActiveDocumentDirty\"\n aria-label=\"Revert changes\"\n class=\"text-c-2 hover:text-c-1 size-6 shrink-0 p-0\"\n data-testid=\"app-header-revert-button\"\n size=\"xs\"\n type=\"button\"\n variant=\"ghost\"\n @click=\"emit('revert')\">\n <ScalarIconArrowCounterClockwise\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n </ScalarButton>\n <ScalarButton\n :aria-label=\"isOffline ? 'Pull (offline)' : 'Pull'\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-pull-button\"\n :disabled=\"!canPullActiveDocument\"\n size=\"xs\"\n :title=\"isOffline ? 'You are offline.' : 'Pull'\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('pull')\">\n <ScalarIconCloudSlash\n v-if=\"isOffline\"\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <ScalarIconCloudArrowDown\n v-else\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Pull</span>\n </ScalarButton>\n <ScalarButton\n :aria-label=\"isOffline ? 'Push (offline)' : 'Push'\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-push-button\"\n :disabled=\"!canPushActiveDocument\"\n size=\"xs\"\n :title=\"isOffline ? 'You are offline.' : 'Push'\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('push')\">\n <ScalarIconCloudSlash\n v-if=\"isOffline\"\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <ScalarIconCloudArrowUp\n v-else\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Push</span>\n </ScalarButton>\n </template>\n <!--\n Team workspace cluster for documents that have not been published\n yet. A single Publish button kicks off the first-time push to the\n registry; once that succeeds the document gets a registry meta and\n switches over to the Pull / Push cluster above on the next render.\n -->\n <ScalarButton\n v-if=\"showTeamPublishAction\"\n :aria-label=\"isOffline ? 'Publish (offline)' : 'Publish'\"\n class=\"shrink-0 gap-1.5\"\n data-testid=\"app-header-publish-button\"\n :disabled=\"isOffline\"\n size=\"xs\"\n :title=\"isOffline ? 'You are offline.' : 'Publish'\"\n type=\"button\"\n variant=\"solid\"\n @click=\"emit('publish')\">\n <ScalarIconCloudSlash\n v-if=\"isOffline\"\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <ScalarIconCloudArrowUp\n v-else\n class=\"size-3.5\"\n size=\"sm\"\n thickness=\"1.5\" />\n <span class=\"max-md:hidden\">Publish</span>\n </ScalarButton>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;EA2BA,MAAM,OAAO;;;IAgBK,QAAA,wBAAA,WAAA,EAAhB,mBA+BW,UAAA,EAAA,KAAA,GAAA,EAAA,CA7BD,QAAA,yBAAA,WAAA,EADR,YAae,MAAA,aAAA,EAAA;;KAXb,cAAW;KACX,OAAM;KACN,eAAY;KACZ,MAAK;KACL,MAAK;KACL,SAAQ;KACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAA;;4BAIQ,CAHpB,YAGoB,MAAA,gCAAA,EAAA;MAFlB,OAAM;MACN,MAAK;MACL,WAAU;;;wCAEd,YAee,MAAA,aAAA,EAAA;KAdb,cAAW;KACX,OAAM;KACN,eAAY;KACX,UAAQ,CAAG,QAAA;KACZ,MAAK;KACL,OAAM;KACN,MAAK;KACL,SAAQ;KACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;;4BAIQ,CAHpB,YAGoB,MAAA,qBAAA,EAAA;MAFlB,OAAM;MACN,MAAK;MACL,WAAU;mCACZ,mBAAuC,QAAA,EAAjC,OAAM,iBAAe,EAAC,QAAI,GAAA,EAAA,CAAA;;;IAUpB,QAAA,uBAAA,WAAA,EAAhB,mBA2DW,UAAA,EAAA,KAAA,GAAA,EAAA;KAzDD,QAAA,yBAAA,WAAA,EADR,YAae,MAAA,aAAA,EAAA;;MAXb,cAAW;MACX,OAAM;MACN,eAAY;MACZ,MAAK;MACL,MAAK;MACL,SAAQ;MACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAA;;6BAIQ,CAHpB,YAGoB,MAAA,gCAAA,EAAA;OAFlB,OAAM;OACN,MAAK;OACL,WAAU;;;;KAEd,YAqBe,MAAA,aAAA,EAAA;MApBZ,cAAY,QAAA,YAAS,mBAAA;MACtB,OAAM;MACN,eAAY;MACX,UAAQ,CAAG,QAAA;MACZ,MAAK;MACJ,OAAO,QAAA,YAAS,qBAAA;MACjB,MAAK;MACL,SAAQ;MACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;;6BAKQ,CAHZ,QAAA,aAAA,WAAA,EADR,YAIoB,MAAA,qBAAA,EAAA;;OAFlB,OAAM;OACN,MAAK;OACL,WAAU;0BACZ,YAIoB,MAAA,yBAAA,EAAA;;OAFlB,OAAM;OACN,MAAK;OACL,WAAU;qCACZ,mBAAuC,QAAA,EAAjC,OAAM,iBAAe,EAAC,QAAI,GAAA,EAAA,CAAA;;;;;;;KAElC,YAqBe,MAAA,aAAA,EAAA;MApBZ,cAAY,QAAA,YAAS,mBAAA;MACtB,OAAM;MACN,eAAY;MACX,UAAQ,CAAG,QAAA;MACZ,MAAK;MACJ,OAAO,QAAA,YAAS,qBAAA;MACjB,MAAK;MACL,SAAQ;MACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;;6BAKQ,CAHZ,QAAA,aAAA,WAAA,EADR,YAIoB,MAAA,qBAAA,EAAA;;OAFlB,OAAM;OACN,MAAK;OACL,WAAU;0BACZ,YAIoB,MAAA,uBAAA,EAAA;;OAFlB,OAAM;OACN,MAAK;OACL,WAAU;qCACZ,mBAAuC,QAAA,EAAjC,OAAM,iBAAe,EAAC,QAAI,GAAA,EAAA,CAAA;;;;;;;;IAU5B,QAAA,yBAAA,WAAA,EADR,YAsBe,MAAA,aAAA,EAAA;;KApBZ,cAAY,QAAA,YAAS,sBAAA;KACtB,OAAM;KACN,eAAY;KACX,UAAU,QAAA;KACX,MAAK;KACJ,OAAO,QAAA,YAAS,qBAAA;KACjB,MAAK;KACL,SAAQ;KACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,UAAA;;4BAKQ,CAHZ,QAAA,aAAA,WAAA,EADR,YAIoB,MAAA,qBAAA,EAAA;;MAFlB,OAAM;MACN,MAAK;MACL,WAAU;yBACZ,YAIoB,MAAA,uBAAA,EAAA;;MAFlB,OAAM;MACN,MAAK;MACL,WAAU;oCACZ,mBAA0C,QAAA,EAApC,OAAM,iBAAe,EAAC,WAAO,GAAA,EAAA,CAAA"}
|
|
@@ -3,8 +3,6 @@ import type { ImportDocumentFromRegistry, RegistryDocumentsState } from '../../.
|
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
/** The app state from @scalar/api-client. */
|
|
5
5
|
app: AppState;
|
|
6
|
-
/** Horizontal indent applied to nested sidebar items, in pixels. */
|
|
7
|
-
indent?: number;
|
|
8
6
|
/**
|
|
9
7
|
* The list of all available registry documents, wrapped in a loading state
|
|
10
8
|
* so the sidebar can render skeleton placeholders while the registry is
|
|
@@ -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":"AA6nBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAcjD,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,0BAA0B,CAAA;AAEjC,KAAK,WAAW,GAAG;IACjB,6CAA6C;IAC7C,GAAG,EAAE,QAAQ,CAAA;IACb;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,sBAAsB,CAAA;IAC1C,8CAA8C;IAC9C,qBAAqB,CAAC,EAAE,0BAA0B,CAAA;CACnD,CAAC;AA6WF,KAAK,gBAAgB,GAAG;IACxB,wCAAwC;IACxC,cAAc,EAAE,MAAM,CAAC;CACtB,CAAC;AAKF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAyoBxD,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-e5f6f69d"]]);
|
|
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 {\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 type { AppState } from '@/v2/features/app'\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 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 {\n ImportDocumentFromRegistry,\n RegistryDocumentsState,\n} 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 }),\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":""}
|
|
1
|
+
{"version":3,"file":"AppSidebar.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\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 type { AppState } from '@/v2/features/app'\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 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 {\n ImportDocumentFromRegistry,\n RegistryDocumentsState,\n} from '@/v2/types/configuration'\n\nconst {\n app,\n registryDocuments = { status: 'success', documents: [] },\n fetchRegistryDocument,\n} = defineProps<{\n /** The app state from @scalar/api-client. */\n app: AppState\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 }),\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-[calc(var(--app-desktop-tabs-height)+var(--spacing-header,48px)+2.5rem)]\">\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":""}
|
|
@@ -10,7 +10,7 @@ import { useSidebarContextMenu } from "../hooks/use-sidebar-context-menu.js";
|
|
|
10
10
|
import { useSidebarDocuments } from "../hooks/use-sidebar-documents.js";
|
|
11
11
|
import { dragHandleFactory } from "../../../helpers/drag-handle-factory.js";
|
|
12
12
|
import { safeRun } from "../../../helpers/safe-run.js";
|
|
13
|
-
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, isRef, mergeModels, normalizeClass,
|
|
13
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, isRef, mergeModels, normalizeClass, onBeforeMount, onBeforeUnmount, openBlock, ref, renderList, renderSlot, unref, useModel, withCtx } from "vue";
|
|
14
14
|
import { ScalarIconButton, ScalarModal, ScalarSidebar, ScalarSidebarButton, ScalarSidebarItems, ScalarSidebarSearchInput, ScalarSidebarSection, useModal } from "@scalar/components";
|
|
15
15
|
import { ScalarIconFolderDashed, ScalarIconFunnel, ScalarIconGearSix, ScalarIconPlus } from "@scalar/icons";
|
|
16
16
|
import { useToasts } from "@scalar/use-toasts";
|
|
@@ -31,7 +31,6 @@ var AppSidebar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
|
|
|
31
31
|
__name: "AppSidebar",
|
|
32
32
|
props: /* @__PURE__ */ mergeModels({
|
|
33
33
|
app: {},
|
|
34
|
-
indent: { default: 20 },
|
|
35
34
|
registryDocuments: { default: () => ({
|
|
36
35
|
status: "success",
|
|
37
36
|
documents: []
|
|
@@ -292,10 +291,7 @@ var AppSidebar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
|
|
|
292
291
|
}])
|
|
293
292
|
}, {
|
|
294
293
|
default: withCtx(() => [
|
|
295
|
-
createElementVNode("div", _hoisted_1, [createVNode(unref(ScalarSidebar), {
|
|
296
|
-
class: "flex min-h-0 flex-1 flex-col max-md:pt-12",
|
|
297
|
-
style: normalizeStyle({ "--scalar-sidebar-indent": __props.indent + "px" })
|
|
298
|
-
}, {
|
|
294
|
+
createElementVNode("div", _hoisted_1, [createVNode(unref(ScalarSidebar), { class: "flex min-h-0 flex-1 flex-col max-md:pt-[calc(var(--app-desktop-tabs-height)+var(--spacing-header,48px)+2.5rem)]" }, {
|
|
299
295
|
default: withCtx(() => [
|
|
300
296
|
!isOnDocumentPage.value ? (openBlock(), createElementBlock("div", _hoisted_2, [createElementVNode("div", _hoisted_3, [
|
|
301
297
|
createVNode(unref(ScalarSidebarButton), {
|
|
@@ -417,7 +413,7 @@ var AppSidebar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ def
|
|
|
417
413
|
renderSlot(_ctx.$slots, "footer", {}, void 0, true)
|
|
418
414
|
]),
|
|
419
415
|
_: 3
|
|
420
|
-
}
|
|
416
|
+
})]),
|
|
421
417
|
createVNode(unref(DocumentSearchModal_default), {
|
|
422
418
|
document: activeDocument.value,
|
|
423
419
|
modalState: unref(searchModal),
|