@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/style.css +31 -14
  3. package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
  4. package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
  5. package/dist/v2/constants.js +1 -1
  6. package/dist/v2/features/app/App.vue.d.ts.map +1 -1
  7. package/dist/v2/features/app/App.vue.js.map +1 -1
  8. package/dist/v2/features/app/App.vue.script.js +30 -28
  9. package/dist/v2/features/app/App.vue.script.js.map +1 -1
  10. package/dist/v2/features/app/components/AppHeader.vue.d.ts.map +1 -1
  11. package/dist/v2/features/app/components/AppHeader.vue.js.map +1 -1
  12. package/dist/v2/features/app/components/AppHeader.vue.script.js +3 -2
  13. package/dist/v2/features/app/components/AppHeader.vue.script.js.map +1 -1
  14. package/dist/v2/features/app/components/AppHeaderActions.vue.d.ts.map +1 -1
  15. package/dist/v2/features/app/components/AppHeaderActions.vue.js.map +1 -1
  16. package/dist/v2/features/app/components/AppHeaderActions.vue.script.js +12 -10
  17. package/dist/v2/features/app/components/AppHeaderActions.vue.script.js.map +1 -1
  18. package/dist/v2/features/app/components/AppSidebar.vue.d.ts +0 -2
  19. package/dist/v2/features/app/components/AppSidebar.vue.d.ts.map +1 -1
  20. package/dist/v2/features/app/components/AppSidebar.vue.js +1 -1
  21. package/dist/v2/features/app/components/AppSidebar.vue.js.map +1 -1
  22. package/dist/v2/features/app/components/AppSidebar.vue.script.js +3 -7
  23. package/dist/v2/features/app/components/AppSidebar.vue.script.js.map +1 -1
  24. package/dist/v2/features/app/components/DesktopTabs.vue.d.ts.map +1 -1
  25. package/dist/v2/features/app/components/DesktopTabs.vue.js.map +1 -1
  26. package/dist/v2/features/app/components/DesktopTabs.vue.script.js +2 -2
  27. package/dist/v2/features/app/components/DesktopTabs.vue.script.js.map +1 -1
  28. package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts.map +1 -1
  29. package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js +1 -1
  30. package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js.map +1 -1
  31. package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js +1 -1
  32. package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js.map +1 -1
  33. package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.d.ts.map +1 -1
  34. package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.js.map +1 -1
  35. package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.script.js +35 -17
  36. package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.script.js.map +1 -1
  37. package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.d.ts.map +1 -1
  38. package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.js.map +1 -1
  39. package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js +15 -13
  40. package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js.map +1 -1
  41. package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.d.ts.map +1 -1
  42. package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.js +1 -1
  43. package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.js.map +1 -1
  44. package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.script.js +51 -39
  45. package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.script.js.map +1 -1
  46. package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.d.ts.map +1 -1
  47. package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.js.map +1 -1
  48. package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js +5 -5
  49. package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js.map +1 -1
  50. package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.d.ts.map +1 -1
  51. package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.js.map +1 -1
  52. package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.script.js +44 -42
  53. package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.script.js.map +1 -1
  54. package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.d.ts.map +1 -1
  55. package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.js.map +1 -1
  56. package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.script.js +33 -17
  57. package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.script.js.map +1 -1
  58. package/dist/v2/features/command-palette/helpers/load-document-from-source.d.ts.map +1 -1
  59. package/dist/v2/features/command-palette/helpers/load-document-from-source.js +3 -2
  60. package/dist/v2/features/command-palette/helpers/load-document-from-source.js.map +1 -1
  61. package/dist/v2/helpers/is-url.d.ts.map +1 -1
  62. package/dist/v2/helpers/is-url.js +2 -1
  63. package/dist/v2/helpers/is-url.js.map +1 -1
  64. package/dist/vue-styles.css +6 -6
  65. package/package.json +8 -8
