@scalar/api-client 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/style.css +102 -130
- package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js +9 -0
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.d.ts +2 -0
- package/dist/v2/blocks/operation-block/components/Header.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.js +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.script.js +20 -10
- package/dist/v2/blocks/operation-block/components/Header.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/send-request.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/send-request.js +2 -2
- package/dist/v2/blocks/operation-block/helpers/send-request.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTable.vue.d.ts +2 -2
- package/dist/v2/blocks/request-block/components/RequestTableRow.vue.d.ts +2 -2
- package/dist/v2/blocks/response-block/components/ResponseEmpty.vue.d.ts +2 -2
- package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.d.ts +2 -2
- package/dist/v2/components/sidebar/Sidebar.vue.script.js +2 -2
- package/dist/v2/components/sidebar/Sidebar.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/app/App.vue.d.ts +22 -4
- 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 +57 -54
- package/dist/v2/features/app/App.vue.script.js.map +1 -1
- package/dist/v2/features/app/app-events.d.ts.map +1 -1
- package/dist/v2/features/app/app-events.js +4 -0
- package/dist/v2/features/app/app-events.js.map +1 -1
- package/dist/v2/features/app/app-state.d.ts +7 -1
- package/dist/v2/features/app/app-state.d.ts.map +1 -1
- package/dist/v2/features/app/app-state.js +24 -4
- package/dist/v2/features/app/app-state.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.d.ts +20 -0
- package/dist/v2/features/app/components/AppHeader.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/AppHeader.vue.js +7 -0
- package/dist/v2/features/app/components/AppHeader.vue.js.map +1 -0
- package/dist/v2/features/app/components/AppHeader.vue.script.js +43 -0
- package/dist/v2/features/app/components/AppHeader.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts +17 -56
- 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 +437 -271
- package/dist/v2/features/app/components/AppSidebar.vue.script.js.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.d.ts +18 -0
- package/dist/v2/features/app/helpers/load-registry-document.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/load-registry-document.js +45 -0
- package/dist/v2/features/app/helpers/load-registry-document.js.map +1 -0
- package/dist/v2/features/app/helpers/routes.d.ts +6 -1
- package/dist/v2/features/app/helpers/routes.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/routes.js +84 -75
- package/dist/v2/features/app/helpers/routes.js.map +1 -1
- package/dist/v2/features/app/hooks/use-document-filter.d.ts +38 -0
- package/dist/v2/features/app/hooks/use-document-filter.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-document-filter.js +63 -0
- package/dist/v2/features/app/hooks/use-document-filter.js.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-context-menu.d.ts +17258 -0
- package/dist/v2/features/app/hooks/use-sidebar-context-menu.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-context-menu.js +107 -0
- package/dist/v2/features/app/hooks/use-sidebar-context-menu.js.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts +95 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.js +97 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.js.map +1 -0
- package/dist/v2/features/app/index.d.ts +1 -0
- package/dist/v2/features/app/index.d.ts.map +1 -1
- package/dist/v2/features/collection/components/GetStarted.vue.d.ts +13 -0
- package/dist/v2/features/collection/components/GetStarted.vue.d.ts.map +1 -0
- package/dist/v2/features/collection/components/GetStarted.vue.js +7 -0
- package/dist/v2/features/collection/components/GetStarted.vue.js.map +1 -0
- package/dist/v2/features/collection/components/GetStarted.vue.script.js +101 -0
- package/dist/v2/features/collection/components/GetStarted.vue.script.js.map +1 -0
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.d.ts +7 -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 +29 -10
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/helpers/generate-unique-slug.d.ts +4 -0
- package/dist/v2/features/command-palette/helpers/generate-unique-slug.d.ts.map +1 -1
- package/dist/v2/features/command-palette/helpers/generate-unique-slug.js +5 -1
- package/dist/v2/features/command-palette/helpers/generate-unique-slug.js.map +1 -1
- package/dist/v2/features/search/components/DocumentSearchModal.vue.d.ts +16 -0
- package/dist/v2/features/search/components/DocumentSearchModal.vue.d.ts.map +1 -0
- package/dist/v2/features/search/components/DocumentSearchModal.vue.js +9 -0
- package/dist/v2/features/search/components/DocumentSearchModal.vue.js.map +1 -0
- package/dist/v2/features/search/components/DocumentSearchModal.vue.script.js +123 -0
- package/dist/v2/features/search/components/DocumentSearchModal.vue.script.js.map +1 -0
- package/dist/v2/features/search/components/SearchResult.vue.d.ts +11 -0
- package/dist/v2/features/search/components/SearchResult.vue.d.ts.map +1 -0
- package/dist/v2/features/search/components/SearchResult.vue.js +7 -0
- package/dist/v2/features/search/components/SearchResult.vue.js.map +1 -0
- package/dist/v2/features/search/components/SearchResult.vue.script.js +71 -0
- package/dist/v2/features/search/components/SearchResult.vue.script.js.map +1 -0
- package/dist/v2/features/search/hooks/use-document-search.d.ts +19 -0
- package/dist/v2/features/search/hooks/use-document-search.d.ts.map +1 -0
- package/dist/v2/features/search/hooks/use-document-search.js +42 -0
- package/dist/v2/features/search/hooks/use-document-search.js.map +1 -0
- package/dist/v2/features/search/index.d.ts +2 -0
- package/dist/v2/features/search/index.d.ts.map +1 -1
- package/dist/v2/features/search/index.js +3 -1
- package/dist/v2/helpers/handle-hotkeys.d.ts.map +1 -1
- package/dist/v2/helpers/handle-hotkeys.js +8 -4
- package/dist/v2/helpers/handle-hotkeys.js.map +1 -1
- package/dist/v2/types/configuration.d.ts +1 -0
- package/dist/v2/types/configuration.d.ts.map +1 -1
- package/package.json +13 -12
- package/dist/assets/rabbit.ascii.virtual.js +0 -6
- package/dist/assets/rabbit.ascii.virtual.js.map +0 -1
- package/dist/assets/rabbitjump.ascii.virtual.js +0 -6
- package/dist/assets/rabbitjump.ascii.virtual.js.map +0 -1
- package/dist/v2/features/app/components/DownloadAppButton.vue.d.ts +0 -4
- package/dist/v2/features/app/components/DownloadAppButton.vue.d.ts.map +0 -1
- package/dist/v2/features/app/components/DownloadAppButton.vue.js +0 -9
- package/dist/v2/features/app/components/DownloadAppButton.vue.js.map +0 -1
- package/dist/v2/features/app/components/DownloadAppButton.vue.script.js +0 -21
- package/dist/v2/features/app/components/DownloadAppButton.vue.script.js.map +0 -1
|
@@ -1,72 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
3
|
-
import type {
|
|
4
|
-
import type { WorkspaceEventBus } from '@scalar/workspace-store/events';
|
|
5
|
-
import type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation';
|
|
6
|
-
import type { ClientLayout } from '../../../../v2/types/layout';
|
|
1
|
+
import type { AppState } from '@scalar/api-client/v2/features/app';
|
|
2
|
+
import { type RegistryDocumentsState } from '../../../../v2/features/app/hooks/use-sidebar-documents.js';
|
|
3
|
+
import type { ImportDocumentFromRegistry } from '../../../../v2/types/configuration';
|
|
7
4
|
type __VLS_Props = {
|
|
5
|
+
/** The app state from @scalar/api-client. */
|
|
6
|
+
app: AppState;
|
|
7
|
+
/** Horizontal indent applied to nested sidebar items, in pixels. */
|
|
8
|
+
indent?: number;
|
|
8
9
|
/**
|
|
9
|
-
* The
|
|
10
|
+
* The list of all available registry documents, wrapped in a loading state
|
|
11
|
+
* so the sidebar can render skeleton placeholders while the registry is
|
|
12
|
+
* still being fetched.
|
|
10
13
|
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
sidebarState: SidebarState<TraversedEntry>;
|
|
16
|
-
/**
|
|
17
|
-
* Whether the workspace overview sidebar is currently open
|
|
18
|
-
*/
|
|
19
|
-
isWorkspaceOpen?: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* The currently active workspace.
|
|
22
|
-
* This represents the workspace that the user is currently working in.
|
|
23
|
-
*/
|
|
24
|
-
activeWorkspace: {
|
|
25
|
-
id: string;
|
|
26
|
-
label: string;
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* The list of all available workspaces.
|
|
30
|
-
* Used to render options for workspace switching and selection.
|
|
31
|
-
*/
|
|
32
|
-
workspaces: WorkspaceGroup[];
|
|
33
|
-
/**
|
|
34
|
-
* The workspace event bus for handling workspace-level events.
|
|
35
|
-
* Used for triggering and responding to workspace changes and actions.
|
|
36
|
-
*/
|
|
37
|
-
eventBus: WorkspaceEventBus;
|
|
38
|
-
/**
|
|
39
|
-
* The WorkspaceStore instance for managing workspace state and actions.
|
|
40
|
-
* Provides methods and state for interacting with the current workspace.
|
|
41
|
-
*/
|
|
42
|
-
store: WorkspaceStore;
|
|
43
|
-
};
|
|
44
|
-
type __VLS_Slots = {
|
|
45
|
-
/** Slot for customizing the actions section of the sidebar menu. */
|
|
46
|
-
sidebarMenuActions?(): unknown;
|
|
14
|
+
registryDocuments?: RegistryDocumentsState;
|
|
15
|
+
/** A function to fetch a registry document */
|
|
16
|
+
fetchRegistryDocument?: ImportDocumentFromRegistry;
|
|
47
17
|
};
|
|
48
18
|
type __VLS_ModelProps = {
|
|
49
|
-
/** Controls the visibility of the sidebar */
|
|
50
|
-
'isSidebarOpen': boolean;
|
|
51
19
|
/** Controls the width of the sidebar */
|
|
52
20
|
'sidebarWidth': number;
|
|
53
21
|
};
|
|
54
22
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
23
|
+
declare var __VLS_150: {};
|
|
24
|
+
type __VLS_Slots = {} & {
|
|
25
|
+
footer?: (props: typeof __VLS_150) => any;
|
|
26
|
+
};
|
|
55
27
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
56
28
|
"update:sidebarWidth": (value: number) => any;
|
|
57
|
-
"update:isSidebarOpen": (value: boolean) => any;
|
|
58
|
-
} & {
|
|
59
|
-
"create:workspace": () => any;
|
|
60
|
-
"select:workspace": (id?: string | undefined) => any;
|
|
61
|
-
selectItem: (id: string) => any;
|
|
62
|
-
"click:workspace": () => any;
|
|
63
29
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
64
|
-
"onCreate:workspace"?: (() => any) | undefined;
|
|
65
|
-
"onSelect:workspace"?: ((id?: string | undefined) => any) | undefined;
|
|
66
|
-
onSelectItem?: ((id: string) => any) | undefined;
|
|
67
30
|
"onUpdate:sidebarWidth"?: ((value: number) => any) | undefined;
|
|
68
|
-
"onClick:workspace"?: (() => any) | undefined;
|
|
69
|
-
"onUpdate:isSidebarOpen"?: ((value: boolean) => any) | undefined;
|
|
70
31
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
71
32
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
72
33
|
declare const _default: typeof __VLS_export;
|
|
@@ -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":"AAorBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAwClE,OAAO,EAEL,KAAK,sBAAsB,EAE5B,MAAM,+CAA+C,CAAA;AAGtD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAA;AAE1E,KAAK,WAAW,GAAG;IACjB,6CAA6C;IAC7C,GAAG,EAAE,QAAQ,CAAA;IACb,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,sBAAsB,CAAA;IAC1C,8CAA8C;IAC9C,qBAAqB,CAAC,EAAE,0BAA0B,CAAA;CACnD,CAAC;AAmVF,KAAK,gBAAgB,GAAG;IACxB,wCAAwC;IACxC,cAAc,EAAE,MAAM,CAAC;CACtB,CAAC;AAKF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAyzBxD,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-e803152c"]]);
|
|
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 ScalarButton,\n ScalarIconButton,\n ScalarModal,\n ScalarSidebarItem,\n useModal,\n type WorkspaceGroup,\n} from '@scalar/components'\nimport { isMacOS } from '@scalar/helpers/general/is-mac-os'\nimport {\n ScalarIconDotsThree,\n ScalarIconGearSix,\n ScalarIconPlus,\n} from '@scalar/icons'\nimport { LibraryIcon } from '@scalar/icons/library'\nimport type { DraggingItem, HoveredItem, SidebarState } from '@scalar/sidebar'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport type {\n TraversedEntry,\n TraversedOperation,\n} from '@scalar/workspace-store/schemas/navigation'\nimport { capitalize, computed, nextTick, ref } from 'vue'\n\nimport Rabbit from '@/assets/rabbit.ascii?raw'\nimport RabbitJump from '@/assets/rabbitjump.ascii?raw'\nimport ScalarAsciiArt from '@/components/ScalarAsciiArt.vue'\nimport DeleteSidebarListElement from '@/components/Sidebar/Actions/DeleteSidebarListElement.vue'\nimport { Sidebar } from '@/v2/components/sidebar'\nimport DownloadAppButton from '@/v2/features/app/components/DownloadAppButton.vue'\nimport SidebarItemMenu from '@/v2/features/app/components/SidebarItemMenu.vue'\nimport { createTempOperation } from '@/v2/features/app/helpers/create-temp-operation'\nimport { dragHandleFactory } from '@/v2/helpers/drag-handle-factory'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nconst { sidebarState, layout, activeWorkspace, workspaces, store, eventBus } =\n defineProps<{\n /**\n * The current layout of the app (e.g., 'desktop', 'web')\n */\n layout: ClientLayout\n\n /**\n * The sidebar state, holding navigation items and state\n */\n sidebarState: SidebarState<TraversedEntry>\n\n /**\n * Whether the workspace overview sidebar is currently open\n */\n isWorkspaceOpen?: boolean\n /**\n * The currently active workspace.\n * This represents the workspace that the user is currently working in.\n */\n activeWorkspace: { id: string; label: string }\n /**\n * The list of all available workspaces.\n * Used to render options for workspace switching and selection.\n */\n workspaces: WorkspaceGroup[]\n /**\n * The workspace event bus for handling workspace-level events.\n * Used for triggering and responding to workspace changes and actions.\n */\n eventBus: WorkspaceEventBus\n /**\n * The WorkspaceStore instance for managing workspace state and actions.\n * Provides methods and state for interacting with the current workspace.\n */\n store: WorkspaceStore\n }>()\n\nconst emit = defineEmits<{\n /** Emitted when the workspace button in the sidebar is clicked */\n (e: 'click:workspace'): void\n /** Emitted when a navigation or sidebar item is selected by ID */\n (e: 'selectItem', id: string): void\n /** Emitted when a workspace is selected by optional ID */\n (e: 'select:workspace', id?: string): void\n /** Emitted when the user requests to create a new workspace */\n (e: 'create:workspace'): void\n}>()\n\ndefineSlots<{\n /** Slot for customizing the actions section of the sidebar menu. */\n sidebarMenuActions?(): unknown\n}>()\n\n/** The label for the workspace button in the sidebar */\nconst workspaceLabel = computed(() => capitalize(activeWorkspace.label))\n\n/** Controls the visibility of the sidebar */\nconst isSidebarOpen = defineModel<boolean>('isSidebarOpen', {\n required: true,\n})\n\n/** Controls the width of the sidebar */\nconst sidebarWidth = defineModel<number>('sidebarWidth', {\n required: true,\n default: 288,\n})\n\n/** Calculate if we should show the getting started section */\nconst showGettingStarted = computed(() => sidebarState.items.value.length <= 1)\n\n/*\n * Setup drag and drop handlers for sidebar items.\n * The dragHandleFactory takes the current workspace store and sidebar state,\n * and returns the appropriate handlers for drag ending and droppability.\n *\n * We use computed to ensure the handlers are recreated when the store or sidebarState changes,\n * so they always have access to the latest values.\n */\nconst dragHandlers = computed(() =>\n dragHandleFactory({\n store,\n sidebarState,\n }),\n)\n\nconst handleDragEnd = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => {\n return dragHandlers.value.handleDragEnd(draggingItem, hoveredItem)\n}\n\nconst isDroppable = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => {\n return dragHandlers.value.isDroppable(draggingItem, hoveredItem)\n}\n\n/** The current target for the dropdown menu */\nconst menuTarget = ref<{\n /** The sidebar item that the menu is targeting */\n item: TraversedEntry\n /** A reference to the element that the menu is for */\n el: HTMLElement\n /** Whether or not to show the menu */\n showMenu: boolean\n} | null>(null)\n\nconst deleteModalState = useModal()\n\n/** Computes the message for the delete modal */\nconst deleteMessage = computed(() => {\n const item = menuTarget.value?.item\n\n if (item?.type === 'document') {\n return \"This cannot be undone. You're about to delete the document and all tags and operations inside it.\"\n }\n\n return `Are you sure you want to delete this ${item?.type ?? 'item'}? This action cannot be undone.`\n})\n\n/** Deletes an item from the sidebar by emitting the appropriate event */\nconst handleDelete = () => {\n const item = menuTarget.value?.item\n\n if (!item) {\n return\n }\n\n const result = sidebarState.getEntryById(item.id)\n\n const document = getParentEntry('document', result)\n const operation = getParentEntry('operation', result)\n\n if (!document) {\n return\n }\n\n if (item.type === 'document') {\n eventBus.emit('document:delete:document', { name: document.name })\n } else if (item.type === 'tag') {\n eventBus.emit('tag:delete:tag', {\n documentName: document.name,\n name: item.name,\n })\n } else if (item.type === 'operation') {\n if (!operation) {\n return\n }\n\n eventBus.emit('operation:delete:operation', {\n meta: {\n method: operation.method,\n path: operation.path,\n },\n documentName: document.name,\n })\n } else if (item.type === 'example') {\n if (!operation) {\n return\n }\n\n eventBus.emit('operation:delete:example', {\n meta: {\n method: operation.method,\n path: operation.path,\n exampleKey: item.name,\n },\n documentName: document.name,\n })\n }\n\n /** Clean up after deletion */\n deleteModalState.hide()\n menuTarget.value = null\n}\n\n/** Opens the dropdown menu for the given item */\nconst openMenu = async (\n event: MouseEvent | KeyboardEvent,\n item: TraversedEntry,\n) => {\n if (menuTarget.value?.showMenu) {\n return\n }\n\n const el = event.currentTarget as HTMLElement\n menuTarget.value = { item, el, showMenu: true }\n\n // Wait for the target to bind to the element\n await nextTick()\n\n // Re-dispatch the event on the target to open the menu\n const cloned =\n event instanceof MouseEvent\n ? new MouseEvent(event.type, event)\n : new KeyboardEvent(event.type, event)\n\n menuTarget.value?.el.dispatchEvent(cloned)\n}\n\n/** Closes the dropdown menu */\nconst closeMenu = () => {\n if (menuTarget.value) {\n menuTarget.value.showMenu = false\n }\n}\n\n/**\n * Creates a new operation directly from the empty folder slot.\n * Uses a unique temporary path to avoid conflicts, then navigates to the operation,\n * updates the path to `/`, and focuses the address bar so the user can immediately start typing.\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\n if (!documentName) {\n console.error('Document name not found')\n return\n }\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus,\n tags: tagName ? [tagName] : undefined,\n })\n}\n\n/**\n * Navigates to the operations page for the provided operation item.\n * Emits a navigation event for the operation overview page.\n */\nconst navigateToOperationsPage = (item: TraversedOperation) => {\n const operationWithParent = sidebarState.getEntryById(item.id)\n const documentSlug = getParentEntry('document', operationWithParent)?.name\n\n eventBus.emit('ui:navigate', {\n page: 'operation',\n path: 'overview',\n operationPath: item.path,\n method: item.method,\n documentSlug: documentSlug,\n })\n}\n</script>\n\n<template>\n <div class=\"flex\">\n <Sidebar\n v-model:sidebarWidth=\"sidebarWidth\"\n :activeWorkspace=\"activeWorkspace\"\n :class=\"[\n 'max-md:inset-y-0 max-md:z-2 max-md:w-full!',\n isSidebarOpen ? 'max-md:absolute! max-md:flex!' : 'max-md:hidden!',\n ]\"\n :documents=\"Object.values(store.workspace.documents)\"\n :isDroppable=\"isDroppable\"\n :layout=\"layout\"\n :sidebarState=\"sidebarState\"\n :workspaces=\"workspaces\"\n @create:workspace=\"emit('create:workspace')\"\n @navigate:to:settings=\"\n eventBus.emit('ui:navigate', { page: 'workspace', path: 'settings' })\n \"\n @reorder=\"\n (draggingItem, hoveredItem) => handleDragEnd(draggingItem, hoveredItem)\n \"\n @select:workspace=\"(id) => emit('select:workspace', id)\"\n @selectItem=\"(id) => emit('selectItem', id)\">\n <template #sidebarMenuActions>\n <slot name=\"sidebarMenuActions\" />\n </template>\n <!-- Workspace Identifier -->\n <template #workspaceButton>\n <ScalarSidebarItem\n is=\"button\"\n :active=\"isWorkspaceOpen\"\n @click=\"emit('click:workspace')\">\n {{ workspaceLabel }}\n </ScalarSidebarItem>\n </template>\n\n <!-- Decorator dropdown menu -->\n <template #decorator=\"{ item }\">\n <div class=\"flex items-center gap-0.5\">\n <ScalarIconButton\n v-if=\"item.type === 'operation'\"\n :icon=\"ScalarIconGearSix\"\n label=\"Operation settings\"\n size=\"sm\"\n weight=\"bold\"\n @click.stop=\"navigateToOperationsPage(item)\"\n @keydown.enter.stop=\"navigateToOperationsPage(item)\"\n @keydown.space.stop=\"navigateToOperationsPage(item)\" />\n <ScalarIconButton\n aria-expanded=\"false\"\n aria-haspopup=\"menu\"\n :icon=\"ScalarIconDotsThree\"\n label=\"More options\"\n size=\"sm\"\n weight=\"bold\"\n @click.stop=\"(e: MouseEvent) => openMenu(e, item)\"\n @keydown.down.stop=\"(e: KeyboardEvent) => openMenu(e, item)\"\n @keydown.enter.stop=\"(e: KeyboardEvent) => openMenu(e, item)\"\n @keydown.space.stop=\"(e: KeyboardEvent) => openMenu(e, item)\"\n @keydown.up.stop=\"(e: KeyboardEvent) => openMenu(e, item)\" />\n </div>\n </template>\n\n <!-- Dirty document icon slot -->\n <template #icon=\"{ item }\">\n <template\n v-if=\"\n item.type === 'document' &&\n store.workspace.documents[item.name]?.['x-scalar-is-dirty'] === true\n \">\n <div class=\"relative flex items-center\">\n <LibraryIcon\n class=\"block\"\n :src=\"\n ('icon' in item && item.icon) || 'interface-content-folder'\n \" />\n <div\n class=\"bg-c-accent absolute -top-0.5 -right-0.5 size-1.5 rounded-full\">\n <span class=\"sr-only\">Unsaved changes</span>\n </div>\n </div>\n </template>\n </template>\n\n <!-- Empty folder slot -->\n <template #empty=\"{ item }\">\n <ScalarSidebarItem\n is=\"button\"\n @click=\"handleAddEmptyFolder(item)\">\n <template #icon>\n <ScalarIconPlus />\n </template>\n <template #default>Add operation</template>\n </ScalarSidebarItem>\n </template>\n\n <!-- Getting started section -->\n <template\n v-if=\"layout !== 'modal'\"\n #footer>\n <div\n :class=\"{\n 'empty-sidebar-item border-t': showGettingStarted,\n }\">\n <div\n v-if=\"showGettingStarted\"\n class=\"empty-sidebar-item-content overflow-hidden px-2.5 py-2.5\">\n <div class=\"rabbit-ascii relative m-auto mt-2 h-[68px] w-[60px]\">\n <ScalarAsciiArt\n :art=\"Rabbit\"\n class=\"rabbitsit font-bold\" />\n <ScalarAsciiArt\n :art=\"RabbitJump\"\n class=\"rabbitjump absolute top-0 left-0 font-bold\" />\n </div>\n <div class=\"mt-2 mb-2 text-center text-sm text-balance\">\n <b class=\"font-medium\">Let's Get Started</b>\n <p class=\"mt-2 leading-5\">\n Create request, folder, collection or import from\n OpenAPI/Postman\n </p>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1.5 p-2\">\n <ScalarButton\n v-if=\"showGettingStarted\"\n class=\"w-full\"\n size=\"sm\"\n @click=\"\n eventBus.emit('ui:open:command-palette', {\n action: 'import-from-openapi-swagger-postman-curl',\n payload: undefined,\n })\n \">\n Import Collection\n </ScalarButton>\n\n <ScalarButton\n class=\"w-full\"\n hotkey=\"K\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"eventBus.emit('ui:open:command-palette')\">\n Add Item \n\n <span\n class=\"text-sidebar-c-2 rounded border px-1.25 py-1 text-xs leading-none font-medium uppercase\">\n <template v-if=\"isMacOS()\">\n <span class=\"sr-only\">Command</span>\n <span aria-hidden=\"true\">⌘</span>\n </template>\n <template v-else>\n <span class=\"sr-only\">CTRL</span>\n <span aria-hidden=\"true\">⌃</span>\n </template>\n K\n </span>\n </ScalarButton>\n <DownloadAppButton v-if=\"layout === 'web'\" />\n </div>\n </div>\n </template>\n </Sidebar>\n <SidebarItemMenu\n v-if=\"menuTarget?.showMenu\"\n :eventBus=\"eventBus\"\n :item=\"menuTarget.item\"\n :sidebarState=\"sidebarState\"\n :target=\"menuTarget.el\"\n :workspaceStore=\"store\"\n @closeMenu=\"closeMenu\"\n @showDeleteModal=\"deleteModalState.show()\" />\n\n <!-- Delete Modal -->\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 </div>\n</template>\n\n<style scoped>\n.empty-sidebar-item-content {\n display: none;\n}\n.empty-sidebar-item .empty-sidebar-item-content {\n display: block;\n}\n.rabbitjump {\n opacity: 0;\n}\n.empty-sidebar-item:hover .rabbitjump {\n opacity: 1;\n animation: rabbitAnimation 0.5s steps(1) infinite;\n}\n.empty-sidebar-item:hover .rabbitsit {\n opacity: 0;\n animation: rabbitAnimation2 0.5s steps(1) infinite;\n}\n.empty-sidebar-item:hover .rabbit-ascii {\n animation: rabbitRun 8s infinite linear;\n}\n@keyframes rabbitRun {\n 0% {\n transform: translate3d(0, 0, 0);\n }\n 25% {\n transform: translate3d(250px, 0, 0);\n }\n 25.01% {\n transform: translate3d(-250px, 0, 0);\n }\n 75% {\n transform: translate3d(250px, 0, 0);\n }\n 75.01% {\n transform: translate3d(-250px, 0, 0);\n }\n 100% {\n transform: translate3d(0, 0, 0);\n }\n}\n@keyframes rabbitAnimation {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0;\n }\n}\n@keyframes rabbitAnimation2 {\n 0%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n transform: translate3d(0, -8px, 0);\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"AppSidebar.vue.js","names":[],"sources":["../../../../../src/v2/features/app/components/AppSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AppState } from '@scalar/api-client/v2/features/app'\nimport {\n ScalarIconButton,\n ScalarModal,\n ScalarSidebar,\n ScalarSidebarButton,\n ScalarSidebarItem,\n ScalarSidebarItems,\n ScalarSidebarNestedItems,\n ScalarSidebarSearchInput,\n ScalarSidebarSection,\n useModal,\n} from '@scalar/components'\nimport {\n ScalarIconCaretLeft,\n ScalarIconDotsThree,\n ScalarIconFolderDashed,\n ScalarIconFunnel,\n ScalarIconGearSix,\n ScalarIconMagnifyingGlass,\n ScalarIconPlus,\n} from '@scalar/icons'\nimport {\n filterItems,\n SidebarItem,\n type DraggingItem,\n type HoveredItem,\n} from '@scalar/sidebar'\nimport { useToasts } from '@scalar/use-toasts'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, onBeforeMount, onBeforeUnmount, ref } from 'vue'\n\nimport DeleteSidebarListElement from '@/components/Sidebar/Actions/DeleteSidebarListElement.vue'\nimport { Resize } from '@/v2/components/resize'\nimport SidebarItemMenu from '@/v2/features/app/components/SidebarItemMenu.vue'\nimport { createTempOperation } from '@/v2/features/app/helpers/create-temp-operation'\nimport { loadRegistryDocument } from '@/v2/features/app/helpers/load-registry-document'\nimport { useDocumentFilter } from '@/v2/features/app/hooks/use-document-filter'\nimport { useSidebarContextMenu } from '@/v2/features/app/hooks/use-sidebar-context-menu'\nimport {\n useSidebarDocuments,\n type RegistryDocumentsState,\n type SidebarDocumentItem,\n} from '@/v2/features/app/hooks/use-sidebar-documents'\nimport { DocumentSearchModal } from '@/v2/features/search'\nimport { dragHandleFactory } from '@/v2/helpers/drag-handle-factory'\nimport type { ImportDocumentFromRegistry } from '@/v2/types/configuration'\n\nconst {\n app,\n indent = 20,\n registryDocuments = { status: 'success', documents: [] },\n fetchRegistryDocument,\n} = defineProps<{\n /** The app state from @scalar/api-client. */\n app: AppState\n /** Horizontal indent applied to nested sidebar items, in pixels. */\n indent?: number\n /**\n * The list of all available registry documents, wrapped in a loading state\n * so the sidebar can render skeleton placeholders while the registry is\n * still being fetched.\n */\n registryDocuments?: RegistryDocumentsState\n /** A function to fetch a registry document */\n fetchRegistryDocument?: ImportDocumentFromRegistry\n}>()\n\nconst { toast } = useToasts()\n\n/**\n * Whether the caller is still fetching the list of registry documents. We\n * only surface the loading state on team workspaces because local workspaces\n * never consult the registry\n */\nconst isLoadingRegistry = computed(\n () =>\n registryDocuments.status === 'loading' &&\n app.workspace.isTeamWorkspace.value,\n)\n\nconst { rest } = useSidebarDocuments({\n app,\n managedDocs: () => registryDocuments.documents ?? [],\n})\n\n/**\n * Whether the workspace truly has no documents to show. Distinct from the\n * filter producing no results: we only surface the \"No APIs yet\" empty state\n * when the workspace is genuinely empty so users see a clear call-to-action\n * instead of a confusing blank space.\n */\nconst isEmpty = computed(\n () => !isLoadingRegistry.value && rest.value.length === 0,\n)\n\n/**\n * Fuzzy filter over the top-level documents. Owns its own input visibility,\n * query string and Fuse index. See `use-document-filter.ts` for details.\n */\nconst {\n isVisible: isFilterVisible,\n query: filterQuery,\n filteredItems: filteredRest,\n toggle: toggleFilter,\n} = useDocumentFilter(rest)\n\nconst sidebarState = app.sidebar.state\n\n/** Which registry documents are currently being fetched. */\nconst loadingKeys = ref<Record<string, boolean>>({})\n\n/**\n * Check if the given {@link SidebarDocumentItem} is the currently active document (from the sidebar state).\n */\nconst isDocActive = (item: SidebarDocumentItem) => {\n if (!item.navigation) {\n return false\n }\n\n return (\n sidebarState.selectedItem.value === item.navigation.id ||\n Boolean(sidebarState.selectedItems.value[item.navigation.id])\n )\n}\n\nconst handleDocumentClick = async (item: SidebarDocumentItem) => {\n if (item.navigation) {\n app.sidebar.handleSelectItem(item.navigation.id)\n return\n }\n\n if (!item.registry || !app.store.value) {\n console.warn('Document does not have a sidebar navigation, skipping...')\n return\n }\n\n if (!fetchRegistryDocument) {\n console.warn(\n 'You need to provide a fetchRegistryDocument function to load registry documents',\n )\n return\n }\n\n if (loadingKeys.value[item.key]) {\n return\n }\n\n loadingKeys.value[item.key] = true\n\n const result = await loadRegistryDocument({\n fetcher: fetchRegistryDocument,\n workspaceStore: app.store.value,\n namespace: item.registry.namespace,\n slug: item.registry.slug,\n })\n\n loadingKeys.value[item.key] = false\n\n if (!result.ok) {\n toast(result.error, 'error')\n return\n }\n\n // After loading, route to the document overview. `syncSidebar` will then\n // mark the document as selected and the template's `:open=\"isDocActive\"`\n // binding drills the sidebar in automatically — no local state needed.\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug: result.documentName,\n })\n}\n\nconst isSelected = (id: string) => sidebarState.isSelected(id)\nconst isExpanded = (id: string) => sidebarState.isExpanded(id)\n\nconst handleSelectItem = (id: string) => {\n app.sidebar.handleSelectItem(id)\n}\n\nconst handleToggleGroup = (id: string) => {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n}\n\n/**\n * Drag-and-drop handlers for sidebar items. The factory reads from the live\n * workspace store and shared sidebar state, so it reflects the latest\n * navigation tree on every drag. Mirrors the behaviour of the old\n * `AppSidebar.vue` (documents, tags, and operations can be reordered, and\n * operations can be moved between tags/documents).\n */\nconst dragHandlers = computed(() =>\n dragHandleFactory({\n store: app.store,\n sidebarState,\n }),\n)\n\nconst handleDragEnd = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => dragHandlers.value.handleDragEnd(draggingItem, hoveredItem)\n\nconst isDroppable = (\n draggingItem: DraggingItem,\n hoveredItem: HoveredItem,\n): boolean => dragHandlers.value.isDroppable(draggingItem, hoveredItem)\n\n/**\n * Contextual \"more\" dropdown for tags, operations and examples, together\n * with the shared delete-confirmation modal it triggers.\n */\nconst {\n menuTarget,\n deleteModalState,\n deleteMessage,\n openMenu,\n closeMenu,\n handleDelete,\n} = useSidebarContextMenu({\n eventBus: app.eventBus,\n sidebarState,\n})\n\n/**\n * Create a new operation from an empty folder slot inside a tag or document.\n * If the entry is a tag, the new operation inherits that tag so it stays\n * grouped under the same folder.\n */\nconst handleAddEmptyFolder = (item: TraversedEntry) => {\n const itemWithParent = sidebarState.getEntryById(item.id)\n const documentName = getParentEntry('document', itemWithParent)?.name\n const tagName = getParentEntry('tag', itemWithParent)?.name\n const store = app.store.value\n\n if (!documentName || !store) {\n console.error('Document name not found')\n return\n }\n\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus: app.eventBus,\n tags: tagName ? [tagName] : undefined,\n })\n}\n\nconst handleCreate = () => {\n app.eventBus.emit('ui:open:command-palette', {\n action: 'create-openapi-document',\n payload: undefined,\n })\n}\n\n/**\n * Create a new operation inside the given document and immediately navigate to\n * it. Uses `createTempOperation` so the operation gets a unique `/temp…` path,\n * then the sidebar focuses the new example and the address bar is focused so\n * the user can start typing the real path right away.\n */\nconst handleCreateOperation = (item: SidebarDocumentItem) => {\n const documentName = item.documentName\n const store = app.store.value\n\n if (!documentName || !store) {\n console.warn('Cannot create an operation: no document loaded')\n return\n }\n\n createTempOperation(documentName, {\n existingPaths: new Set(\n Object.keys(store.workspace.documents[documentName]?.paths ?? {}),\n ),\n eventBus: app.eventBus,\n })\n}\n\n/**\n * Navigates back to the workspace \"Get started\" page.\n */\nconst handleBack = () => {\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'get-started',\n })\n}\n\n/**\n * True when the user is currently viewing a document (any of its subpages).\n * When inside a document, the sidebar actions are scoped to that document:\n * the gear icon opens the document (collection) settings and the filter icon\n * becomes a search icon that focuses the search input.\n */\nconst isOnDocumentPage = computed(() =>\n Boolean(app.activeEntities.documentSlug.value),\n)\n\nconst handleOpenSettings = () => {\n if (isOnDocumentPage.value) {\n app.eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'settings',\n documentSlug: app.activeEntities.documentSlug.value,\n })\n return\n }\n\n app.eventBus.emit('ui:navigate', {\n page: 'workspace',\n path: 'settings',\n })\n}\n\n/**\n * Controls the per-document search modal. Only used when the user is drilled\n * into a single document and clicks the magnifying-glass icon.\n */\nconst searchModal = useModal()\n\n/**\n * The OpenAPI document currently selected in the workspace. The search modal\n * scopes its Fuse index to this document so results never leak across\n * collections.\n */\nconst activeDocument = computed(() => app.store.value?.workspace.activeDocument)\n\nconst handleFilterOrSearch = () => {\n // Inside a document, this icon opens a modal search that is scoped to that\n // single document (similar to the reference search modal), so users can jump\n // to any operation / tag / heading without noise from other documents.\n if (isOnDocumentPage.value) {\n searchModal.show()\n return\n }\n\n // At the top-level documents view, the icon toggles a lightweight filter\n // that narrows the visible documents by title.\n toggleFilter()\n}\n\n/**\n * Handle the `ui:focus:search` event. Dispatch is driven by the shared\n * `handleHotkeys` helper (Cmd/Ctrl+J) or by programmatic callers such as the\n * workspace \"Get started\" page. When a `KeyboardEvent` is included we\n * preventDefault to override the browser's Cmd+J (downloads panel), then\n * delegate to `handleFilterOrSearch`, which already branches on whether the\n * user is viewing a document or the workspace root.\n */\nconst handleSearchHotkey = (payload: { event: KeyboardEvent } | undefined) => {\n payload?.event.preventDefault()\n handleFilterOrSearch()\n}\n\n/**\n * Handle the `ui:open:settings` event (Cmd/Ctrl+I). Same delegation model as\n * `handleSearchHotkey`: preventDefault on the originating keyboard event (if\n * any) and hand off to `handleOpenSettings`, which routes to the document-\n * level settings page when a document is active and the workspace-level\n * settings page otherwise.\n */\nconst handleSettingsHotkey = (\n payload: { event: KeyboardEvent } | undefined,\n) => {\n payload?.event.preventDefault()\n handleOpenSettings()\n}\n\nonBeforeMount(() => {\n app.eventBus.on('ui:focus:search', handleSearchHotkey)\n app.eventBus.on('ui:open:settings', handleSettingsHotkey)\n})\nonBeforeUnmount(() => {\n app.eventBus.off('ui:focus:search', handleSearchHotkey)\n app.eventBus.off('ui:open:settings', handleSettingsHotkey)\n})\n\n/**\n * Navigate to the selected search result. `scroll-to:nav-item` is already\n * wired up through the app event bus to update the sidebar + route, matching\n * the behaviour used elsewhere in the app.\n */\nconst handleSearchSelect = (id: string) => {\n app.eventBus.emit('scroll-to:nav-item', { id })\n}\n\n/** Controls the width of the sidebar */\nconst sidebarWidth = defineModel<number>('sidebarWidth', {\n required: true,\n default: 288,\n})\n</script>\n\n<template>\n <Resize\n v-model:width=\"sidebarWidth\"\n class=\"flex flex-col max-md:inset-y-0 max-md:z-2 max-md:w-full!\"\n :class=\"{\n 'max-md:absolute! max-md:flex!': app.sidebar.isOpen.value,\n 'max-md:hidden!': !app.sidebar.isOpen.value,\n }\">\n <template #default>\n <div class=\"flex flex-1\">\n <ScalarSidebar\n class=\"flex min-h-0 flex-1 flex-col max-md:pt-12\"\n :style=\"{ '--scalar-sidebar-indent': indent + 'px' }\">\n <!-- Top-level sidebar header -->\n <div\n v-if=\"!isOnDocumentPage\"\n class=\"flex flex-col gap-1.5 p-(--scalar-sidebar-padding)\">\n <div class=\"flex items-center gap-1\">\n <ScalarSidebarButton\n is=\"div\"\n class=\"text-sidebar-c-1 font-sidebar-active flex-1\"\n disabled>\n All Documents\n </ScalarSidebarButton>\n <ScalarIconButton\n :icon=\"ScalarIconGearSix\"\n label=\"Workspace settings\"\n size=\"sm\"\n @click=\"handleOpenSettings\" />\n <ScalarIconButton\n :icon=\"ScalarIconFunnel\"\n label=\"Filter documents\"\n size=\"sm\"\n @click=\"handleFilterOrSearch\" />\n <ScalarIconButton\n class=\"rounded-full border\"\n :icon=\"ScalarIconPlus\"\n label=\"Add document\"\n size=\"sm\"\n @click=\"handleCreate\" />\n </div>\n <ScalarSidebarSearchInput\n v-if=\"isFilterVisible\"\n v-model=\"filterQuery\"\n autofocus />\n </div>\n\n <!-- Document list (top-level) -->\n <div class=\"custom-scroll flex flex-1 flex-col\">\n <!--\n Empty state: no documents in the workspace yet. Matches the\n minimal `empty folder` appearance.\n -->\n <div\n v-if=\"isEmpty && !isOnDocumentPage\"\n class=\"text-c-3 flex flex-1 flex-col items-center justify-center gap-2 p-6 text-center select-none\">\n <ScalarIconFolderDashed\n class=\"size-10\"\n weight=\"light\" />\n <p class=\"text-sm font-medium\">No APIs yet</p>\n </div>\n <ScalarSidebarItems v-else>\n <!-- Show pinned documents after we add support for it -->\n <!-- <ScalarSidebarSection v-if=\"pinned.length\">\n Pinned\n <template #items>\n <ScalarSidebarNestedItems\n v-for=\"item in pinned\"\n :key=\"item.key\"\n :active=\"isDocActive(item)\"\n controlled\n :open=\"Boolean(openKeys[item.key])\"\n @back=\"openKeys[item.key] = false\"\n @click=\"handleDocumentClick(item)\">\n <span class=\"truncate\">{{ item.title }}</span>\n <template #back-label>{{ item.title }}</template>\n <template #items>\n <SidebarItem\n v-for=\"child in filterItems(item.navigation?.children ?? [])\"\n :key=\"child.id\"\n :isDraggable=\"false\"\n :isDroppable=\"isDroppable\"\n :isExpanded=\"isExpanded\"\n :isSelected=\"isSelected\"\n :item=\"child\"\n layout=\"client\"\n @selectItem=\"handleSelectItem\"\n @toggleGroup=\"handleToggleGroup\" />\n </template>\n </ScalarSidebarNestedItems>\n </template>\n </ScalarSidebarSection> -->\n\n <ScalarSidebarSection>\n All documents\n <template #items>\n <!--\n Skeleton rows shown while the caller is still fetching\n the registry document list. We only render skeletons in\n the top-level view (when no document is drilled-in) so\n the collection view is never masked by placeholders.\n -->\n <template v-if=\"isLoadingRegistry && !isOnDocumentPage\">\n <li\n v-for=\"n in 4\"\n :key=\"`registry-skeleton-${n}`\"\n aria-hidden=\"true\"\n class=\"sidebar-skeleton-row px-(--scalar-sidebar-padding) py-1\">\n <span class=\"bg-b-3 block h-6 rounded-md\" />\n </li>\n </template>\n <ScalarSidebarNestedItems\n v-for=\"item in filteredRest\"\n :key=\"item.key\"\n :active=\"isDocActive(item)\"\n controlled\n :open=\"isDocActive(item)\"\n @back=\"handleBack\"\n @click=\"handleDocumentClick(item)\">\n <span>{{ item.title }}</span>\n <template\n v-if=\"loadingKeys[item.key]\"\n #aside>\n <span class=\"text-c-3 text-xs\">Loading…</span>\n </template>\n <!-- Document back row -->\n <template #back>\n <div class=\"flex items-center gap-1\">\n <ScalarSidebarButton\n is=\"button\"\n class=\"text-sidebar-c-1 font-sidebar-active hover:text-sidebar-c-1 flex-1\"\n @click=\"handleBack\">\n <template #icon>\n <ScalarIconCaretLeft\n class=\"text-sidebar-c-2 -m-px size-4\" />\n </template>\n Back\n </ScalarSidebarButton>\n <ScalarIconButton\n :icon=\"ScalarIconGearSix\"\n label=\"Collection settings\"\n size=\"sm\"\n @click=\"handleOpenSettings\" />\n <ScalarIconButton\n :icon=\"ScalarIconMagnifyingGlass\"\n label=\"Search collection\"\n size=\"sm\"\n @click=\"handleFilterOrSearch\" />\n <ScalarIconButton\n class=\"rounded-full border\"\n :icon=\"ScalarIconPlus\"\n label=\"Add operation\"\n size=\"sm\"\n @click=\"handleCreateOperation(item)\" />\n </div>\n </template>\n <!-- Document items (operations, tags, examples) -->\n <template #items>\n <template v-if=\"item.navigation?.children?.length\">\n <SidebarItem\n v-for=\"child in filterItems(\n 'client',\n item.navigation.children,\n )\"\n :key=\"child.id\"\n :isDroppable=\"isDroppable\"\n :isExpanded=\"isExpanded\"\n :isSelected=\"isSelected\"\n :item=\"child\"\n layout=\"client\"\n @onDragEnd=\"handleDragEnd\"\n @selectItem=\"handleSelectItem\"\n @toggleGroup=\"handleToggleGroup\">\n <!--\n Per-item \"more\" dropdown for tags, operations and\n examples (add / edit / delete…). The dropdown is\n rendered once at the sidebar root and anchors\n itself to whichever icon button opened it.\n Operation settings live on the operation header\n (next to the environment selector), not here.\n -->\n <template #decorator=\"{ item: entry }\">\n <ScalarIconButton\n aria-expanded=\"false\"\n aria-haspopup=\"menu\"\n class=\"bg-b-2\"\n :icon=\"ScalarIconDotsThree\"\n label=\"More options\"\n size=\"sm\"\n variant=\"ghost\"\n weight=\"bold\"\n @click.stop=\"\n (e: MouseEvent) => openMenu(e, entry)\n \"\n @keydown.down.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \"\n @keydown.enter.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \"\n @keydown.space.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \"\n @keydown.up.stop=\"\n (e: KeyboardEvent) => openMenu(e, entry)\n \" />\n </template>\n <!--\n Empty tag / folder slot. Matches the \"Add operation\"\n affordance from the old sidebar so users can create\n an operation directly inside the hovered tag.\n -->\n <template #empty=\"{ item: emptyItem }\">\n <ScalarSidebarItem\n is=\"button\"\n @click=\"handleAddEmptyFolder(emptyItem)\">\n <template #icon>\n <ScalarIconPlus />\n </template>\n <template #default>Add operation</template>\n </ScalarSidebarItem>\n </template>\n </SidebarItem>\n </template>\n <li\n v-else\n class=\"text-c-3 px-3 py-1 text-xs\">\n Empty document\n </li>\n </template>\n </ScalarSidebarNestedItems>\n </template>\n </ScalarSidebarSection>\n </ScalarSidebarItems>\n </div>\n\n <slot name=\"footer\" />\n </ScalarSidebar>\n </div>\n <DocumentSearchModal\n :document=\"activeDocument\"\n :modalState=\"searchModal\"\n @select=\"handleSearchSelect\" />\n <!--\n Contextual dropdown menu for tags, operations and examples. Rendered\n once for the whole sidebar and re-anchored to the triggering icon via\n `menuTarget.el`, so we do not create a dropdown per item.\n -->\n <SidebarItemMenu\n v-if=\"app.store.value && menuTarget?.showMenu\"\n :eventBus=\"app.eventBus\"\n :item=\"menuTarget.item\"\n :sidebarState=\"sidebarState\"\n :target=\"menuTarget.el\"\n :workspaceStore=\"app.store.value\"\n @closeMenu=\"closeMenu\"\n @showDeleteModal=\"deleteModalState.show()\" />\n <!-- Delete confirmation modal, triggered from the dropdown menu above. -->\n <ScalarModal\n v-if=\"menuTarget\"\n size=\"xxs\"\n :state=\"deleteModalState\"\n :title=\"`Delete ${menuTarget.item.title}`\">\n <DeleteSidebarListElement\n :variableName=\"menuTarget.item.title\"\n :warningMessage=\"deleteMessage\"\n @close=\"deleteModalState.hide()\"\n @delete=\"handleDelete\" />\n </ScalarModal>\n </template>\n </Resize>\n</template>\n\n<style scoped>\n/*\n * Gentle pulse for the registry loading skeletons. Matches the existing\n * `LoadingSkeleton.vue` easing/duration used in `@scalar/api-reference` so\n * any skeleton in the app feels consistent.\n */\n.sidebar-skeleton-row > span {\n animation: sidebar-skeleton-pulse 1.5s infinite alternate;\n}\n\n@keyframes sidebar-skeleton-pulse {\n from {\n opacity: 1;\n }\n to {\n opacity: 0.33;\n }\n}\n</style>\n"],"mappings":""}
|