@@ -6,19 +6,25 @@ import { ScalarButton, ScalarDropdown, ScalarDropdownItem, ScalarIcon, ScalarLis
6
6
  //#region src/v2/features/command-palette/components/CommandPaletteExample.vue?vue&type=script&setup=true&lang.ts
7
7
  var _hoisted_1 = {
8
8
  key: 0,
9
- class: "flex flex-1 gap-1"
9
+ class: "text-red px-2 pb-1 text-xs",
10
+ "data-testid": "command-palette-example-error",
11
+ role: "alert"
10
12
  };
11
13
  var _hoisted_2 = {
12
14
  key: 0,
13
- class: "text-c-1 truncate"
15
+ class: "flex flex-1 gap-1"
14
16
  };
15
17
  var _hoisted_3 = {
18
+ key: 0,
19
+ class: "text-c-1 truncate"
20
+ };
21
+ var _hoisted_4 = {
16
22
  key: 1,
17
23
  class: "text-c-3"
18
24
  };
19
- var _hoisted_4 = { class: "flex items-center gap-2" };
20
- var _hoisted_5 = { class: "custom-scroll max-h-40" };
21
- var _hoisted_6 = { class: "truncate" };
25
+ var _hoisted_5 = { class: "flex items-center gap-2" };
26
+ var _hoisted_6 = { class: "custom-scroll max-h-40" };
27
+ var _hoisted_7 = { class: "truncate" };
22
28
  var CommandPaletteExample_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
23
29
  name: "CommandPaletteExample",
24
30
  props: {
@@ -78,16 +84,28 @@ var CommandPaletteExample_vue_vue_type_script_setup_true_lang_default = /* @__PU
78
84
  if (operation) selectedOperation.value = operation;
79
85
  };
80
86
  /**
81
- * Check if the form should be disabled.
82
- * Disabled when any required field is missing or empty.
87
+ * Validation message surfaced under the input.
88
+ *
89
+ * Resolves to `null` when the form is valid; otherwise to a human-readable
90
+ * reason the user can act on. Empty input or an unchanged name in edit mode
91
+ * are treated as the default state so the modal does not greet the user
92
+ * with a misleading error.
93
+ */
94
+ const errorMessage = computed(() => {
95
+ if (!exampleNameTrimmed.value || !selectedDocument.value || !selectedOperation.value) return null;
96
+ if (isEditMode.value && exampleNameTrimmed.value === __props.example?.name) return null;
97
+ if (selectedOperation.value.exampleNames.some((name) => name === exampleNameTrimmed.value && name !== __props.example?.name)) return `An example named "${exampleNameTrimmed.value}" already exists for ${selectedOperation.value.method.toUpperCase()} ${selectedOperation.value.path}. Try a different name.`;
98
+ return null;
99
+ });
100
+ /**
101
+ * Submit is blocked while required fields are missing, the name is unchanged
102
+ * in edit mode, or another example already uses the same name. The inline
103
+ * `errorMessage` explains the duplicate case so the user knows how to recover.
83
104
  */
84
105
  const isDisabled = computed(() => {
85
106
  if (!exampleNameTrimmed.value || !selectedDocument.value || !selectedOperation.value) return true;
86
- if (isEditMode.value && __props.example) {
87
- if (exampleNameTrimmed.value === __props.example.name) return true;
88
- }
89
- if (selectedOperation.value.exampleNames.some((name) => name === exampleNameTrimmed.value && name !== __props.example?.name)) return true;
90
- return false;
107
+ if (isEditMode.value && exampleNameTrimmed.value === __props.example?.name) return true;
108
+ return errorMessage.value !== null;
91
109
  });
92
110
  /**
93
111
  * Navigate to the example route which will create it automatically.
@@ -132,7 +150,7 @@ var CommandPaletteExample_vue_vue_type_script_setup_true_lang_default = /* @__PU
132
150
  disabled: isDisabled.value,
133
151
  onSubmit: handleSubmit
134
152
  }, {
135
- options: withCtx(() => [!isEditMode.value ? (openBlock(), createElementBlock("div", _hoisted_1, [createVNode(unref(ScalarListbox), {
153
+ options: withCtx(() => [!isEditMode.value ? (openBlock(), createElementBlock("div", _hoisted_2, [createVNode(unref(ScalarListbox), {
136
154
  modelValue: selectedDocument.value,
137
155
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => selectedDocument.value = $event),
138
156
  options: availableDocuments.value
@@ -153,13 +171,13 @@ var CommandPaletteExample_vue_vue_type_script_setup_true_lang_default = /* @__PU
153
171
  placement: "bottom",
154
172
  resize: ""
155
173
  }, {
156
- items: withCtx(() => [createElementVNode("div", _hoisted_5, [(openBlock(true), createElementBlock(Fragment, null, renderList(availableOperations.value, (operation) => {
174
+ items: withCtx(() => [createElementVNode("div", _hoisted_6, [(openBlock(true), createElementBlock(Fragment, null, renderList(availableOperations.value, (operation) => {
157
175
  return openBlock(), createBlock(unref(ScalarDropdownItem), {
158
176
  key: operation.id,
159
177
  class: "flex h-7 w-full items-center justify-between px-1 pr-[26px]",
160
178
  onClick: ($event) => handleSelect(operation)
161
179
  }, {
162
- default: withCtx(() => [createElementVNode("span", _hoisted_6, toDisplayString(operation.path), 1), createVNode(HttpMethod_default, { method: operation.method }, null, 8, ["method"])]),
180
+ default: withCtx(() => [createElementVNode("span", _hoisted_7, toDisplayString(operation.path), 1), createVNode(HttpMethod_default, { method: operation.method }, null, 8, ["method"])]),
163
181
  _: 2
164
182
  }, 1032, ["onClick"]);
165
183
  }), 128))])]),
@@ -168,7 +186,7 @@ var CommandPaletteExample_vue_vue_type_script_setup_true_lang_default = /* @__PU
168
186
  disabled: !availableOperations.value.length,
169
187
  variant: "outlined"
170
188
  }, {
171
- default: withCtx(() => [selectedOperation.value ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(selectedOperation.value.path), 1)) : (openBlock(), createElementBlock("span", _hoisted_3, " Select Operation ")), createElementVNode("div", _hoisted_4, [selectedOperation.value ? (openBlock(), createBlock(HttpMethod_default, {
189
+ default: withCtx(() => [selectedOperation.value ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(selectedOperation.value.path), 1)) : (openBlock(), createElementBlock("span", _hoisted_4, " Select Operation ")), createElementVNode("div", _hoisted_5, [selectedOperation.value ? (openBlock(), createBlock(HttpMethod_default, {
172
190
  key: 0,
173
191
  method: selectedOperation.value.method
174
192
  }, null, 8, ["method"])) : createCommentVNode("", true), createVNode(unref(ScalarIcon), {
@@ -195,7 +213,7 @@ var CommandPaletteExample_vue_vue_type_script_setup_true_lang_default = /* @__PU
195
213
  label: "Example Name",
196
214
  placeholder: "Example Name",
197
215
  onDelete: handleBack
198
- }, null, 8, ["modelValue"])]),
216
+ }, null, 8, ["modelValue"]), errorMessage.value ? (openBlock(), createElementBlock("p", _hoisted_1, toDisplayString(errorMessage.value), 1)) : createCommentVNode("", true)]),
199
217
  _: 1
200
218
  }, 8, ["disabled"]);
201
219
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteExample.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteExample.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Example Component\n *\n * Provides a form for creating a new example for an API operation.\n * Users can name the example, select a document (collection), and choose an operation.\n * Automatically navigates to the example route which creates the example.\n *\n * @example\n * <CommandPaletteExample\n * :workspaceStore=\"workspaceStore\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n */\nexport default {\n name: 'CommandPaletteExample',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarDropdown,\n ScalarDropdownItem,\n ScalarIcon,\n ScalarListbox,\n} from '@scalar/components'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type {\n TraversedEntry,\n TraversedExample,\n TraversedOperation,\n} from '@scalar/workspace-store/schemas/navigation'\nimport { computed, ref, watch } from 'vue'\n\nimport HttpMethodBadge from '@/v2/blocks/operation-code-sample/components/HttpMethod.vue'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\n\nconst { workspaceStore, eventBus, documentName, operationId, example } =\n defineProps<{\n /** The workspace store for accessing documents and operations */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** Document id to create the example for */\n documentName?: string\n /** Preselected path and method to create the example for */\n operationId?: string\n /** Existing example for edit mode */\n example?: TraversedExample\n }>()\n\nconst emit = defineEmits<{\n /** Emitted when the example is created successfully */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\n/** Operation option type for selectors */\ntype OperationOption = {\n id: string\n label: string\n path: string\n method: HttpMethod\n exampleNames: string[]\n}\n\nconst isEditMode = computed(() => example !== undefined)\n\nconst exampleName = ref(example?.name ?? '')\nconst exampleNameTrimmed = computed(() => exampleName.value.trim())\n\n/** All available documents (collections) in the workspace */\nconst availableDocuments = computed(() =>\n Object.entries(workspaceStore.workspace.documents).map(\n ([name, document]) => ({\n id: name,\n label: document.info.title || name,\n }),\n ),\n)\n\nconst selectedDocument = ref<{ id: string; label: string } | undefined>(\n documentName\n ? availableDocuments.value.find((document) => document.id === documentName)\n : (availableDocuments.value[0] ?? undefined),\n)\n\n/**\n * Recursively traverse navigation entries to find all operations.\n * Operations can be nested under tags or at the document level.\n */\nconst getAllOperations = (entries: TraversedEntry[]): TraversedOperation[] => {\n const operations: TraversedOperation[] = []\n\n for (const entry of entries) {\n if (entry.type === 'operation') {\n operations.push(entry)\n }\n\n /** Recursively traverse child entries if they exist */\n if ('children' in entry && entry.children) {\n operations.push(...getAllOperations(entry.children))\n }\n }\n\n return operations\n}\n\n/** All available operations for the selected document */\nconst availableOperations = computed(() => {\n if (!selectedDocument.value) {\n return []\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n if (!document || !document['x-scalar-navigation']) {\n return []\n }\n\n const navigation = document['x-scalar-navigation']\n const operations = getAllOperations(navigation.children ?? [])\n\n return operations.map((operation) => ({\n id: operation.id,\n label: `${operation.method.toUpperCase()} ${operation.path}`,\n path: operation.path,\n method: operation.method,\n exampleNames:\n operation.children\n ?.filter((child): child is TraversedExample => child.type === 'example')\n .map((child) => child.name) ?? [],\n }))\n})\n\nconst selectedOperation = ref<OperationOption | undefined>(\n operationId\n ? availableOperations.value.find(\n (operation) => operation.id === operationId,\n )\n : undefined,\n)\n\n/** Reset operation selection when document changes */\nwatch(\n selectedDocument,\n () => {\n selectedOperation.value = operationId\n ? availableOperations.value.find(\n (operation) => operation.id === operationId,\n )\n : (availableOperations.value[0] ?? undefined)\n },\n { immediate: true },\n)\n\n/** Handle operation selection from dropdown */\nconst handleSelect = (operation: OperationOption | undefined): void => {\n if (operation) {\n selectedOperation.value = operation\n }\n}\n\n/**\n * Check if the form should be disabled.\n * Disabled when any required field is missing or empty.\n */\nconst isDisabled = computed<boolean>(() => {\n if (\n !exampleNameTrimmed.value ||\n !selectedDocument.value ||\n !selectedOperation.value\n ) {\n return true\n }\n\n if (isEditMode.value && example) {\n if (exampleNameTrimmed.value === example.name) {\n return true\n }\n }\n\n if (\n selectedOperation.value.exampleNames.some(\n (name) => name === exampleNameTrimmed.value && name !== example?.name,\n )\n ) {\n return true\n }\n\n return false\n})\n\n/**\n * Navigate to the example route which will create it automatically.\n * The route handler will create the example with the provided details.\n */\nconst handleSubmit = (): void => {\n if (isDisabled.value || !selectedDocument.value || !selectedOperation.value) {\n return\n }\n\n if (isEditMode.value && example) {\n eventBus.emit('operation:rename:example', {\n documentName: selectedDocument.value.id,\n meta: {\n path: selectedOperation.value.path,\n method: selectedOperation.value.method,\n exampleKey: example.name,\n },\n payload: {\n name: exampleNameTrimmed.value,\n },\n })\n emit('close')\n return\n }\n\n eventBus.emit('operation:create:draft-example', {\n documentName: selectedDocument.value.id,\n meta: {\n path: selectedOperation.value.path,\n method: selectedOperation.value.method,\n },\n exampleName: exampleNameTrimmed.value,\n })\n\n emit('close')\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n if (isEditMode.value) {\n return\n }\n\n emit('back', event)\n}\n\n/** Handle cancel action in edit mode */\nconst handleCancel = (): void => {\n emit('close')\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n @submit=\"handleSubmit\">\n <CommandActionInput\n v-model=\"exampleName\"\n label=\"Example Name\"\n placeholder=\"Example Name\"\n @delete=\"handleBack\" />\n\n <!-- Selectors for document and operation -->\n <template #options>\n <div\n v-if=\"!isEditMode\"\n class=\"flex flex-1 gap-1\">\n <!-- Document (collection) selector -->\n <ScalarListbox\n v-model=\"selectedDocument\"\n :options=\"availableDocuments\">\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-[150px] min-w-[150px] justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <span :class=\"selectedDocument ? 'text-c-1 truncate' : 'text-c-3'\">\n {{\n selectedDocument ? selectedDocument.label : 'Select Document'\n }}\n </span>\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n </ScalarListbox>\n\n <!-- Operation selector (path + method) -->\n <ScalarDropdown\n placement=\"bottom\"\n resize>\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-full justify-between gap-1 p-2 text-xs\"\n :disabled=\"!availableOperations.length\"\n variant=\"outlined\">\n <span\n v-if=\"selectedOperation\"\n class=\"text-c-1 truncate\">\n {{ selectedOperation.path }}\n </span>\n <span\n v-else\n class=\"text-c-3\">\n Select Operation\n </span>\n <div class=\"flex items-center gap-2\">\n <HttpMethodBadge\n v-if=\"selectedOperation\"\n :method=\"selectedOperation.method\" />\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </div>\n </ScalarButton>\n\n <!-- Dropdown list of all operations -->\n <template #items>\n <div class=\"custom-scroll max-h-40\">\n <ScalarDropdownItem\n v-for=\"operation in availableOperations\"\n :key=\"operation.id\"\n class=\"flex h-7 w-full items-center justify-between px-1 pr-[26px]\"\n @click=\"handleSelect(operation)\">\n <span class=\"truncate\">{{ operation.path }}</span>\n <HttpMethodBadge :method=\"operation.method\" />\n </ScalarDropdownItem>\n </div>\n </template>\n </ScalarDropdown>\n </div>\n\n <ScalarButton\n v-else\n class=\"max-h-8 px-3 text-xs\"\n variant=\"outlined\"\n @click=\"handleCancel\">\n Cancel\n </ScalarButton>\n </template>\n\n <template #submit>{{ isEditMode ? 'Save' : 'Create Example' }}</template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CommandPaletteExample.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteExample.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Example Component\n *\n * Provides a form for creating a new example for an API operation.\n * Users can name the example, select a document (collection), and choose an operation.\n * Automatically navigates to the example route which creates the example.\n *\n * @example\n * <CommandPaletteExample\n * :workspaceStore=\"workspaceStore\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n */\nexport default {\n name: 'CommandPaletteExample',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarDropdown,\n ScalarDropdownItem,\n ScalarIcon,\n ScalarListbox,\n} from '@scalar/components'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type {\n TraversedEntry,\n TraversedExample,\n TraversedOperation,\n} from '@scalar/workspace-store/schemas/navigation'\nimport { computed, ref, watch, type ComputedRef } from 'vue'\n\nimport HttpMethodBadge from '@/v2/blocks/operation-code-sample/components/HttpMethod.vue'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\n\nconst { workspaceStore, eventBus, documentName, operationId, example } =\n defineProps<{\n /** The workspace store for accessing documents and operations */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** Document id to create the example for */\n documentName?: string\n /** Preselected path and method to create the example for */\n operationId?: string\n /** Existing example for edit mode */\n example?: TraversedExample\n }>()\n\nconst emit = defineEmits<{\n /** Emitted when the example is created successfully */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\n/** Operation option type for selectors */\ntype OperationOption = {\n id: string\n label: string\n path: string\n method: HttpMethod\n exampleNames: string[]\n}\n\nconst isEditMode = computed(() => example !== undefined)\n\nconst exampleName = ref(example?.name ?? '')\nconst exampleNameTrimmed = computed(() => exampleName.value.trim())\n\n/** All available documents (collections) in the workspace */\nconst availableDocuments = computed(() =>\n Object.entries(workspaceStore.workspace.documents).map(\n ([name, document]) => ({\n id: name,\n label: document.info.title || name,\n }),\n ),\n)\n\nconst selectedDocument = ref<{ id: string; label: string } | undefined>(\n documentName\n ? availableDocuments.value.find((document) => document.id === documentName)\n : (availableDocuments.value[0] ?? undefined),\n)\n\n/**\n * Recursively traverse navigation entries to find all operations.\n * Operations can be nested under tags or at the document level.\n */\nconst getAllOperations = (entries: TraversedEntry[]): TraversedOperation[] => {\n const operations: TraversedOperation[] = []\n\n for (const entry of entries) {\n if (entry.type === 'operation') {\n operations.push(entry)\n }\n\n /** Recursively traverse child entries if they exist */\n if ('children' in entry && entry.children) {\n operations.push(...getAllOperations(entry.children))\n }\n }\n\n return operations\n}\n\n/** All available operations for the selected document */\nconst availableOperations = computed(() => {\n if (!selectedDocument.value) {\n return []\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n if (!document || !document['x-scalar-navigation']) {\n return []\n }\n\n const navigation = document['x-scalar-navigation']\n const operations = getAllOperations(navigation.children ?? [])\n\n return operations.map((operation) => ({\n id: operation.id,\n label: `${operation.method.toUpperCase()} ${operation.path}`,\n path: operation.path,\n method: operation.method,\n exampleNames:\n operation.children\n ?.filter((child): child is TraversedExample => child.type === 'example')\n .map((child) => child.name) ?? [],\n }))\n})\n\nconst selectedOperation = ref<OperationOption | undefined>(\n operationId\n ? availableOperations.value.find(\n (operation) => operation.id === operationId,\n )\n : undefined,\n)\n\n/** Reset operation selection when document changes */\nwatch(\n selectedDocument,\n () => {\n selectedOperation.value = operationId\n ? availableOperations.value.find(\n (operation) => operation.id === operationId,\n )\n : (availableOperations.value[0] ?? undefined)\n },\n { immediate: true },\n)\n\n/** Handle operation selection from dropdown */\nconst handleSelect = (operation: OperationOption | undefined): void => {\n if (operation) {\n selectedOperation.value = operation\n }\n}\n\n/**\n * Validation message surfaced under the input.\n *\n * Resolves to `null` when the form is valid; otherwise to a human-readable\n * reason the user can act on. Empty input or an unchanged name in edit mode\n * are treated as the default state so the modal does not greet the user\n * with a misleading error.\n */\nconst errorMessage: ComputedRef<string | null> = computed(() => {\n if (\n !exampleNameTrimmed.value ||\n !selectedDocument.value ||\n !selectedOperation.value\n ) {\n return null\n }\n\n if (isEditMode.value && exampleNameTrimmed.value === example?.name) {\n return null\n }\n\n const nameConflict = selectedOperation.value.exampleNames.some(\n (name) => name === exampleNameTrimmed.value && name !== example?.name,\n )\n\n if (nameConflict) {\n return `An example named \"${exampleNameTrimmed.value}\" already exists for ${selectedOperation.value.method.toUpperCase()} ${selectedOperation.value.path}. Try a different name.`\n }\n\n return null\n})\n\n/**\n * Submit is blocked while required fields are missing, the name is unchanged\n * in edit mode, or another example already uses the same name. The inline\n * `errorMessage` explains the duplicate case so the user knows how to recover.\n */\nconst isDisabled = computed<boolean>(() => {\n if (\n !exampleNameTrimmed.value ||\n !selectedDocument.value ||\n !selectedOperation.value\n ) {\n return true\n }\n\n if (isEditMode.value && exampleNameTrimmed.value === example?.name) {\n return true\n }\n\n return errorMessage.value !== null\n})\n\n/**\n * Navigate to the example route which will create it automatically.\n * The route handler will create the example with the provided details.\n */\nconst handleSubmit = (): void => {\n if (isDisabled.value || !selectedDocument.value || !selectedOperation.value) {\n return\n }\n\n if (isEditMode.value && example) {\n eventBus.emit('operation:rename:example', {\n documentName: selectedDocument.value.id,\n meta: {\n path: selectedOperation.value.path,\n method: selectedOperation.value.method,\n exampleKey: example.name,\n },\n payload: {\n name: exampleNameTrimmed.value,\n },\n })\n emit('close')\n return\n }\n\n eventBus.emit('operation:create:draft-example', {\n documentName: selectedDocument.value.id,\n meta: {\n path: selectedOperation.value.path,\n method: selectedOperation.value.method,\n },\n exampleName: exampleNameTrimmed.value,\n })\n\n emit('close')\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n if (isEditMode.value) {\n return\n }\n\n emit('back', event)\n}\n\n/** Handle cancel action in edit mode */\nconst handleCancel = (): void => {\n emit('close')\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n @submit=\"handleSubmit\">\n <CommandActionInput\n v-model=\"exampleName\"\n label=\"Example Name\"\n placeholder=\"Example Name\"\n @delete=\"handleBack\" />\n\n <p\n v-if=\"errorMessage\"\n class=\"text-red px-2 pb-1 text-xs\"\n data-testid=\"command-palette-example-error\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <!-- Selectors for document and operation -->\n <template #options>\n <div\n v-if=\"!isEditMode\"\n class=\"flex flex-1 gap-1\">\n <!-- Document (collection) selector -->\n <ScalarListbox\n v-model=\"selectedDocument\"\n :options=\"availableDocuments\">\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-[150px] min-w-[150px] justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <span :class=\"selectedDocument ? 'text-c-1 truncate' : 'text-c-3'\">\n {{\n selectedDocument ? selectedDocument.label : 'Select Document'\n }}\n </span>\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n </ScalarListbox>\n\n <!-- Operation selector (path + method) -->\n <ScalarDropdown\n placement=\"bottom\"\n resize>\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-full justify-between gap-1 p-2 text-xs\"\n :disabled=\"!availableOperations.length\"\n variant=\"outlined\">\n <span\n v-if=\"selectedOperation\"\n class=\"text-c-1 truncate\">\n {{ selectedOperation.path }}\n </span>\n <span\n v-else\n class=\"text-c-3\">\n Select Operation\n </span>\n <div class=\"flex items-center gap-2\">\n <HttpMethodBadge\n v-if=\"selectedOperation\"\n :method=\"selectedOperation.method\" />\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </div>\n </ScalarButton>\n\n <!-- Dropdown list of all operations -->\n <template #items>\n <div class=\"custom-scroll max-h-40\">\n <ScalarDropdownItem\n v-for=\"operation in availableOperations\"\n :key=\"operation.id\"\n class=\"flex h-7 w-full items-center justify-between px-1 pr-[26px]\"\n @click=\"handleSelect(operation)\">\n <span class=\"truncate\">{{ operation.path }}</span>\n <HttpMethodBadge :method=\"operation.method\" />\n </ScalarDropdownItem>\n </div>\n </template>\n </ScalarDropdown>\n </div>\n\n <ScalarButton\n v-else\n class=\"max-h-8 px-3 text-xs\"\n variant=\"outlined\"\n @click=\"handleCancel\">\n Cancel\n </ScalarButton>\n </template>\n\n <template #submit>{{ isEditMode ? 'Save' : 'Create Example' }}</template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteImport.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"names":[],"mappings":"AAyZA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAG7D,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAkBvE;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY;IAEhB,+CAA+C;oBAC/B,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,oCAAoC;iBACvB,YAAY;;;;;IALzB,+CAA+C;oBAC/B,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,oCAAoC;iBACvB,YAAY;;;;;IAczB;;;;;;OAMG;sBACe;QAChB,8DAA8D;QAC9D,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAChE,GAAG,IAAI;EAugBN,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"CommandPaletteImport.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"names":[],"mappings":"AA6ZA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAG7D,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAiBvE;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY;IAEhB,+CAA+C;oBAC/B,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,oCAAoC;iBACvB,YAAY;;;;;IALzB,+CAA+C;oBAC/B,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,oCAAoC;iBACvB,YAAY;;;;;IAczB;;;;;;OAMG;sBACe;QAChB,8DAA8D;QAC9D,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAChE,GAAG,IAAI;EA4gBN,CAAC;AACL,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":"CommandPaletteImport.vue.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Import Component\n *\n * Provides a form for importing OpenAPI descriptions from URL, file, or pasted JSON/YAML.\n * Postman collection JSON and Postman files open {@link CommandPaletteImportPostman}.\n * cURL commands redirect to {@link CommandPaletteImportCurl}.\n *\n * Supports watch mode for URL imports to automatically update when content changes.\n */\nexport default {\n name: 'CommandPaletteImport',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarCodeBlock,\n ScalarIcon,\n ScalarTooltip,\n useLoadingState,\n} from '@scalar/components'\nimport { isLocalUrl } from '@scalar/helpers/url/is-local-url'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { isPostmanCollection } from '@scalar/postman-to-openapi'\nimport { useToasts } from '@scalar/use-toasts'\nimport {\n createWorkspaceStore,\n type WorkspaceStore,\n} from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { computed, ref, watch } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport { useFileDialog } from '@/hooks/use-file-dialog'\nimport { getOpenApiDocumentDetails } from '@/v2/features/command-palette/helpers/get-openapi-document-details'\nimport { importDocumentToWorkspace } from '@/v2/features/command-palette/helpers/import-document-to-workspace'\nimport {\n loadDocumentFromSource,\n type ImportEventData,\n} from '@/v2/features/command-palette/helpers/load-document-from-source'\nimport { isUrl } from '@/v2/helpers/is-url'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\nimport WatchModeToggle from './WatchModeToggle.vue'\n\nconst { workspaceStore, eventBus, fileLoader } = defineProps<{\n /** The workspace store for adding documents */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** Loader plugin for file import */\n fileLoader?: LoaderPlugin\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the import is complete or cancelled */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\ndefineSlots<{\n /**\n * Slot for custom file upload component that can trigger import.\n *\n * The provided `import` function automatically detects Postman collections\n * and routes them to the Postman import modal, matching the behavior of the\n * default file picker.\n */\n fileUpload(props: {\n /** Function to trigger import with source content and type */\n import: (source: string, type: 'file' | 'raw') => Promise<void>\n }): void\n}>()\n\nconst { toast } = useToasts()\n\nconst router = useRouter()\nconst loader = useLoadingState()\n\nconst inputContent = ref('')\nconst watchMode = ref(false)\n\n/** Check if the input content is a URL */\nconst isUrlInput = computed<boolean>(() => isUrl(inputContent.value))\nconst isLocalUrlInput = computed<boolean>(\n () => isUrlInput.value && isLocalUrl(inputContent.value),\n)\n\nconst documentDetails = computed(() =>\n getOpenApiDocumentDetails(inputContent.value),\n)\n\n/** Get the document type for syntax highlighting */\nconst documentType = computed<string>(() =>\n documentDetails.value ? documentDetails.value.type : 'json',\n)\n\n/** Check if the form should be disabled (when input is empty) */\nconst isDisabled = computed<boolean>(() => {\n return !inputContent.value.trim()\n})\n\n/**\n * Toggle watchMode based on whether the input is a local URL.\n * Only enables watch mode for local URLs, not for files or pasted content.\n */\nwatch(isLocalUrlInput, (value: boolean) => {\n watchMode.value = value\n})\n\n/**\n * Handles errors during the import process.\n * Shows an error toast, invalidates the loader to show an error state,\n * and closes the command palette modal.\n *\n * @param errorMessage - The error message to display and log\n */\nconst handleImportError = async (errorMessage: string) => {\n // Log the error\n console.error(errorMessage)\n toast(errorMessage, 'error')\n\n // Invalidate the loader to show the error state\n await loader.invalidate()\n\n // Close the command palette\n emit('close')\n}\n\n/**\n * Directly imports a document into the workspace without showing the modal.\n * This is used when there is only one workspace and it is empty.\n */\nconst handleImport = async (\n newSource: string,\n type?: ImportEventData['type'],\n): Promise<void> => {\n loader.start()\n\n const TEMP_DOCUMENT_NAME = 'drafts'\n\n // First load the document into a draft store\n // This is to get the title of the document so we can generate a unique slug for store\n const draftStore = createWorkspaceStore({\n fileLoader,\n meta: {\n /** Ensure we use the active proxy to fetch documents */\n 'x-scalar-active-proxy':\n workspaceStore.workspace['x-scalar-active-proxy'],\n },\n })\n\n const eventType = (() => {\n if (type) {\n return type\n }\n\n if (isUrlInput.value) {\n return 'url'\n }\n\n return 'raw'\n })()\n\n const isSuccessfullyLoaded = await loadDocumentFromSource(\n draftStore,\n { source: newSource, type: eventType },\n TEMP_DOCUMENT_NAME,\n watchMode.value,\n )\n\n if (!isSuccessfullyLoaded) {\n return handleImportError('Failed to import document')\n }\n\n const importResult = await importDocumentToWorkspace({\n workspaceStore,\n workspaceState: draftStore.exportWorkspace(),\n name: TEMP_DOCUMENT_NAME,\n })\n\n if (!importResult.ok) {\n return handleImportError(importResult.error)\n }\n\n // Validate the loader to show the success state\n await loader.validate()\n\n // Navigate to the document overview page\n navigateToDocument(importResult.slug)\n\n // Close the command palette\n emit('close')\n}\n\n/** Navigate to the document overview page after successful import */\nconst navigateToDocument = (documentName: string): void => {\n router.push({\n name: 'document.overview',\n params: { documentSlug: documentName },\n })\n}\n\n/**\n * Import a file, routing Postman collections to the Postman import modal and\n * everything else through the OpenAPI import flow.\n *\n * Shared between the default file picker and the `fileUpload` slot so custom\n * path-based importers get the same Postman detection behavior.\n *\n * When `type` is `'file'` the `source` is a file path resolved through the\n * configured `fileLoader`; when `type` is `'raw'` the `source` is treated as\n * the file's text content directly.\n */\nconst handleFileImport: (\n source: string,\n type?: 'file' | 'raw',\n) => Promise<void> = async (source, type = 'raw') => {\n // Resolve the raw text content so we can sniff for a Postman collection.\n // For raw pastes / uploads the source already is the text. For path-based\n // imports we delegate to the file loader plugin, if one is configured.\n const rawContent = await (async (): Promise<string> => {\n if (type === 'raw') {\n return source\n }\n\n const result = await fileLoader?.exec(source)\n return result?.ok ? result.raw : ''\n })()\n\n if (isPostmanCollection(rawContent)) {\n eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: rawContent,\n },\n })\n await loader.clear()\n return\n }\n\n await handleImport(source, type)\n}\n\n/**\n * Handle file selection and import from file dialog.\n * Reads the file as text and imports it as OpenAPI or Postman collection.\n * Shows loading state during the import process.\n */\nconst { open: openSpecFileDialog } = useFileDialog({\n onChange: (files) => {\n const [file] = files ?? []\n\n if (!file) {\n return\n }\n\n loader.start()\n\n const onLoad = async (event: ProgressEvent<FileReader>): Promise<void> => {\n const text = event.target?.result as string\n await handleFileImport(text, 'raw')\n }\n\n const reader = new FileReader()\n reader.onload = onLoad\n reader.readAsText(file)\n },\n multiple: false,\n accept: '.json,.yaml,.yml',\n})\n\n/**\n * Handle input changes.\n * Detects cURL commands and redirects to the cURL import command.\n */\nconst handleInput = (value: string): void => {\n const trimmed = value.trim()\n\n if (trimmed.toLowerCase().startsWith('curl')) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-curl-command',\n payload: {\n inputValue: value,\n },\n })\n }\n\n if (isPostmanCollection(trimmed)) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: value,\n },\n })\n }\n\n inputContent.value = value\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n emit('back', event)\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n :loader\n @submit=\"handleImport(inputContent)\">\n <!-- URL or cURL input mode -->\n <template v-if=\"!documentDetails || isUrlInput\">\n <CommandActionInput\n :modelValue=\"inputContent\"\n placeholder=\"OpenAPI/Swagger/Postman URL or cURL\"\n @delete=\"handleBack\"\n @update:modelValue=\"handleInput\" />\n </template>\n\n <!-- Preview mode for pasted content -->\n <template v-else>\n <!-- Preview header with clear button -->\n <div class=\"flex justify-between\">\n <div class=\"text-c-2 min-h-8 w-full py-2 pl-12 text-center text-xs\">\n Preview\n </div>\n <ScalarButton\n class=\"hover:bg-b-2 relative ml-auto max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"ghost\"\n @click=\"inputContent = ''\">\n Clear\n </ScalarButton>\n </div>\n\n <!-- Code preview with syntax highlighting -->\n <ScalarCodeBlock\n v-if=\"documentDetails && !isUrlInput\"\n class=\"bg-b-2 mt-1 max-h-[40dvh] rounded border px-2 py-1 text-sm\"\n :content=\"inputContent\"\n :copy=\"false\"\n :lang=\"documentType\" />\n </template>\n\n <!-- Actions: File upload and watch mode toggle -->\n <template #options>\n <div class=\"flex w-full flex-row items-center justify-between gap-3\">\n <!-- Custom file upload slot or default button -->\n <slot\n :import=\"handleFileImport\"\n name=\"fileUpload\">\n <!-- Default file upload button -->\n <ScalarButton\n class=\"hover:bg-b-2 relative max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"outlined\"\n @click=\"openSpecFileDialog\">\n JSON, or YAML File\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"Upload\"\n size=\"md\" />\n </ScalarButton>\n </slot>\n\n <!-- Watch mode toggle (only enabled for URL imports) -->\n <ScalarTooltip\n :content=\"\n isUrlInput\n ? 'Watch mode automatically updates the API client when the OpenAPI URL content changes, ensuring your client remains up-to-date.'\n : 'Watch mode is only available for URL imports. When enabled it automatically updates the API client when the OpenAPI URL content changes.'\n \"\n placement=\"bottom\">\n <WatchModeToggle\n v-model=\"watchMode\"\n :disabled=\"!isUrlInput\" />\n </ScalarTooltip>\n </div>\n </template>\n\n <!-- Dynamic submit button text based on import type -->\n <template #submit>\n Import\n <template v-if=\"isUrlInput\">from URL</template>\n <template v-else-if=\"documentDetails && documentType\">\n <template v-if=\"documentDetails.title\">\n \"{{ documentDetails.title }}\"\n </template>\n <template v-else>\n {{ documentDetails.version }}\n </template>\n </template>\n <template v-else>Collection</template>\n </template>\n </CommandActionForm>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"CommandPaletteImport.vue.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Import Component\n *\n * Provides a form for importing OpenAPI descriptions from URL, file, or pasted JSON/YAML.\n * Postman collection JSON and Postman files open {@link CommandPaletteImportPostman}.\n * cURL commands redirect to {@link CommandPaletteImportCurl}.\n *\n * Supports watch mode for URL imports to automatically update when content changes.\n */\nexport default {\n name: 'CommandPaletteImport',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarCodeBlock,\n ScalarIcon,\n ScalarTooltip,\n useLoadingState,\n} from '@scalar/components'\nimport { isLocalUrl } from '@scalar/helpers/url/is-local-url'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { isPostmanCollection } from '@scalar/postman-to-openapi'\nimport { useToasts } from '@scalar/use-toasts'\nimport {\n createWorkspaceStore,\n type WorkspaceStore,\n} from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { computed, ref, watch } from 'vue'\n\nimport { useFileDialog } from '@/hooks/use-file-dialog'\nimport { getOpenApiDocumentDetails } from '@/v2/features/command-palette/helpers/get-openapi-document-details'\nimport { importDocumentToWorkspace } from '@/v2/features/command-palette/helpers/import-document-to-workspace'\nimport {\n loadDocumentFromSource,\n type ImportEventData,\n} from '@/v2/features/command-palette/helpers/load-document-from-source'\nimport { isUrl } from '@/v2/helpers/is-url'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\nimport WatchModeToggle from './WatchModeToggle.vue'\n\nconst { workspaceStore, eventBus, fileLoader } = defineProps<{\n /** The workspace store for adding documents */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** Loader plugin for file import */\n fileLoader?: LoaderPlugin\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the import is complete or cancelled */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\ndefineSlots<{\n /**\n * Slot for custom file upload component that can trigger import.\n *\n * The provided `import` function automatically detects Postman collections\n * and routes them to the Postman import modal, matching the behavior of the\n * default file picker.\n */\n fileUpload(props: {\n /** Function to trigger import with source content and type */\n import: (source: string, type: 'file' | 'raw') => Promise<void>\n }): void\n}>()\n\nconst { toast } = useToasts()\n\nconst loader = useLoadingState()\n\nconst inputContent = ref('')\nconst watchMode = ref(false)\n\n/** Trim paste noise for URL checks without changing raw JSON/YAML content. */\nconst normalizedInputContent = computed<string>(() => inputContent.value.trim())\n\n/** Check if the input content is a URL */\nconst isUrlInput = computed<boolean>(() => isUrl(normalizedInputContent.value))\nconst isLocalUrlInput = computed<boolean>(\n () => isUrlInput.value && isLocalUrl(normalizedInputContent.value),\n)\n\nconst documentDetails = computed(() =>\n getOpenApiDocumentDetails(inputContent.value),\n)\n\n/** Get the document type for syntax highlighting */\nconst documentType = computed<string>(() =>\n documentDetails.value ? documentDetails.value.type : 'json',\n)\n\n/** Check if the form should be disabled (when input is empty) */\nconst isDisabled = computed<boolean>(() => {\n return !inputContent.value.trim()\n})\n\n/**\n * Toggle watchMode based on whether the input is a local URL.\n * Only enables watch mode for local URLs, not for files or pasted content.\n */\nwatch(isLocalUrlInput, (value: boolean) => {\n watchMode.value = value\n})\n\n/**\n * Handles errors during the import process.\n * Shows an error toast, invalidates the loader to show an error state,\n * and closes the command palette modal.\n *\n * @param errorMessage - The error message to display and log\n */\nconst handleImportError = async (errorMessage: string) => {\n // Log the error\n console.error(errorMessage)\n toast(errorMessage, 'error')\n\n // Invalidate the loader to show the error state\n await loader.invalidate()\n\n // Close the command palette\n emit('close')\n}\n\n/**\n * Directly imports a document into the workspace without showing the modal.\n * This is used when there is only one workspace and it is empty.\n */\nconst handleImport = async (\n newSource: string,\n type?: ImportEventData['type'],\n): Promise<void> => {\n loader.start()\n\n const TEMP_DOCUMENT_NAME = 'drafts'\n\n // First load the document into a draft store\n // This is to get the title of the document so we can generate a unique slug for store\n const draftStore = createWorkspaceStore({\n fileLoader,\n meta: {\n /** Ensure we use the active proxy to fetch documents */\n 'x-scalar-active-proxy':\n workspaceStore.workspace['x-scalar-active-proxy'],\n },\n })\n\n const eventType = (() => {\n if (type) {\n return type\n }\n\n if (isUrl(newSource.trim())) {\n return 'url'\n }\n\n return 'raw'\n })()\n\n const source = eventType === 'url' ? newSource.trim() : newSource\n\n const isSuccessfullyLoaded = await loadDocumentFromSource(\n draftStore,\n { source, type: eventType },\n TEMP_DOCUMENT_NAME,\n watchMode.value,\n )\n\n if (!isSuccessfullyLoaded) {\n return handleImportError('Failed to import document')\n }\n\n const importResult = await importDocumentToWorkspace({\n workspaceStore,\n workspaceState: draftStore.exportWorkspace(),\n name: TEMP_DOCUMENT_NAME,\n })\n\n if (!importResult.ok) {\n return handleImportError(importResult.error)\n }\n\n // Validate the loader to show the success state\n await loader.validate()\n\n // Navigate to the document overview page\n navigateToDocument(importResult.slug)\n\n // Close the command palette\n emit('close')\n}\n\n/** Navigate to the document overview page after successful import */\nconst navigateToDocument = (documentName: string): void => {\n eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug: documentName,\n })\n}\n\n/**\n * Import a file, routing Postman collections to the Postman import modal and\n * everything else through the OpenAPI import flow.\n *\n * Shared between the default file picker and the `fileUpload` slot so custom\n * path-based importers get the same Postman detection behavior.\n *\n * When `type` is `'file'` the `source` is a file path resolved through the\n * configured `fileLoader`; when `type` is `'raw'` the `source` is treated as\n * the file's text content directly.\n */\nconst handleFileImport: (\n source: string,\n type?: 'file' | 'raw',\n) => Promise<void> = async (source, type = 'raw') => {\n // Resolve the raw text content so we can sniff for a Postman collection.\n // For raw pastes / uploads the source already is the text. For path-based\n // imports we delegate to the file loader plugin, if one is configured.\n const rawContent = await (async (): Promise<string> => {\n if (type === 'raw') {\n return source\n }\n\n const result = await fileLoader?.exec(source)\n return result?.ok ? result.raw : ''\n })()\n\n if (isPostmanCollection(rawContent)) {\n eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: rawContent,\n },\n })\n await loader.clear()\n return\n }\n\n await handleImport(source, type)\n}\n\n/**\n * Handle file selection and import from file dialog.\n * Reads the file as text and imports it as OpenAPI or Postman collection.\n * Shows loading state during the import process.\n */\nconst { open: openSpecFileDialog } = useFileDialog({\n onChange: (files) => {\n const [file] = files ?? []\n\n if (!file) {\n return\n }\n\n loader.start()\n\n const onLoad = async (event: ProgressEvent<FileReader>): Promise<void> => {\n const text = event.target?.result as string\n await handleFileImport(text, 'raw')\n }\n\n const reader = new FileReader()\n reader.onload = onLoad\n reader.readAsText(file)\n },\n multiple: false,\n accept: '.json,.yaml,.yml',\n})\n\n/**\n * Handle input changes.\n * Detects cURL commands and redirects to the cURL import command.\n */\nconst handleInput = (value: string): void => {\n const trimmed = value.trim()\n\n if (trimmed.toLowerCase().startsWith('curl')) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-curl-command',\n payload: {\n inputValue: value,\n },\n })\n }\n\n if (isPostmanCollection(trimmed)) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: value,\n },\n })\n }\n\n inputContent.value = value\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n emit('back', event)\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n :loader\n @submit=\"handleImport(inputContent)\">\n <!-- URL or cURL input mode -->\n <template v-if=\"!documentDetails || isUrlInput\">\n <CommandActionInput\n :modelValue=\"inputContent\"\n placeholder=\"OpenAPI/Swagger/Postman URL or cURL\"\n @delete=\"handleBack\"\n @update:modelValue=\"handleInput\" />\n </template>\n\n <!-- Preview mode for pasted content -->\n <template v-else>\n <!-- Preview header with clear button -->\n <div class=\"flex justify-between\">\n <div class=\"text-c-2 min-h-8 w-full py-2 pl-12 text-center text-xs\">\n Preview\n </div>\n <ScalarButton\n class=\"hover:bg-b-2 relative ml-auto max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"ghost\"\n @click=\"inputContent = ''\">\n Clear\n </ScalarButton>\n </div>\n\n <!-- Code preview with syntax highlighting -->\n <ScalarCodeBlock\n v-if=\"documentDetails && !isUrlInput\"\n class=\"bg-b-2 mt-1 max-h-[40dvh] rounded border px-2 py-1 text-sm\"\n :content=\"inputContent\"\n :copy=\"false\"\n :lang=\"documentType\" />\n </template>\n\n <!-- Actions: File upload and watch mode toggle -->\n <template #options>\n <div class=\"flex w-full flex-row items-center justify-between gap-3\">\n <!-- Custom file upload slot or default button -->\n <slot\n :import=\"handleFileImport\"\n name=\"fileUpload\">\n <!-- Default file upload button -->\n <ScalarButton\n class=\"hover:bg-b-2 relative max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"outlined\"\n @click=\"openSpecFileDialog\">\n JSON, or YAML File\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"Upload\"\n size=\"md\" />\n </ScalarButton>\n </slot>\n\n <!-- Watch mode toggle (only enabled for URL imports) -->\n <ScalarTooltip\n :content=\"\n isUrlInput\n ? 'Watch mode automatically updates the API client when the OpenAPI URL content changes, ensuring your client remains up-to-date.'\n : 'Watch mode is only available for URL imports. When enabled it automatically updates the API client when the OpenAPI URL content changes.'\n \"\n placement=\"bottom\">\n <WatchModeToggle\n v-model=\"watchMode\"\n :disabled=\"!isUrlInput\" />\n </ScalarTooltip>\n </div>\n </template>\n\n <!-- Dynamic submit button text based on import type -->\n <template #submit>\n Import\n <template v-if=\"isUrlInput\">from URL</template>\n <template v-else-if=\"documentDetails && documentType\">\n <template v-if=\"documentDetails.title\">\n \"{{ documentDetails.title }}\"\n </template>\n <template v-else>\n {{ documentDetails.version }}\n </template>\n </template>\n <template v-else>Collection</template>\n </template>\n </CommandActionForm>\n</template>\n"],"mappings":""}
@@ -12,7 +12,6 @@ import { useToasts } from "@scalar/use-toasts";
12
12
  import { isLocalUrl } from "@scalar/helpers/url/is-local-url";
13
13
  import { isPostmanCollection } from "@scalar/postman-to-openapi";
14
14
  import { createWorkspaceStore } from "@scalar/workspace-store/client";
15
- import { useRouter } from "vue-router";
16
15
  //#region src/v2/features/command-palette/components/CommandPaletteImport.vue?vue&type=script&setup=true&lang.ts
17
16
  var _hoisted_1 = { class: "flex justify-between" };
18
17
  var _hoisted_2 = { class: "flex w-full flex-row items-center justify-between gap-3" };
@@ -27,13 +26,14 @@ var CommandPaletteImport_vue_vue_type_script_setup_true_lang_default = /* @__PUR
27
26
  setup(__props, { emit: __emit }) {
28
27
  const emit = __emit;
29
28
  const { toast } = useToasts();
30
- const router = useRouter();
31
29
  const loader = useLoadingState();
32
30
  const inputContent = ref("");
33
31
  const watchMode = ref(false);
32
+ /** Trim paste noise for URL checks without changing raw JSON/YAML content. */
33
+ const normalizedInputContent = computed(() => inputContent.value.trim());
34
34
  /** Check if the input content is a URL */
35
- const isUrlInput = computed(() => isUrl(inputContent.value));
36
- const isLocalUrlInput = computed(() => isUrlInput.value && isLocalUrl(inputContent.value));
35
+ const isUrlInput = computed(() => isUrl(normalizedInputContent.value));
36
+ const isLocalUrlInput = computed(() => isUrlInput.value && isLocalUrl(normalizedInputContent.value));
37
37
  const documentDetails = computed(() => getOpenApiDocumentDetails(inputContent.value));
38
38
  /** Get the document type for syntax highlighting */
39
39
  const documentType = computed(() => documentDetails.value ? documentDetails.value.type : "json");
@@ -72,13 +72,14 @@ var CommandPaletteImport_vue_vue_type_script_setup_true_lang_default = /* @__PUR
72
72
  fileLoader: __props.fileLoader,
73
73
  meta: { "x-scalar-active-proxy": __props.workspaceStore.workspace["x-scalar-active-proxy"] }
74
74
  });
75
+ const eventType = (() => {
76
+ if (type) return type;
77
+ if (isUrl(newSource.trim())) return "url";
78
+ return "raw";
79
+ })();
75
80
  if (!await loadDocumentFromSource(draftStore, {
76
- source: newSource,
77
- type: (() => {
78
- if (type) return type;
79
- if (isUrlInput.value) return "url";
80
- return "raw";
81
- })()
81
+ source: eventType === "url" ? newSource.trim() : newSource,
82
+ type: eventType
82
83
  }, TEMP_DOCUMENT_NAME, watchMode.value)) return handleImportError("Failed to import document");
83
84
  const importResult = await importDocumentToWorkspace({
84
85
  workspaceStore: __props.workspaceStore,
@@ -92,9 +93,10 @@ var CommandPaletteImport_vue_vue_type_script_setup_true_lang_default = /* @__PUR
92
93
  };
93
94
  /** Navigate to the document overview page after successful import */
94
95
  const navigateToDocument = (documentName) => {
95
- router.push({
96
- name: "document.overview",
97
- params: { documentSlug: documentName }
96
+ __props.eventBus.emit("ui:navigate", {
97
+ page: "document",
98
+ path: "overview",
99
+ documentSlug: documentName
98
100
  });
99
101
  };
100
102
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteImport.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Import Component\n *\n * Provides a form for importing OpenAPI descriptions from URL, file, or pasted JSON/YAML.\n * Postman collection JSON and Postman files open {@link CommandPaletteImportPostman}.\n * cURL commands redirect to {@link CommandPaletteImportCurl}.\n *\n * Supports watch mode for URL imports to automatically update when content changes.\n */\nexport default {\n name: 'CommandPaletteImport',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarCodeBlock,\n ScalarIcon,\n ScalarTooltip,\n useLoadingState,\n} from '@scalar/components'\nimport { isLocalUrl } from '@scalar/helpers/url/is-local-url'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { isPostmanCollection } from '@scalar/postman-to-openapi'\nimport { useToasts } from '@scalar/use-toasts'\nimport {\n createWorkspaceStore,\n type WorkspaceStore,\n} from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { computed, ref, watch } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport { useFileDialog } from '@/hooks/use-file-dialog'\nimport { getOpenApiDocumentDetails } from '@/v2/features/command-palette/helpers/get-openapi-document-details'\nimport { importDocumentToWorkspace } from '@/v2/features/command-palette/helpers/import-document-to-workspace'\nimport {\n loadDocumentFromSource,\n type ImportEventData,\n} from '@/v2/features/command-palette/helpers/load-document-from-source'\nimport { isUrl } from '@/v2/helpers/is-url'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\nimport WatchModeToggle from './WatchModeToggle.vue'\n\nconst { workspaceStore, eventBus, fileLoader } = defineProps<{\n /** The workspace store for adding documents */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** Loader plugin for file import */\n fileLoader?: LoaderPlugin\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the import is complete or cancelled */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\ndefineSlots<{\n /**\n * Slot for custom file upload component that can trigger import.\n *\n * The provided `import` function automatically detects Postman collections\n * and routes them to the Postman import modal, matching the behavior of the\n * default file picker.\n */\n fileUpload(props: {\n /** Function to trigger import with source content and type */\n import: (source: string, type: 'file' | 'raw') => Promise<void>\n }): void\n}>()\n\nconst { toast } = useToasts()\n\nconst router = useRouter()\nconst loader = useLoadingState()\n\nconst inputContent = ref('')\nconst watchMode = ref(false)\n\n/** Check if the input content is a URL */\nconst isUrlInput = computed<boolean>(() => isUrl(inputContent.value))\nconst isLocalUrlInput = computed<boolean>(\n () => isUrlInput.value && isLocalUrl(inputContent.value),\n)\n\nconst documentDetails = computed(() =>\n getOpenApiDocumentDetails(inputContent.value),\n)\n\n/** Get the document type for syntax highlighting */\nconst documentType = computed<string>(() =>\n documentDetails.value ? documentDetails.value.type : 'json',\n)\n\n/** Check if the form should be disabled (when input is empty) */\nconst isDisabled = computed<boolean>(() => {\n return !inputContent.value.trim()\n})\n\n/**\n * Toggle watchMode based on whether the input is a local URL.\n * Only enables watch mode for local URLs, not for files or pasted content.\n */\nwatch(isLocalUrlInput, (value: boolean) => {\n watchMode.value = value\n})\n\n/**\n * Handles errors during the import process.\n * Shows an error toast, invalidates the loader to show an error state,\n * and closes the command palette modal.\n *\n * @param errorMessage - The error message to display and log\n */\nconst handleImportError = async (errorMessage: string) => {\n // Log the error\n console.error(errorMessage)\n toast(errorMessage, 'error')\n\n // Invalidate the loader to show the error state\n await loader.invalidate()\n\n // Close the command palette\n emit('close')\n}\n\n/**\n * Directly imports a document into the workspace without showing the modal.\n * This is used when there is only one workspace and it is empty.\n */\nconst handleImport = async (\n newSource: string,\n type?: ImportEventData['type'],\n): Promise<void> => {\n loader.start()\n\n const TEMP_DOCUMENT_NAME = 'drafts'\n\n // First load the document into a draft store\n // This is to get the title of the document so we can generate a unique slug for store\n const draftStore = createWorkspaceStore({\n fileLoader,\n meta: {\n /** Ensure we use the active proxy to fetch documents */\n 'x-scalar-active-proxy':\n workspaceStore.workspace['x-scalar-active-proxy'],\n },\n })\n\n const eventType = (() => {\n if (type) {\n return type\n }\n\n if (isUrlInput.value) {\n return 'url'\n }\n\n return 'raw'\n })()\n\n const isSuccessfullyLoaded = await loadDocumentFromSource(\n draftStore,\n { source: newSource, type: eventType },\n TEMP_DOCUMENT_NAME,\n watchMode.value,\n )\n\n if (!isSuccessfullyLoaded) {\n return handleImportError('Failed to import document')\n }\n\n const importResult = await importDocumentToWorkspace({\n workspaceStore,\n workspaceState: draftStore.exportWorkspace(),\n name: TEMP_DOCUMENT_NAME,\n })\n\n if (!importResult.ok) {\n return handleImportError(importResult.error)\n }\n\n // Validate the loader to show the success state\n await loader.validate()\n\n // Navigate to the document overview page\n navigateToDocument(importResult.slug)\n\n // Close the command palette\n emit('close')\n}\n\n/** Navigate to the document overview page after successful import */\nconst navigateToDocument = (documentName: string): void => {\n router.push({\n name: 'document.overview',\n params: { documentSlug: documentName },\n })\n}\n\n/**\n * Import a file, routing Postman collections to the Postman import modal and\n * everything else through the OpenAPI import flow.\n *\n * Shared between the default file picker and the `fileUpload` slot so custom\n * path-based importers get the same Postman detection behavior.\n *\n * When `type` is `'file'` the `source` is a file path resolved through the\n * configured `fileLoader`; when `type` is `'raw'` the `source` is treated as\n * the file's text content directly.\n */\nconst handleFileImport: (\n source: string,\n type?: 'file' | 'raw',\n) => Promise<void> = async (source, type = 'raw') => {\n // Resolve the raw text content so we can sniff for a Postman collection.\n // For raw pastes / uploads the source already is the text. For path-based\n // imports we delegate to the file loader plugin, if one is configured.\n const rawContent = await (async (): Promise<string> => {\n if (type === 'raw') {\n return source\n }\n\n const result = await fileLoader?.exec(source)\n return result?.ok ? result.raw : ''\n })()\n\n if (isPostmanCollection(rawContent)) {\n eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: rawContent,\n },\n })\n await loader.clear()\n return\n }\n\n await handleImport(source, type)\n}\n\n/**\n * Handle file selection and import from file dialog.\n * Reads the file as text and imports it as OpenAPI or Postman collection.\n * Shows loading state during the import process.\n */\nconst { open: openSpecFileDialog } = useFileDialog({\n onChange: (files) => {\n const [file] = files ?? []\n\n if (!file) {\n return\n }\n\n loader.start()\n\n const onLoad = async (event: ProgressEvent<FileReader>): Promise<void> => {\n const text = event.target?.result as string\n await handleFileImport(text, 'raw')\n }\n\n const reader = new FileReader()\n reader.onload = onLoad\n reader.readAsText(file)\n },\n multiple: false,\n accept: '.json,.yaml,.yml',\n})\n\n/**\n * Handle input changes.\n * Detects cURL commands and redirects to the cURL import command.\n */\nconst handleInput = (value: string): void => {\n const trimmed = value.trim()\n\n if (trimmed.toLowerCase().startsWith('curl')) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-curl-command',\n payload: {\n inputValue: value,\n },\n })\n }\n\n if (isPostmanCollection(trimmed)) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: value,\n },\n })\n }\n\n inputContent.value = value\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n emit('back', event)\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n :loader\n @submit=\"handleImport(inputContent)\">\n <!-- URL or cURL input mode -->\n <template v-if=\"!documentDetails || isUrlInput\">\n <CommandActionInput\n :modelValue=\"inputContent\"\n placeholder=\"OpenAPI/Swagger/Postman URL or cURL\"\n @delete=\"handleBack\"\n @update:modelValue=\"handleInput\" />\n </template>\n\n <!-- Preview mode for pasted content -->\n <template v-else>\n <!-- Preview header with clear button -->\n <div class=\"flex justify-between\">\n <div class=\"text-c-2 min-h-8 w-full py-2 pl-12 text-center text-xs\">\n Preview\n </div>\n <ScalarButton\n class=\"hover:bg-b-2 relative ml-auto max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"ghost\"\n @click=\"inputContent = ''\">\n Clear\n </ScalarButton>\n </div>\n\n <!-- Code preview with syntax highlighting -->\n <ScalarCodeBlock\n v-if=\"documentDetails && !isUrlInput\"\n class=\"bg-b-2 mt-1 max-h-[40dvh] rounded border px-2 py-1 text-sm\"\n :content=\"inputContent\"\n :copy=\"false\"\n :lang=\"documentType\" />\n </template>\n\n <!-- Actions: File upload and watch mode toggle -->\n <template #options>\n <div class=\"flex w-full flex-row items-center justify-between gap-3\">\n <!-- Custom file upload slot or default button -->\n <slot\n :import=\"handleFileImport\"\n name=\"fileUpload\">\n <!-- Default file upload button -->\n <ScalarButton\n class=\"hover:bg-b-2 relative max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"outlined\"\n @click=\"openSpecFileDialog\">\n JSON, or YAML File\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"Upload\"\n size=\"md\" />\n </ScalarButton>\n </slot>\n\n <!-- Watch mode toggle (only enabled for URL imports) -->\n <ScalarTooltip\n :content=\"\n isUrlInput\n ? 'Watch mode automatically updates the API client when the OpenAPI URL content changes, ensuring your client remains up-to-date.'\n : 'Watch mode is only available for URL imports. When enabled it automatically updates the API client when the OpenAPI URL content changes.'\n \"\n placement=\"bottom\">\n <WatchModeToggle\n v-model=\"watchMode\"\n :disabled=\"!isUrlInput\" />\n </ScalarTooltip>\n </div>\n </template>\n\n <!-- Dynamic submit button text based on import type -->\n <template #submit>\n Import\n <template v-if=\"isUrlInput\">from URL</template>\n <template v-else-if=\"documentDetails && documentType\">\n <template v-if=\"documentDetails.title\">\n \"{{ documentDetails.title }}\"\n </template>\n <template v-else>\n {{ documentDetails.version }}\n </template>\n </template>\n <template v-else>Collection</template>\n </template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CommandPaletteImport.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Import Component\n *\n * Provides a form for importing OpenAPI descriptions from URL, file, or pasted JSON/YAML.\n * Postman collection JSON and Postman files open {@link CommandPaletteImportPostman}.\n * cURL commands redirect to {@link CommandPaletteImportCurl}.\n *\n * Supports watch mode for URL imports to automatically update when content changes.\n */\nexport default {\n name: 'CommandPaletteImport',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarCodeBlock,\n ScalarIcon,\n ScalarTooltip,\n useLoadingState,\n} from '@scalar/components'\nimport { isLocalUrl } from '@scalar/helpers/url/is-local-url'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { isPostmanCollection } from '@scalar/postman-to-openapi'\nimport { useToasts } from '@scalar/use-toasts'\nimport {\n createWorkspaceStore,\n type WorkspaceStore,\n} from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { computed, ref, watch } from 'vue'\n\nimport { useFileDialog } from '@/hooks/use-file-dialog'\nimport { getOpenApiDocumentDetails } from '@/v2/features/command-palette/helpers/get-openapi-document-details'\nimport { importDocumentToWorkspace } from '@/v2/features/command-palette/helpers/import-document-to-workspace'\nimport {\n loadDocumentFromSource,\n type ImportEventData,\n} from '@/v2/features/command-palette/helpers/load-document-from-source'\nimport { isUrl } from '@/v2/helpers/is-url'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\nimport WatchModeToggle from './WatchModeToggle.vue'\n\nconst { workspaceStore, eventBus, fileLoader } = defineProps<{\n /** The workspace store for adding documents */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** Loader plugin for file import */\n fileLoader?: LoaderPlugin\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the import is complete or cancelled */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\ndefineSlots<{\n /**\n * Slot for custom file upload component that can trigger import.\n *\n * The provided `import` function automatically detects Postman collections\n * and routes them to the Postman import modal, matching the behavior of the\n * default file picker.\n */\n fileUpload(props: {\n /** Function to trigger import with source content and type */\n import: (source: string, type: 'file' | 'raw') => Promise<void>\n }): void\n}>()\n\nconst { toast } = useToasts()\n\nconst loader = useLoadingState()\n\nconst inputContent = ref('')\nconst watchMode = ref(false)\n\n/** Trim paste noise for URL checks without changing raw JSON/YAML content. */\nconst normalizedInputContent = computed<string>(() => inputContent.value.trim())\n\n/** Check if the input content is a URL */\nconst isUrlInput = computed<boolean>(() => isUrl(normalizedInputContent.value))\nconst isLocalUrlInput = computed<boolean>(\n () => isUrlInput.value && isLocalUrl(normalizedInputContent.value),\n)\n\nconst documentDetails = computed(() =>\n getOpenApiDocumentDetails(inputContent.value),\n)\n\n/** Get the document type for syntax highlighting */\nconst documentType = computed<string>(() =>\n documentDetails.value ? documentDetails.value.type : 'json',\n)\n\n/** Check if the form should be disabled (when input is empty) */\nconst isDisabled = computed<boolean>(() => {\n return !inputContent.value.trim()\n})\n\n/**\n * Toggle watchMode based on whether the input is a local URL.\n * Only enables watch mode for local URLs, not for files or pasted content.\n */\nwatch(isLocalUrlInput, (value: boolean) => {\n watchMode.value = value\n})\n\n/**\n * Handles errors during the import process.\n * Shows an error toast, invalidates the loader to show an error state,\n * and closes the command palette modal.\n *\n * @param errorMessage - The error message to display and log\n */\nconst handleImportError = async (errorMessage: string) => {\n // Log the error\n console.error(errorMessage)\n toast(errorMessage, 'error')\n\n // Invalidate the loader to show the error state\n await loader.invalidate()\n\n // Close the command palette\n emit('close')\n}\n\n/**\n * Directly imports a document into the workspace without showing the modal.\n * This is used when there is only one workspace and it is empty.\n */\nconst handleImport = async (\n newSource: string,\n type?: ImportEventData['type'],\n): Promise<void> => {\n loader.start()\n\n const TEMP_DOCUMENT_NAME = 'drafts'\n\n // First load the document into a draft store\n // This is to get the title of the document so we can generate a unique slug for store\n const draftStore = createWorkspaceStore({\n fileLoader,\n meta: {\n /** Ensure we use the active proxy to fetch documents */\n 'x-scalar-active-proxy':\n workspaceStore.workspace['x-scalar-active-proxy'],\n },\n })\n\n const eventType = (() => {\n if (type) {\n return type\n }\n\n if (isUrl(newSource.trim())) {\n return 'url'\n }\n\n return 'raw'\n })()\n\n const source = eventType === 'url' ? newSource.trim() : newSource\n\n const isSuccessfullyLoaded = await loadDocumentFromSource(\n draftStore,\n { source, type: eventType },\n TEMP_DOCUMENT_NAME,\n watchMode.value,\n )\n\n if (!isSuccessfullyLoaded) {\n return handleImportError('Failed to import document')\n }\n\n const importResult = await importDocumentToWorkspace({\n workspaceStore,\n workspaceState: draftStore.exportWorkspace(),\n name: TEMP_DOCUMENT_NAME,\n })\n\n if (!importResult.ok) {\n return handleImportError(importResult.error)\n }\n\n // Validate the loader to show the success state\n await loader.validate()\n\n // Navigate to the document overview page\n navigateToDocument(importResult.slug)\n\n // Close the command palette\n emit('close')\n}\n\n/** Navigate to the document overview page after successful import */\nconst navigateToDocument = (documentName: string): void => {\n eventBus.emit('ui:navigate', {\n page: 'document',\n path: 'overview',\n documentSlug: documentName,\n })\n}\n\n/**\n * Import a file, routing Postman collections to the Postman import modal and\n * everything else through the OpenAPI import flow.\n *\n * Shared between the default file picker and the `fileUpload` slot so custom\n * path-based importers get the same Postman detection behavior.\n *\n * When `type` is `'file'` the `source` is a file path resolved through the\n * configured `fileLoader`; when `type` is `'raw'` the `source` is treated as\n * the file's text content directly.\n */\nconst handleFileImport: (\n source: string,\n type?: 'file' | 'raw',\n) => Promise<void> = async (source, type = 'raw') => {\n // Resolve the raw text content so we can sniff for a Postman collection.\n // For raw pastes / uploads the source already is the text. For path-based\n // imports we delegate to the file loader plugin, if one is configured.\n const rawContent = await (async (): Promise<string> => {\n if (type === 'raw') {\n return source\n }\n\n const result = await fileLoader?.exec(source)\n return result?.ok ? result.raw : ''\n })()\n\n if (isPostmanCollection(rawContent)) {\n eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: rawContent,\n },\n })\n await loader.clear()\n return\n }\n\n await handleImport(source, type)\n}\n\n/**\n * Handle file selection and import from file dialog.\n * Reads the file as text and imports it as OpenAPI or Postman collection.\n * Shows loading state during the import process.\n */\nconst { open: openSpecFileDialog } = useFileDialog({\n onChange: (files) => {\n const [file] = files ?? []\n\n if (!file) {\n return\n }\n\n loader.start()\n\n const onLoad = async (event: ProgressEvent<FileReader>): Promise<void> => {\n const text = event.target?.result as string\n await handleFileImport(text, 'raw')\n }\n\n const reader = new FileReader()\n reader.onload = onLoad\n reader.readAsText(file)\n },\n multiple: false,\n accept: '.json,.yaml,.yml',\n})\n\n/**\n * Handle input changes.\n * Detects cURL commands and redirects to the cURL import command.\n */\nconst handleInput = (value: string): void => {\n const trimmed = value.trim()\n\n if (trimmed.toLowerCase().startsWith('curl')) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-curl-command',\n payload: {\n inputValue: value,\n },\n })\n }\n\n if (isPostmanCollection(trimmed)) {\n return eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: value,\n },\n })\n }\n\n inputContent.value = value\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n emit('back', event)\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n :loader\n @submit=\"handleImport(inputContent)\">\n <!-- URL or cURL input mode -->\n <template v-if=\"!documentDetails || isUrlInput\">\n <CommandActionInput\n :modelValue=\"inputContent\"\n placeholder=\"OpenAPI/Swagger/Postman URL or cURL\"\n @delete=\"handleBack\"\n @update:modelValue=\"handleInput\" />\n </template>\n\n <!-- Preview mode for pasted content -->\n <template v-else>\n <!-- Preview header with clear button -->\n <div class=\"flex justify-between\">\n <div class=\"text-c-2 min-h-8 w-full py-2 pl-12 text-center text-xs\">\n Preview\n </div>\n <ScalarButton\n class=\"hover:bg-b-2 relative ml-auto max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"ghost\"\n @click=\"inputContent = ''\">\n Clear\n </ScalarButton>\n </div>\n\n <!-- Code preview with syntax highlighting -->\n <ScalarCodeBlock\n v-if=\"documentDetails && !isUrlInput\"\n class=\"bg-b-2 mt-1 max-h-[40dvh] rounded border px-2 py-1 text-sm\"\n :content=\"inputContent\"\n :copy=\"false\"\n :lang=\"documentType\" />\n </template>\n\n <!-- Actions: File upload and watch mode toggle -->\n <template #options>\n <div class=\"flex w-full flex-row items-center justify-between gap-3\">\n <!-- Custom file upload slot or default button -->\n <slot\n :import=\"handleFileImport\"\n name=\"fileUpload\">\n <!-- Default file upload button -->\n <ScalarButton\n class=\"hover:bg-b-2 relative max-h-8 gap-1.5 p-2 text-xs\"\n variant=\"outlined\"\n @click=\"openSpecFileDialog\">\n JSON, or YAML File\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"Upload\"\n size=\"md\" />\n </ScalarButton>\n </slot>\n\n <!-- Watch mode toggle (only enabled for URL imports) -->\n <ScalarTooltip\n :content=\"\n isUrlInput\n ? 'Watch mode automatically updates the API client when the OpenAPI URL content changes, ensuring your client remains up-to-date.'\n : 'Watch mode is only available for URL imports. When enabled it automatically updates the API client when the OpenAPI URL content changes.'\n \"\n placement=\"bottom\">\n <WatchModeToggle\n v-model=\"watchMode\"\n :disabled=\"!isUrlInput\" />\n </ScalarTooltip>\n </div>\n </template>\n\n <!-- Dynamic submit button text based on import type -->\n <template #submit>\n Import\n <template v-if=\"isUrlInput\">from URL</template>\n <template v-else-if=\"documentDetails && documentType\">\n <template v-if=\"documentDetails.title\">\n \"{{ documentDetails.title }}\"\n </template>\n <template v-else>\n {{ documentDetails.version }}\n </template>\n </template>\n <template v-else>Collection</template>\n </template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteImportCurl.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImportCurl.vue"],"names":[],"mappings":"AAwOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAUvE;;;;;;;;;;;;;;;;;GAiBG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY;IAEhB,iEAAiE;oBACjD,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,kDAAkD;gBACtC,MAAM;;;;;IALlB,iEAAiE;oBACjD,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,kDAAkD;gBACtC,MAAM;;;;kFA8ShB,CAAC"}
1
+ {"version":3,"file":"CommandPaletteImportCurl.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImportCurl.vue"],"names":[],"mappings":"AA0PA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AASvE;;;;;;;;;;;;;;;;;GAiBG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY;IAEhB,iEAAiE;oBACjD,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,kDAAkD;gBACtC,MAAM;;;;;IALlB,iEAAiE;oBACjD,cAAc;IAC9B,uDAAuD;cAC7C,iBAAiB;IAC3B,kDAAkD;gBACtC,MAAM;;;;kFAsUhB,CAAC"}
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_
2
2
  import CommandPaletteImportCurl_vue_vue_type_script_setup_true_lang_default from "./CommandPaletteImportCurl.vue.script.js";
3
3
  /* empty css */
4
4
  //#region src/v2/features/command-palette/components/CommandPaletteImportCurl.vue
5
- var CommandPaletteImportCurl_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CommandPaletteImportCurl_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-f4568236"]]);
5
+ var CommandPaletteImportCurl_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CommandPaletteImportCurl_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-311ac78f"]]);
6
6
  //#endregion
7
7
  export { CommandPaletteImportCurl_default as default };
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPaletteImportCurl.vue.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImportCurl.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Import cURL Component\n *\n * Provides a form for importing API requests from cURL commands.\n * Parses the cURL command to extract the HTTP method, URL, path, headers,\n * and body, then creates a new operation in the selected document.\n *\n * Validates that no conflicting operation exists at the same path/method.\n *\n * @example\n * <CommandPaletteImportCurl\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * :curl=\"curlCommand\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n */\nexport default {\n name: 'CommandPaletteImportCurl',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarIcon,\n ScalarListbox,\n type ScalarComboboxOption,\n} from '@scalar/components'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { computed, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport HttpMethod from '@/v2/blocks/operation-code-sample/components/HttpMethod.vue'\nimport CommandActionForm from '@/v2/features/command-palette/components/CommandActionForm.vue'\nimport CommandActionInput from '@/v2/features/command-palette/components/CommandActionInput.vue'\nimport { getOperationFromCurl } from '@/v2/features/command-palette/helpers/get-operation-from-curl'\n\nconst { workspaceStore, inputValue, eventBus } = defineProps<{\n /** The workspace store for accessing documents and operations */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** The cURL command string to parse and import */\n inputValue: string\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the import is complete */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\nconst router = useRouter()\n\nconst exampleKey = ref('')\n\n/** Trimmed version of the example key for validation and submission */\nconst exampleKeyTrimmed = computed<string>(() => exampleKey.value.trim())\n\n/** Parse the cURL command to extract path, method, and operation details */\nconst { path, method, operation } = getOperationFromCurl(inputValue)\n\n/** List of all available documents (collections) in the workspace */\nconst documents = computed(() =>\n Object.keys(workspaceStore.workspace.documents).map((document) => ({\n id: document,\n label: document,\n })),\n)\n\nconst selectedDocument = ref<ScalarComboboxOption | undefined>(\n documents.value[0],\n)\n\n/**\n * Check if the form should be disabled.\n * Disabled when:\n * - Example key is empty\n * - No document is selected\n * - An operation with the same path and method already exists in the selected document\n */\nconst isDisabled = computed<boolean>(() => {\n if (!exampleKeyTrimmed.value || !selectedDocument.value) {\n return true\n }\n\n /** Prevent creating duplicate operations at the same path/method */\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n if (document?.paths?.[path]?.[method]) {\n return true\n }\n\n return false\n})\n\n/**\n * Handle the import submission.\n * Creates a new operation in the selected document from the parsed cURL command.\n */\nconst handleImportClick = (): void => {\n const documentName = selectedDocument.value\n\n if (isDisabled.value || !documentName) {\n return\n }\n\n /** Re-parse with the example key to include it in the operation */\n const result = getOperationFromCurl(inputValue, exampleKeyTrimmed.value)\n\n eventBus.emit('operation:create:operation', {\n documentName: documentName.id,\n path: result.path,\n method: result.method,\n operation: result.operation,\n exampleKey: exampleKeyTrimmed.value,\n callback: (success) => {\n if (success) {\n // build the sidebar\n workspaceStore.buildSidebar(documentName.id)\n\n const path = result.path.startsWith('/')\n ? result.path\n : `/${result.path}`\n\n // navigate to the operation\n router.push({\n name: 'example',\n params: {\n documentSlug: documentName.id,\n pathEncoded: encodeURIComponent(path),\n method: result.method,\n exampleName: exampleKeyTrimmed.value,\n },\n })\n }\n },\n })\n\n emit('close')\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n emit('back', event)\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n @submit=\"handleImportClick\">\n <!-- Example key input -->\n <CommandActionInput\n v-model=\"exampleKey\"\n placeholder=\"Curl example key (e.g., example-1)\"\n @delete=\"handleBack\" />\n\n <!-- Preview of the parsed cURL request (method + URL + path) -->\n <div class=\"flex flex-1 flex-col gap-2\">\n <div\n class=\"flex h-9 flex-row items-center gap-2 rounded border p-[3px] text-sm\">\n <HttpMethod\n class=\"border-r-1 px-1\"\n :method=\"method\" />\n <span class=\"scroll-timeline-x whitespace-nowrap\">\n {{ operation.servers?.[0]?.url || '' }}{{ path }}\n </span>\n </div>\n </div>\n\n <!-- Document selector -->\n <template #options>\n <div class=\"flex items-center gap-2\">\n <ScalarListbox\n v-model=\"selectedDocument\"\n :options=\"documents\">\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-full justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <span\n class=\"whitespace-nowrap\"\n :class=\"selectedDocument ? 'text-c-1' : 'text-c-3'\">\n {{\n selectedDocument ? selectedDocument.label : 'Select Collection'\n }}\n </span>\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n </ScalarListbox>\n </div>\n </template>\n\n <template #submit>Import Request</template>\n </CommandActionForm>\n</template>\n<style scoped>\n/**\n * Custom horizontal scroll for the URL preview.\n * Hides scrollbar for a cleaner appearance while maintaining scroll functionality.\n */\n.scroll-timeline-x {\n overflow: auto;\n scroll-timeline: --scroll-timeline x;\n /* Firefox support */\n scroll-timeline: --scroll-timeline horizontal;\n /* Hide scrollbar in IE and Edge */\n -ms-overflow-style: none;\n /* Hide scrollbar in Firefox */\n scrollbar-width: none;\n}\n\n/* Hide scrollbar in Chrome, Safari, and Opera */\n.scroll-timeline-x::-webkit-scrollbar {\n display: none;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"CommandPaletteImportCurl.vue.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImportCurl.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Import cURL Component\n *\n * Provides a form for importing API requests from cURL commands.\n * Parses the cURL command to extract the HTTP method, URL, path, headers,\n * and body, then creates a new operation in the selected document.\n *\n * Validates that no conflicting operation exists at the same path/method.\n *\n * @example\n * <CommandPaletteImportCurl\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * :curl=\"curlCommand\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n */\nexport default {\n name: 'CommandPaletteImportCurl',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarIcon,\n ScalarListbox,\n type ScalarComboboxOption,\n} from '@scalar/components'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport { computed, ref, type ComputedRef } from 'vue'\n\nimport HttpMethod from '@/v2/blocks/operation-code-sample/components/HttpMethod.vue'\nimport CommandActionForm from '@/v2/features/command-palette/components/CommandActionForm.vue'\nimport CommandActionInput from '@/v2/features/command-palette/components/CommandActionInput.vue'\nimport { getOperationFromCurl } from '@/v2/features/command-palette/helpers/get-operation-from-curl'\n\nconst { workspaceStore, inputValue, eventBus } = defineProps<{\n /** The workspace store for accessing documents and operations */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting operation creation events */\n eventBus: WorkspaceEventBus\n /** The cURL command string to parse and import */\n inputValue: string\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the import is complete */\n (event: 'close'): void\n /** Emitted when user navigates back (e.g., backspace on empty input) */\n (event: 'back', keyboardEvent: KeyboardEvent): void\n}>()\n\nconst exampleKey = ref('')\n\n/** Trimmed version of the example key for validation and submission */\nconst exampleKeyTrimmed = computed<string>(() => exampleKey.value.trim())\n\n/** Parse the cURL command to extract path, method, and operation details */\nconst { path, method, operation } = getOperationFromCurl(inputValue)\n\n/** List of all available documents (collections) in the workspace */\nconst documents = computed(() =>\n Object.keys(workspaceStore.workspace.documents).map((document) => ({\n id: document,\n label: document,\n })),\n)\n\nconst selectedDocument = ref<ScalarComboboxOption | undefined>(\n documents.value[0],\n)\n\n/**\n * Validation message surfaced under the input.\n *\n * Resolves to `null` when the form is valid; otherwise to a human-readable\n * reason the user can act on. Empty input is the default state so we keep\n * the field free of error styling `isDisabled` still blocks submission\n * there, matching the {@link CommandPaletteOpenApiDocument} pattern.\n */\nconst errorMessage: ComputedRef<string | null> = computed(() => {\n if (!exampleKeyTrimmed.value || !selectedDocument.value) {\n return null\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n\n if (document?.paths?.[path]?.[method]) {\n return `A ${method.toUpperCase()} operation at \"${path}\" already exists in \"${selectedDocument.value.label}\". Importing this cURL would conflict with it.`\n }\n\n return null\n})\n\n/**\n * Submit is blocked while required fields are missing or a duplicate exists.\n * The inline `errorMessage` makes the duplicate case explicit so the user\n * understands why import is unavailable rather than seeing a silent disable.\n */\nconst isDisabled = computed<boolean>(\n () =>\n !exampleKeyTrimmed.value ||\n !selectedDocument.value ||\n errorMessage.value !== null,\n)\n\n/**\n * Handle the import submission.\n * Creates a new operation in the selected document from the parsed cURL command.\n */\nconst handleImportClick = (): void => {\n const documentName = selectedDocument.value\n\n if (isDisabled.value || !documentName) {\n return\n }\n\n /** Re-parse with the example key to include it in the operation */\n const result = getOperationFromCurl(inputValue, exampleKeyTrimmed.value)\n\n eventBus.emit('operation:create:operation', {\n documentName: documentName.id,\n path: result.path,\n method: result.method,\n operation: result.operation,\n exampleKey: exampleKeyTrimmed.value,\n callback: (success) => {\n if (!success) {\n return\n }\n\n // build the sidebar\n workspaceStore.buildSidebar(documentName.id)\n\n const normalizedPath = result.path.startsWith('/')\n ? result.path\n : `/${result.path}`\n\n // Navigate to the new example via the event bus rather than the router\n eventBus.emit('ui:navigate', {\n page: 'example',\n documentSlug: documentName.id,\n path: normalizedPath,\n method: result.method,\n exampleName: exampleKeyTrimmed.value,\n })\n },\n })\n\n emit('close')\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n emit('back', event)\n}\n</script>\n<template>\n <CommandActionForm\n :disabled=\"isDisabled\"\n @submit=\"handleImportClick\">\n <!-- Example key input -->\n <CommandActionInput\n v-model=\"exampleKey\"\n placeholder=\"Curl example key (e.g., example-1)\"\n @delete=\"handleBack\" />\n\n <p\n v-if=\"errorMessage\"\n class=\"text-red px-2 pb-1 text-xs\"\n data-testid=\"command-palette-import-curl-error\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <!-- Preview of the parsed cURL request (method + URL + path) -->\n <div class=\"flex flex-1 flex-col gap-2\">\n <div\n class=\"flex h-9 flex-row items-center gap-2 rounded border p-[3px] text-sm\">\n <HttpMethod\n class=\"border-r-1 px-1\"\n :method=\"method\" />\n <span class=\"scroll-timeline-x whitespace-nowrap\">\n {{ operation.servers?.[0]?.url || '' }}{{ path }}\n </span>\n </div>\n </div>\n\n <!-- Document selector -->\n <template #options>\n <div class=\"flex items-center gap-2\">\n <ScalarListbox\n v-model=\"selectedDocument\"\n :options=\"documents\">\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-full justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <span\n class=\"whitespace-nowrap\"\n :class=\"selectedDocument ? 'text-c-1' : 'text-c-3'\">\n {{\n selectedDocument ? selectedDocument.label : 'Select Collection'\n }}\n </span>\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n </ScalarListbox>\n </div>\n </template>\n\n <template #submit>Import Request</template>\n </CommandActionForm>\n</template>\n<style scoped>\n/**\n * Custom horizontal scroll for the URL preview.\n * Hides scrollbar for a cleaner appearance while maintaining scroll functionality.\n */\n.scroll-timeline-x {\n overflow: auto;\n scroll-timeline: --scroll-timeline x;\n /* Firefox support */\n scroll-timeline: --scroll-timeline horizontal;\n /* Hide scrollbar in IE and Edge */\n -ms-overflow-style: none;\n /* Hide scrollbar in Firefox */\n scrollbar-width: none;\n}\n\n/* Hide scrollbar in Chrome, Safari, and Opera */\n.scroll-timeline-x::-webkit-scrollbar {\n display: none;\n}\n</style>\n"],"mappings":""}