@scalar/api-client 3.5.1 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/style.css +3933 -4768
- package/dist/styles/tailwind.config.css +20 -0
- package/dist/styles/utilities.css +45 -0
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.js +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.js.map +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.script.js +1 -1
- package/dist/v2/blocks/operation-code-sample/components/OperationCodeSample.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.d.ts +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.d.ts.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/app/App.vue.d.ts +15 -31
- package/dist/v2/features/app/App.vue.d.ts.map +1 -1
- package/dist/v2/features/app/App.vue.js.map +1 -1
- package/dist/v2/features/app/App.vue.script.js +131 -50
- package/dist/v2/features/app/App.vue.script.js.map +1 -1
- package/dist/v2/features/app/app-state.d.ts +10 -14
- package/dist/v2/features/app/app-state.d.ts.map +1 -1
- package/dist/v2/features/app/app-state.js +53 -21
- package/dist/v2/features/app/app-state.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.js.map +1 -1
- package/dist/v2/features/app/components/AppHeader.vue.script.js +4 -3
- package/dist/v2/features/app/components/AppHeader.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/AppHeaderActions.vue.d.ts +32 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.js +7 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.js.map +1 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.script.js +172 -0
- package/dist/v2/features/app/components/AppHeaderActions.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts +2 -5
- package/dist/v2/features/app/components/AppSidebar.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.js +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.js.map +1 -1
- package/dist/v2/features/app/components/AppSidebar.vue.script.js +4 -9
- package/dist/v2/features/app/components/AppSidebar.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DesktopTabs.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/DesktopTabs.vue.js.map +1 -1
- package/dist/v2/features/app/components/DesktopTabs.vue.script.js +2 -2
- package/dist/v2/features/app/components/DesktopTabs.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts +1 -2
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.js.map +1 -1
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js +4 -35
- package/dist/v2/features/app/components/DocumentBreadcrumb.vue.script.js.map +1 -1
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.d.ts +1 -1
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.d.ts.map +1 -1
- package/dist/v2/features/app/components/PublishDocumentModal.vue.d.ts +77 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.d.ts.map +1 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.js +7 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.js.map +1 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.script.js +209 -0
- package/dist/v2/features/app/components/PublishDocumentModal.vue.script.js.map +1 -0
- package/dist/v2/features/app/components/SyncConflictResolutionEditor.vue.d.ts.map +1 -0
- package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.js +2 -2
- package/dist/v2/features/app/components/SyncConflictResolutionEditor.vue.js.map +1 -0
- package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.script.js +1 -1
- package/dist/v2/features/{collection/components/SyncConflictResolutionEditor.vue.js.map → app/components/SyncConflictResolutionEditor.vue.script.js.map} +1 -1
- package/dist/v2/features/app/helpers/check-version-conflict.d.ts +8 -5
- package/dist/v2/features/app/helpers/check-version-conflict.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/check-version-conflict.js +10 -7
- package/dist/v2/features/app/helpers/check-version-conflict.js.map +1 -1
- package/dist/v2/features/app/helpers/create-api-client-app.d.ts +8 -5
- package/dist/v2/features/app/helpers/create-api-client-app.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/create-api-client-app.js +2 -2
- package/dist/v2/features/app/helpers/create-api-client-app.js.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.d.ts +1 -10
- package/dist/v2/features/app/helpers/load-registry-document.d.ts.map +1 -1
- package/dist/v2/features/app/helpers/load-registry-document.js +6 -5
- package/dist/v2/features/app/helpers/load-registry-document.js.map +1 -1
- package/dist/v2/features/app/helpers/registry-error-messages.d.ts +23 -0
- package/dist/v2/features/app/helpers/registry-error-messages.d.ts.map +1 -0
- package/dist/v2/features/app/helpers/registry-error-messages.js +63 -0
- package/dist/v2/features/app/helpers/registry-error-messages.js.map +1 -0
- package/dist/v2/features/app/hooks/use-active-document-version.d.ts +2 -1
- package/dist/v2/features/app/hooks/use-active-document-version.d.ts.map +1 -1
- package/dist/v2/features/app/hooks/use-active-document-version.js.map +1 -1
- package/dist/v2/features/app/hooks/use-document-sync.d.ts +126 -0
- package/dist/v2/features/app/hooks/use-document-sync.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-document-sync.js +448 -0
- package/dist/v2/features/app/hooks/use-document-sync.js.map +1 -0
- package/dist/v2/features/app/hooks/use-network-status.d.ts +29 -0
- package/dist/v2/features/app/hooks/use-network-status.d.ts.map +1 -0
- package/dist/v2/features/app/hooks/use-network-status.js +58 -0
- package/dist/v2/features/app/hooks/use-network-status.js.map +1 -0
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts +1 -25
- package/dist/v2/features/app/hooks/use-sidebar-documents.d.ts.map +1 -1
- package/dist/v2/features/app/hooks/use-sidebar-documents.js.map +1 -1
- package/dist/v2/features/app/index.d.ts +1 -1
- package/dist/v2/features/app/index.d.ts.map +1 -1
- package/dist/v2/features/collection/DocumentCollection.vue.d.ts.map +1 -1
- package/dist/v2/features/collection/DocumentCollection.vue.js.map +1 -1
- package/dist/v2/features/collection/DocumentCollection.vue.script.js +43 -277
- package/dist/v2/features/collection/DocumentCollection.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.script.js +35 -17
- package/dist/v2/features/command-palette/components/CommandPaletteExample.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js +15 -13
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.js +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.script.js +51 -39
- package/dist/v2/features/command-palette/components/CommandPaletteImportCurl.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js +30 -14
- package/dist/v2/features/command-palette/components/CommandPaletteOpenApiDocument.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.script.js +44 -42
- package/dist/v2/features/command-palette/components/CommandPaletteRequest.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.script.js +33 -17
- package/dist/v2/features/command-palette/components/CommandPaletteTag.vue.script.js.map +1 -1
- package/dist/v2/features/command-palette/helpers/load-document-from-source.d.ts.map +1 -1
- package/dist/v2/features/command-palette/helpers/load-document-from-source.js +3 -2
- package/dist/v2/features/command-palette/helpers/load-document-from-source.js.map +1 -1
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.d.ts.map +1 -1
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.js +5 -5
- package/dist/v2/features/editor/hooks/use-three-way-merge-editor.js.map +1 -1
- package/dist/v2/helpers/is-url.d.ts.map +1 -1
- package/dist/v2/helpers/is-url.js +2 -1
- package/dist/v2/helpers/is-url.js.map +1 -1
- package/dist/v2/types/configuration.d.ts +273 -7
- package/dist/v2/types/configuration.d.ts.map +1 -1
- package/dist/vue-styles.css +1389 -0
- package/package.json +20 -14
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.js +0 -7
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.js.map +0 -1
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.script.js +0 -51
- package/dist/v2/features/app/components/DocumentSyncIndicator.vue.script.js.map +0 -1
- package/dist/v2/features/collection/components/SyncConflictResolutionEditor.vue.d.ts.map +0 -1
- package/dist/v2/features/collection/components/SyncConflictResolutionEditor.vue.script.js.map +0 -1
- /package/dist/v2/features/{collection → app}/components/SyncConflictResolutionEditor.vue.d.ts +0 -0
|
@@ -3,14 +3,19 @@ import CommandActionForm_default from "./CommandActionForm.vue.js";
|
|
|
3
3
|
import CommandActionInput_default from "./CommandActionInput.vue.js";
|
|
4
4
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, watch, withCtx } from "vue";
|
|
5
5
|
import { ScalarButton, ScalarDropdown, ScalarDropdownItem, ScalarIcon, ScalarListbox } from "@scalar/components";
|
|
6
|
-
import { useRouter } from "vue-router";
|
|
7
6
|
import { HTTP_METHODS } from "@scalar/helpers/http/http-methods";
|
|
8
7
|
//#region src/v2/features/command-palette/components/CommandPaletteRequest.vue?vue&type=script&setup=true&lang.ts
|
|
9
|
-
var _hoisted_1 = {
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
var _hoisted_1 = {
|
|
9
|
+
key: 0,
|
|
10
|
+
class: "text-red px-2 pb-1 text-xs",
|
|
11
|
+
"data-testid": "command-palette-request-error",
|
|
12
|
+
role: "alert"
|
|
13
|
+
};
|
|
14
|
+
var _hoisted_2 = { class: "flex flex-1 gap-1" };
|
|
15
|
+
var _hoisted_3 = { class: "flex items-center gap-2" };
|
|
12
16
|
var _hoisted_4 = { class: "custom-scroll max-h-40" };
|
|
13
|
-
var _hoisted_5 = { class: "
|
|
17
|
+
var _hoisted_5 = { class: "custom-scroll max-h-40" };
|
|
18
|
+
var _hoisted_6 = { class: "truncate" };
|
|
14
19
|
var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
15
20
|
name: "CommandPaletteRequest",
|
|
16
21
|
props: {
|
|
@@ -23,9 +28,10 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
23
28
|
setup(__props, { emit: __emit }) {
|
|
24
29
|
const emit = __emit;
|
|
25
30
|
/** HTTP method option type for selectors */
|
|
26
|
-
const router = useRouter();
|
|
27
31
|
const requestPath = ref("/");
|
|
28
32
|
const requestPathTrimmed = computed(() => requestPath.value.trim());
|
|
33
|
+
/** Ensure path starts with '/' for consistent lookup */
|
|
34
|
+
const normalizedRequestPath = computed(() => requestPathTrimmed.value.startsWith("/") ? requestPathTrimmed.value : `/${requestPathTrimmed.value}`);
|
|
29
35
|
/** All available documents (collections) in the workspace */
|
|
30
36
|
const availableDocuments = computed(() => Object.entries(__props.workspaceStore.workspace.documents).map(([name, document]) => ({
|
|
31
37
|
id: name,
|
|
@@ -60,26 +66,26 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
60
66
|
selectedTag.value = availableTags.value.find((tag) => tag.id === "");
|
|
61
67
|
});
|
|
62
68
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
69
|
+
* Validation message surfaced under the input.
|
|
70
|
+
*
|
|
71
|
+
* Resolves to `null` when the form is valid; otherwise to a human-readable
|
|
72
|
+
* reason the user can act on. Empty input is the default state so we keep
|
|
73
|
+
* the field free of error styling — `isDisabled` still blocks submission
|
|
74
|
+
* there, matching the {@link CommandPaletteOpenApiDocument} pattern.
|
|
65
75
|
*/
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
76
|
+
const errorMessage = computed(() => {
|
|
77
|
+
if (!requestPathTrimmed.value || !selectedDocument.value || !selectedMethod.value) return null;
|
|
68
78
|
const document = __props.workspaceStore.workspace.documents[selectedDocument.value.id];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
79
|
+
const method = selectedMethod.value.method;
|
|
80
|
+
if (document?.paths?.[normalizedRequestPath.value]?.[method]) return `A ${method.toUpperCase()} operation at "${normalizedRequestPath.value}" already exists in "${selectedDocument.value.label}". Try a different path or method.`;
|
|
81
|
+
return null;
|
|
72
82
|
});
|
|
73
83
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
84
|
+
* Submit is blocked while required fields are missing or a duplicate exists.
|
|
85
|
+
* The inline `errorMessage` explains the duplicate case so the user knows how
|
|
86
|
+
* to recover instead of facing a silently disabled button.
|
|
76
87
|
*/
|
|
77
|
-
const isDisabled = computed(() =>
|
|
78
|
-
if (!requestPathTrimmed.value || !selectedDocument.value || !selectedMethod.value) return true;
|
|
79
|
-
/** Prevent creating duplicate operations */
|
|
80
|
-
if (operationExists.value) return true;
|
|
81
|
-
return false;
|
|
82
|
-
});
|
|
88
|
+
const isDisabled = computed(() => !requestPathTrimmed.value || !selectedDocument.value || !selectedMethod.value || errorMessage.value !== null);
|
|
83
89
|
/** Handle HTTP method selection from dropdown */
|
|
84
90
|
const handleSelectMethod = (method) => {
|
|
85
91
|
if (method) selectedMethod.value = method;
|
|
@@ -101,21 +107,17 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
101
107
|
method: selectedMethod.value.method,
|
|
102
108
|
operation: { tags: selectedTag.value?.id ? [selectedTag.value.id] : void 0 },
|
|
103
109
|
callback: (success) => {
|
|
104
|
-
if (success)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
exampleName: "default"
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
110
|
+
if (!success) return;
|
|
111
|
+
/** Build the sidebar */
|
|
112
|
+
__props.workspaceStore.buildSidebar(selectedDocument.value?.id ?? "");
|
|
113
|
+
/** Navigate to the example via the event bus rather than the router */
|
|
114
|
+
__props.eventBus.emit("ui:navigate", {
|
|
115
|
+
page: "example",
|
|
116
|
+
documentSlug: selectedDocument.value?.id,
|
|
117
|
+
path: normalizedRequestPath.value,
|
|
118
|
+
method: selectedMethod.value?.method ?? "get",
|
|
119
|
+
exampleName: "default"
|
|
120
|
+
});
|
|
119
121
|
}
|
|
120
122
|
});
|
|
121
123
|
emit("close");
|
|
@@ -129,7 +131,7 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
129
131
|
disabled: isDisabled.value,
|
|
130
132
|
onSubmit: handleSubmit
|
|
131
133
|
}, {
|
|
132
|
-
options: withCtx(() => [createElementVNode("div",
|
|
134
|
+
options: withCtx(() => [createElementVNode("div", _hoisted_2, [
|
|
133
135
|
createVNode(unref(ScalarListbox), {
|
|
134
136
|
modelValue: selectedDocument.value,
|
|
135
137
|
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => selectedDocument.value = $event),
|
|
@@ -152,7 +154,7 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
152
154
|
placement: "bottom",
|
|
153
155
|
resize: ""
|
|
154
156
|
}, {
|
|
155
|
-
items: withCtx(() => [createElementVNode("div",
|
|
157
|
+
items: withCtx(() => [createElementVNode("div", _hoisted_4, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(availableMethods), (method) => {
|
|
156
158
|
return openBlock(), createBlock(unref(ScalarDropdownItem), {
|
|
157
159
|
key: method.id,
|
|
158
160
|
class: "flex h-7 w-full items-center justify-center px-1",
|
|
@@ -166,7 +168,7 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
166
168
|
class: "hover:bg-b-2 max-h-8 w-[100px] min-w-[100px] justify-between gap-1 p-2 text-xs",
|
|
167
169
|
variant: "outlined"
|
|
168
170
|
}, {
|
|
169
|
-
default: withCtx(() => [createElementVNode("div",
|
|
171
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_3, [selectedMethod.value ? (openBlock(), createBlock(HttpMethod_default, {
|
|
170
172
|
key: 0,
|
|
171
173
|
method: selectedMethod.value.method
|
|
172
174
|
}, null, 8, ["method"])) : createCommentVNode("", true), createVNode(unref(ScalarIcon), {
|
|
@@ -182,13 +184,13 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
182
184
|
placement: "bottom",
|
|
183
185
|
resize: ""
|
|
184
186
|
}, {
|
|
185
|
-
items: withCtx(() => [createElementVNode("div",
|
|
187
|
+
items: withCtx(() => [createElementVNode("div", _hoisted_5, [(openBlock(true), createElementBlock(Fragment, null, renderList(availableTags.value, (tag) => {
|
|
186
188
|
return openBlock(), createBlock(unref(ScalarDropdownItem), {
|
|
187
189
|
key: tag.id,
|
|
188
190
|
class: "flex h-7 w-full items-center px-1",
|
|
189
191
|
onClick: ($event) => handleSelectTag(tag)
|
|
190
192
|
}, {
|
|
191
|
-
default: withCtx(() => [createElementVNode("span",
|
|
193
|
+
default: withCtx(() => [createElementVNode("span", _hoisted_6, toDisplayString(tag.label), 1)]),
|
|
192
194
|
_: 2
|
|
193
195
|
}, 1032, ["onClick"]);
|
|
194
196
|
}), 128))])]),
|
|
@@ -214,7 +216,7 @@ var CommandPaletteRequest_vue_vue_type_script_setup_true_lang_default = /* @__PU
|
|
|
214
216
|
label: "Request Path",
|
|
215
217
|
placeholder: "/users",
|
|
216
218
|
onDelete: handleBack
|
|
217
|
-
}, null, 8, ["modelValue"])]),
|
|
219
|
+
}, null, 8, ["modelValue"]), errorMessage.value ? (openBlock(), createElementBlock("p", _hoisted_1, toDisplayString(errorMessage.value), 1)) : createCommentVNode("", true)]),
|
|
218
220
|
_: 1
|
|
219
221
|
}, 8, ["disabled"]);
|
|
220
222
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPaletteRequest.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteRequest.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Request Component\n *\n * Provides a form for creating a new API request (operation) in a document.\n * Users can specify the request path, HTTP method, document (collection),\n * and optionally assign it to a tag.\n *\n * Validates that no operation with the same path and method already exists\n * in the selected document to prevent duplicates.\n *\n * @example\n * <CommandPaletteRequest\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n */\nexport default {\n name: 'CommandPaletteRequest',\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 {\n HTTP_METHODS,\n type HttpMethod,\n} from '@scalar/helpers/http/http-methods'\nimport type { WorkspaceStore } 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 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, tagId } = 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 /** Preselected document id to create the request in */\n documentName?: string\n /** Preselected tag id to add the request to (optional) */\n tagId?: string\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the request 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/** HTTP method option type for selectors */\ntype MethodOption = {\n id: string\n label: string\n method: HttpMethod\n}\n\n/** Tag option type for selectors */\ntype TagOption = {\n id: string\n label: string\n}\n\nconst router = useRouter()\n\nconst requestPath = ref('/')\nconst requestPathTrimmed = computed(() => requestPath.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\n/** Available HTTP methods for the dropdown (GET, POST, PUT, etc.) */\nconst availableMethods: MethodOption[] = HTTP_METHODS.map((method) => ({\n id: method,\n label: method.toUpperCase(),\n method,\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\nconst selectedMethod = ref<MethodOption | undefined>(\n availableMethods.find((method) => method.method === 'get'),\n)\n\n/**\n * All available tags for the selected document.\n * Includes a \"No Tag\" option for operations without a tag assignment.\n */\nconst availableTags = computed<TagOption[]>(() => {\n if (!selectedDocument.value) {\n return []\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n if (!document) {\n return []\n }\n\n return [\n { id: '', label: 'No Tag' },\n ...(document.tags?.map((tag) => ({\n id: tag.name,\n label: tag.name,\n })) ?? []),\n ]\n})\n\nconst selectedTag = ref<TagOption | undefined>(\n tagId ? availableTags.value.find((tag) => tag.id === tagId) : undefined,\n)\n\n// Reset the selected tag to the \"No Tag\" option when the document changes\nwatch(selectedDocument, () => {\n selectedTag.value = availableTags.value.find((tag) => tag.id === '')\n})\n\n/**\n * Check if an operation with the same path and method already exists.\n * Used to prevent creating duplicate operations.\n */\nconst operationExists = computed<boolean>(() => {\n if (\n !selectedDocument.value ||\n !selectedMethod.value ||\n !requestPathTrimmed.value\n ) {\n return false\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n\n /** Ensure path starts with '/' for consistent lookup */\n const normalizedPath = requestPathTrimmed.value.startsWith('/')\n ? requestPathTrimmed.value\n : `/${requestPathTrimmed.value}`\n\n return !!document?.paths?.[normalizedPath]?.[selectedMethod.value.method]\n})\n\n/**\n * Check if the form should be disabled.\n * Disabled when any required field is missing or operation already exists.\n */\nconst isDisabled = computed<boolean>(() => {\n if (\n !requestPathTrimmed.value ||\n !selectedDocument.value ||\n !selectedMethod.value\n ) {\n return true\n }\n\n /** Prevent creating duplicate operations */\n if (operationExists.value) {\n return true\n }\n\n return false\n})\n\n/** Handle HTTP method selection from dropdown */\nconst handleSelectMethod = (method: MethodOption | undefined): void => {\n if (method) {\n selectedMethod.value = method\n }\n}\n\n/** Handle tag selection from dropdown */\nconst handleSelectTag = (tag: TagOption | undefined): void => {\n if (tag) {\n selectedTag.value = tag\n }\n}\n\n/**\n * Create the request and close the command palette.\n * Emits an event to create a new operation with the specified details.\n */\nconst handleSubmit = (): void => {\n if (isDisabled.value || !selectedDocument.value || !selectedMethod.value) {\n return\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n\n if (!document) {\n return\n }\n\n eventBus.emit('operation:create:operation', {\n documentName: selectedDocument.value.id,\n path: requestPathTrimmed.value,\n method: selectedMethod.value.method,\n operation: {\n tags: selectedTag.value?.id ? [selectedTag.value.id] : undefined,\n },\n callback: (success) => {\n if (success) {\n /** Build the sidebar */\n workspaceStore.buildSidebar(selectedDocument.value?.id ?? '')\n\n const path = requestPathTrimmed.value.startsWith('/')\n ? requestPathTrimmed.value\n : `/${requestPathTrimmed.value}`\n\n /** Navigate to the example */\n router.push({\n name: 'example',\n params: {\n documentSlug: selectedDocument.value?.id,\n pathEncoded: encodeURIComponent(path),\n method: selectedMethod.value?.method,\n exampleName: 'default',\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=\"handleSubmit\">\n <!-- Request path input -->\n <CommandActionInput\n v-model=\"requestPath\"\n label=\"Request Path\"\n placeholder=\"/users\"\n @delete=\"handleBack\" />\n\n <!-- Selectors for document, method, and tag -->\n <template #options>\n <div 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 <!-- HTTP method selector (GET, POST, PUT, etc.) -->\n <ScalarDropdown\n placement=\"bottom\"\n resize>\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-[100px] min-w-[100px] justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <div class=\"flex items-center gap-2\">\n <HttpMethodBadge\n v-if=\"selectedMethod\"\n :method=\"selectedMethod.method\" />\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </div>\n </ScalarButton>\n\n <!-- Dropdown list of all HTTP methods -->\n <template #items>\n <div class=\"custom-scroll max-h-40\">\n <ScalarDropdownItem\n v-for=\"method in availableMethods\"\n :key=\"method.id\"\n class=\"flex h-7 w-full items-center justify-center px-1\"\n @click=\"handleSelectMethod(method)\">\n <HttpMethodBadge :method=\"method.method\" />\n </ScalarDropdownItem>\n </div>\n </template>\n </ScalarDropdown>\n\n <!-- Tag selector (optional) for organizing operations -->\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=\"!availableTags.length\"\n variant=\"outlined\">\n <span :class=\"selectedTag ? 'text-c-1 truncate' : 'text-c-3'\">\n {{ selectedTag ? selectedTag.label : 'Select Tag (Optional)' }}\n </span>\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n\n <!-- Dropdown list of available tags -->\n <template #items>\n <div class=\"custom-scroll max-h-40\">\n <ScalarDropdownItem\n v-for=\"tag in availableTags\"\n :key=\"tag.id\"\n class=\"flex h-7 w-full items-center px-1\"\n @click=\"handleSelectTag(tag)\">\n <span class=\"truncate\">{{ tag.label }}</span>\n </ScalarDropdownItem>\n </div>\n </template>\n </ScalarDropdown>\n </div>\n </template>\n\n <template #submit>Create Request</template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommandPaletteRequest.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteRequest.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Request Component\n *\n * Provides a form for creating a new API request (operation) in a document.\n * Users can specify the request path, HTTP method, document (collection),\n * and optionally assign it to a tag.\n *\n * Validates that no operation with the same path and method already exists\n * in the selected document to prevent duplicates.\n *\n * @example\n * <CommandPaletteRequest\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n */\nexport default {\n name: 'CommandPaletteRequest',\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 {\n HTTP_METHODS,\n type HttpMethod,\n} from '@scalar/helpers/http/http-methods'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\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, tagId } = 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 /** Preselected document id to create the request in */\n documentName?: string\n /** Preselected tag id to add the request to (optional) */\n tagId?: string\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the request 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/** HTTP method option type for selectors */\ntype MethodOption = {\n id: string\n label: string\n method: HttpMethod\n}\n\n/** Tag option type for selectors */\ntype TagOption = {\n id: string\n label: string\n}\n\nconst requestPath = ref('/')\nconst requestPathTrimmed = computed(() => requestPath.value.trim())\n\n/** Ensure path starts with '/' for consistent lookup */\nconst normalizedRequestPath = computed<string>(() =>\n requestPathTrimmed.value.startsWith('/')\n ? requestPathTrimmed.value\n : `/${requestPathTrimmed.value}`,\n)\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\n/** Available HTTP methods for the dropdown (GET, POST, PUT, etc.) */\nconst availableMethods: MethodOption[] = HTTP_METHODS.map((method) => ({\n id: method,\n label: method.toUpperCase(),\n method,\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\nconst selectedMethod = ref<MethodOption | undefined>(\n availableMethods.find((method) => method.method === 'get'),\n)\n\n/**\n * All available tags for the selected document.\n * Includes a \"No Tag\" option for operations without a tag assignment.\n */\nconst availableTags = computed<TagOption[]>(() => {\n if (!selectedDocument.value) {\n return []\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n if (!document) {\n return []\n }\n\n return [\n { id: '', label: 'No Tag' },\n ...(document.tags?.map((tag) => ({\n id: tag.name,\n label: tag.name,\n })) ?? []),\n ]\n})\n\nconst selectedTag = ref<TagOption | undefined>(\n tagId ? availableTags.value.find((tag) => tag.id === tagId) : undefined,\n)\n\n// Reset the selected tag to the \"No Tag\" option when the document changes\nwatch(selectedDocument, () => {\n selectedTag.value = availableTags.value.find((tag) => tag.id === '')\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 (\n !requestPathTrimmed.value ||\n !selectedDocument.value ||\n !selectedMethod.value\n ) {\n return null\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n const method = selectedMethod.value.method\n\n if (document?.paths?.[normalizedRequestPath.value]?.[method]) {\n return `A ${method.toUpperCase()} operation at \"${normalizedRequestPath.value}\" already exists in \"${selectedDocument.value.label}\". Try a different path or method.`\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` explains the duplicate case so the user knows how\n * to recover instead of facing a silently disabled button.\n */\nconst isDisabled = computed<boolean>(\n () =>\n !requestPathTrimmed.value ||\n !selectedDocument.value ||\n !selectedMethod.value ||\n errorMessage.value !== null,\n)\n\n/** Handle HTTP method selection from dropdown */\nconst handleSelectMethod = (method: MethodOption | undefined): void => {\n if (method) {\n selectedMethod.value = method\n }\n}\n\n/** Handle tag selection from dropdown */\nconst handleSelectTag = (tag: TagOption | undefined): void => {\n if (tag) {\n selectedTag.value = tag\n }\n}\n\n/**\n * Create the request and close the command palette.\n * Emits an event to create a new operation with the specified details.\n */\nconst handleSubmit = (): void => {\n if (isDisabled.value || !selectedDocument.value || !selectedMethod.value) {\n return\n }\n\n const document = workspaceStore.workspace.documents[selectedDocument.value.id]\n\n if (!document) {\n return\n }\n\n eventBus.emit('operation:create:operation', {\n documentName: selectedDocument.value.id,\n path: requestPathTrimmed.value,\n method: selectedMethod.value.method,\n operation: {\n tags: selectedTag.value?.id ? [selectedTag.value.id] : undefined,\n },\n callback: (success) => {\n if (!success) {\n return\n }\n\n /** Build the sidebar */\n workspaceStore.buildSidebar(selectedDocument.value?.id ?? '')\n\n /** Navigate to the example via the event bus rather than the router */\n eventBus.emit('ui:navigate', {\n page: 'example',\n documentSlug: selectedDocument.value?.id,\n path: normalizedRequestPath.value,\n method: selectedMethod.value?.method ?? 'get',\n exampleName: 'default',\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=\"handleSubmit\">\n <!-- Request path input -->\n <CommandActionInput\n v-model=\"requestPath\"\n label=\"Request Path\"\n placeholder=\"/users\"\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-request-error\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <!-- Selectors for document, method, and tag -->\n <template #options>\n <div 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 <!-- HTTP method selector (GET, POST, PUT, etc.) -->\n <ScalarDropdown\n placement=\"bottom\"\n resize>\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-[100px] min-w-[100px] justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <div class=\"flex items-center gap-2\">\n <HttpMethodBadge\n v-if=\"selectedMethod\"\n :method=\"selectedMethod.method\" />\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </div>\n </ScalarButton>\n\n <!-- Dropdown list of all HTTP methods -->\n <template #items>\n <div class=\"custom-scroll max-h-40\">\n <ScalarDropdownItem\n v-for=\"method in availableMethods\"\n :key=\"method.id\"\n class=\"flex h-7 w-full items-center justify-center px-1\"\n @click=\"handleSelectMethod(method)\">\n <HttpMethodBadge :method=\"method.method\" />\n </ScalarDropdownItem>\n </div>\n </template>\n </ScalarDropdown>\n\n <!-- Tag selector (optional) for organizing operations -->\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=\"!availableTags.length\"\n variant=\"outlined\">\n <span :class=\"selectedTag ? 'text-c-1 truncate' : 'text-c-3'\">\n {{ selectedTag ? selectedTag.label : 'Select Tag (Optional)' }}\n </span>\n <ScalarIcon\n class=\"text-c-3\"\n icon=\"ChevronDown\"\n size=\"md\" />\n </ScalarButton>\n\n <!-- Dropdown list of available tags -->\n <template #items>\n <div class=\"custom-scroll max-h-40\">\n <ScalarDropdownItem\n v-for=\"tag in availableTags\"\n :key=\"tag.id\"\n class=\"flex h-7 w-full items-center px-1\"\n @click=\"handleSelectTag(tag)\">\n <span class=\"truncate\">{{ tag.label }}</span>\n </ScalarDropdownItem>\n </div>\n </template>\n </ScalarDropdown>\n </div>\n </template>\n\n <template #submit>Create Request</template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPaletteTag.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteTag.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CommandPaletteTag.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteTag.vue"],"names":[],"mappings":"AAwPA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAA;AAO9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,QAAA,MAAM,YAAY;IAEhB,2DAA2D;oBAC3C,cAAc;IAC9B,iDAAiD;cACvC,iBAAiB;IAC3B,mDAAmD;mBACpC,MAAM;IACrB,8EAA8E;UACxE,YAAY;;;;;IAPlB,2DAA2D;oBAC3C,cAAc;IAC9B,iDAAiD;cACvC,iBAAiB;IAC3B,mDAAmD;mBACpC,MAAM;IACrB,8EAA8E;UACxE,YAAY;;;;kFAuUhB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPaletteTag.vue.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteTag.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Tag Component\n *\n * Provides a form for creating or editing a tag in a document (collection).\n * Tags are used to organize and group related API operations.\n *\n * When `tag` is provided, the component enters edit mode where:\n * - The name input is pre-filled with the current tag name\n * - The collection selector is hidden (tag already belongs to a document)\n * - A cancel button is shown to close the modal without saving\n * - Back navigation is disabled (cannot go back with backspace)\n * - Submitting emits an 'edit' event instead of creating a new tag\n *\n * In create mode, validates that the tag name does not already exist\n * in the selected document to prevent duplicates.\n *\n * @example\n * // Create mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n *\n * // Edit mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * :tag=\"tag\"\n * @close=\"handleClose\"\n * @edit=\"handleEdit\"\n * />\n */\nexport default {\n name: 'CommandPaletteTag',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton, ScalarIcon, ScalarListbox } from '@scalar/components'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { TraversedTag } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, ref } from 'vue'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\n\nconst { workspaceStore, eventBus, documentName, tag } = defineProps<{\n /** The workspace store for accessing documents and tags */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting tag creation events */\n eventBus: WorkspaceEventBus\n /** Preselected document id to create the tag in */\n documentName?: string\n /** When provided, the component enters edit mode with this name pre-filled */\n tag?: TraversedTag\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the tag is created or edited 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\nconst isEditMode = computed(() => tag !== undefined)\n\nconst name = ref(tag?.name ?? '')\nconst nameTrimmed = computed(() => name.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 *
|
|
1
|
+
{"version":3,"file":"CommandPaletteTag.vue.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteTag.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Tag Component\n *\n * Provides a form for creating or editing a tag in a document (collection).\n * Tags are used to organize and group related API operations.\n *\n * When `tag` is provided, the component enters edit mode where:\n * - The name input is pre-filled with the current tag name\n * - The collection selector is hidden (tag already belongs to a document)\n * - A cancel button is shown to close the modal without saving\n * - Back navigation is disabled (cannot go back with backspace)\n * - Submitting emits an 'edit' event instead of creating a new tag\n *\n * In create mode, validates that the tag name does not already exist\n * in the selected document to prevent duplicates.\n *\n * @example\n * // Create mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n *\n * // Edit mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * :tag=\"tag\"\n * @close=\"handleClose\"\n * @edit=\"handleEdit\"\n * />\n */\nexport default {\n name: 'CommandPaletteTag',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton, ScalarIcon, ScalarListbox } from '@scalar/components'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { TraversedTag } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, ref, type ComputedRef } from 'vue'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\n\nconst { workspaceStore, eventBus, documentName, tag } = defineProps<{\n /** The workspace store for accessing documents and tags */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting tag creation events */\n eventBus: WorkspaceEventBus\n /** Preselected document id to create the tag in */\n documentName?: string\n /** When provided, the component enters edit mode with this name pre-filled */\n tag?: TraversedTag\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the tag is created or edited 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\nconst isEditMode = computed(() => tag !== undefined)\n\nconst name = ref(tag?.name ?? '')\nconst nameTrimmed = computed(() => name.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 * 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 *\n * In edit mode, an unchanged name is treated as a no-op (no error shown,\n * but submit stays disabled) so opening the modal does not greet the user\n * with a confusing message about their current name.\n */\nconst errorMessage: ComputedRef<string | null> = computed(() => {\n if (!nameTrimmed.value) {\n return null\n }\n\n const document =\n workspaceStore.workspace.documents[selectedDocument.value?.id ?? '']\n\n if (!selectedDocument.value || !document) {\n return null\n }\n\n // Unchanged name in edit mode is a silent no-op rather than an error\n if (isEditMode.value && nameTrimmed.value === tag?.name) {\n return null\n }\n\n if (document.tags?.some((existing) => existing.name === nameTrimmed.value)) {\n return `A tag named \"${nameTrimmed.value}\" already exists in \"${selectedDocument.value.label}\". 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 a duplicate tag exists. The inline `errorMessage` makes\n * the duplicate case explicit instead of leaving the user staring at a\n * silently disabled button.\n */\nconst isDisabled = computed<boolean>(() => {\n const document =\n workspaceStore.workspace.documents[selectedDocument.value?.id ?? '']\n if (!nameTrimmed.value || !selectedDocument.value || !document) {\n return true\n }\n\n if (isEditMode.value && nameTrimmed.value === tag?.name) {\n return true\n }\n\n return errorMessage.value !== null\n})\n\n/**\n * Handle form submission.\n * In edit mode, emits the new name. In create mode, creates the tag via the event bus.\n */\nconst handleSubmit = (): void => {\n if (isDisabled.value || !selectedDocument.value) {\n return\n }\n\n // In edit mode, emit the new name and close\n if (isEditMode.value && tag) {\n eventBus.emit(\n 'tag:edit:tag',\n {\n tag,\n documentName: selectedDocument.value.id,\n newName: nameTrimmed.value,\n },\n { skipUnpackProxy: true },\n )\n emit('close')\n return\n }\n\n eventBus.emit('tag:create:tag', {\n name: nameTrimmed.value,\n documentName: selectedDocument.value.id,\n })\n\n emit('close')\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n // Do not allow back navigation in edit mode\n if (isEditMode.value) {\n return\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 <!-- Tag name input -->\n <CommandActionInput\n v-model=\"name\"\n label=\"Tag Name\"\n placeholder=\"Tag 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-tag-error\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <!-- Collection selector (hidden in edit mode) -->\n <template #options>\n <ScalarListbox\n v-if=\"!isEditMode\"\n v-model=\"selectedDocument\"\n :options=\"availableDocuments\">\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-fit justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <span :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\n <!-- Cancel button in edit mode -->\n <ScalarButton\n v-if=\"isEditMode\"\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 Tag' }}</template>\n </CommandActionForm>\n</template>\n"],"mappings":""}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import CommandActionForm_default from "./CommandActionForm.vue.js";
|
|
2
2
|
import CommandActionInput_default from "./CommandActionInput.vue.js";
|
|
3
|
-
import { computed, createBlock, createCommentVNode, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, toDisplayString, unref, withCtx } from "vue";
|
|
3
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, toDisplayString, unref, withCtx } from "vue";
|
|
4
4
|
import { ScalarButton, ScalarIcon, ScalarListbox } from "@scalar/components";
|
|
5
|
+
//#region src/v2/features/command-palette/components/CommandPaletteTag.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
+
var _hoisted_1 = {
|
|
7
|
+
key: 0,
|
|
8
|
+
class: "text-red px-2 pb-1 text-xs",
|
|
9
|
+
"data-testid": "command-palette-tag-error",
|
|
10
|
+
role: "alert"
|
|
11
|
+
};
|
|
5
12
|
var CommandPaletteTag_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
6
13
|
name: "CommandPaletteTag",
|
|
7
14
|
props: {
|
|
@@ -23,27 +30,36 @@ var CommandPaletteTag_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
23
30
|
})));
|
|
24
31
|
const selectedDocument = ref(__props.documentName ? availableDocuments.value.find((document) => document.id === __props.documentName) : availableDocuments.value[0] ?? void 0);
|
|
25
32
|
/**
|
|
26
|
-
*
|
|
33
|
+
* Validation message surfaced under the input.
|
|
27
34
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
35
|
+
* Resolves to `null` when the form is valid; otherwise to a human-readable
|
|
36
|
+
* reason the user can act on. Empty input is the default state so we keep
|
|
37
|
+
* the field free of error styling — `isDisabled` still blocks submission
|
|
38
|
+
* there, matching the {@link CommandPaletteOpenApiDocument} pattern.
|
|
32
39
|
*
|
|
33
|
-
* In
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
* In edit mode, an unchanged name is treated as a no-op (no error shown,
|
|
41
|
+
* but submit stays disabled) so opening the modal does not greet the user
|
|
42
|
+
* with a confusing message about their current name.
|
|
43
|
+
*/
|
|
44
|
+
const errorMessage = computed(() => {
|
|
45
|
+
if (!nameTrimmed.value) return null;
|
|
46
|
+
const document = __props.workspaceStore.workspace.documents[selectedDocument.value?.id ?? ""];
|
|
47
|
+
if (!selectedDocument.value || !document) return null;
|
|
48
|
+
if (isEditMode.value && nameTrimmed.value === __props.tag?.name) return null;
|
|
49
|
+
if (document.tags?.some((existing) => existing.name === nameTrimmed.value)) return `A tag named "${nameTrimmed.value}" already exists in "${selectedDocument.value.label}". Try a different name.`;
|
|
50
|
+
return null;
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Submit is blocked while required fields are missing, the name is unchanged
|
|
54
|
+
* in edit mode, or a duplicate tag exists. The inline `errorMessage` makes
|
|
55
|
+
* the duplicate case explicit instead of leaving the user staring at a
|
|
56
|
+
* silently disabled button.
|
|
38
57
|
*/
|
|
39
58
|
const isDisabled = computed(() => {
|
|
40
59
|
const document = __props.workspaceStore.workspace.documents[selectedDocument.value?.id ?? ""];
|
|
41
60
|
if (!nameTrimmed.value || !selectedDocument.value || !document) return true;
|
|
42
|
-
if (isEditMode.value)
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
if (document.tags?.some((tag) => tag.name === nameTrimmed.value)) return true;
|
|
46
|
-
return false;
|
|
61
|
+
if (isEditMode.value && nameTrimmed.value === __props.tag?.name) return true;
|
|
62
|
+
return errorMessage.value !== null;
|
|
47
63
|
});
|
|
48
64
|
/**
|
|
49
65
|
* Handle form submission.
|
|
@@ -114,7 +130,7 @@ var CommandPaletteTag_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
114
130
|
label: "Tag Name",
|
|
115
131
|
placeholder: "Tag Name",
|
|
116
132
|
onDelete: handleBack
|
|
117
|
-
}, null, 8, ["modelValue"])]),
|
|
133
|
+
}, null, 8, ["modelValue"]), errorMessage.value ? (openBlock(), createElementBlock("p", _hoisted_1, toDisplayString(errorMessage.value), 1)) : createCommentVNode("", true)]),
|
|
118
134
|
_: 1
|
|
119
135
|
}, 8, ["disabled"]);
|
|
120
136
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPaletteTag.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteTag.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Tag Component\n *\n * Provides a form for creating or editing a tag in a document (collection).\n * Tags are used to organize and group related API operations.\n *\n * When `tag` is provided, the component enters edit mode where:\n * - The name input is pre-filled with the current tag name\n * - The collection selector is hidden (tag already belongs to a document)\n * - A cancel button is shown to close the modal without saving\n * - Back navigation is disabled (cannot go back with backspace)\n * - Submitting emits an 'edit' event instead of creating a new tag\n *\n * In create mode, validates that the tag name does not already exist\n * in the selected document to prevent duplicates.\n *\n * @example\n * // Create mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n *\n * // Edit mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * :tag=\"tag\"\n * @close=\"handleClose\"\n * @edit=\"handleEdit\"\n * />\n */\nexport default {\n name: 'CommandPaletteTag',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton, ScalarIcon, ScalarListbox } from '@scalar/components'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { TraversedTag } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, ref } from 'vue'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\n\nconst { workspaceStore, eventBus, documentName, tag } = defineProps<{\n /** The workspace store for accessing documents and tags */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting tag creation events */\n eventBus: WorkspaceEventBus\n /** Preselected document id to create the tag in */\n documentName?: string\n /** When provided, the component enters edit mode with this name pre-filled */\n tag?: TraversedTag\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the tag is created or edited 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\nconst isEditMode = computed(() => tag !== undefined)\n\nconst name = ref(tag?.name ?? '')\nconst nameTrimmed = computed(() => name.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 *
|
|
1
|
+
{"version":3,"file":"CommandPaletteTag.vue.script.js","names":[],"sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteTag.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Command Palette Tag Component\n *\n * Provides a form for creating or editing a tag in a document (collection).\n * Tags are used to organize and group related API operations.\n *\n * When `tag` is provided, the component enters edit mode where:\n * - The name input is pre-filled with the current tag name\n * - The collection selector is hidden (tag already belongs to a document)\n * - A cancel button is shown to close the modal without saving\n * - Back navigation is disabled (cannot go back with backspace)\n * - Submitting emits an 'edit' event instead of creating a new tag\n *\n * In create mode, validates that the tag name does not already exist\n * in the selected document to prevent duplicates.\n *\n * @example\n * // Create mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * @close=\"handleClose\"\n * @back=\"handleBack\"\n * />\n *\n * // Edit mode\n * <CommandPaletteTag\n * :workspaceStore=\"workspaceStore\"\n * :eventBus=\"eventBus\"\n * :tag=\"tag\"\n * @close=\"handleClose\"\n * @edit=\"handleEdit\"\n * />\n */\nexport default {\n name: 'CommandPaletteTag',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton, ScalarIcon, ScalarListbox } from '@scalar/components'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { TraversedTag } from '@scalar/workspace-store/schemas/navigation'\nimport { computed, ref, type ComputedRef } from 'vue'\n\nimport CommandActionForm from './CommandActionForm.vue'\nimport CommandActionInput from './CommandActionInput.vue'\n\nconst { workspaceStore, eventBus, documentName, tag } = defineProps<{\n /** The workspace store for accessing documents and tags */\n workspaceStore: WorkspaceStore\n /** Event bus for emitting tag creation events */\n eventBus: WorkspaceEventBus\n /** Preselected document id to create the tag in */\n documentName?: string\n /** When provided, the component enters edit mode with this name pre-filled */\n tag?: TraversedTag\n}>()\n\nconst emit = defineEmits<{\n /** Emitted when the tag is created or edited 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\nconst isEditMode = computed(() => tag !== undefined)\n\nconst name = ref(tag?.name ?? '')\nconst nameTrimmed = computed(() => name.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 * 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 *\n * In edit mode, an unchanged name is treated as a no-op (no error shown,\n * but submit stays disabled) so opening the modal does not greet the user\n * with a confusing message about their current name.\n */\nconst errorMessage: ComputedRef<string | null> = computed(() => {\n if (!nameTrimmed.value) {\n return null\n }\n\n const document =\n workspaceStore.workspace.documents[selectedDocument.value?.id ?? '']\n\n if (!selectedDocument.value || !document) {\n return null\n }\n\n // Unchanged name in edit mode is a silent no-op rather than an error\n if (isEditMode.value && nameTrimmed.value === tag?.name) {\n return null\n }\n\n if (document.tags?.some((existing) => existing.name === nameTrimmed.value)) {\n return `A tag named \"${nameTrimmed.value}\" already exists in \"${selectedDocument.value.label}\". 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 a duplicate tag exists. The inline `errorMessage` makes\n * the duplicate case explicit instead of leaving the user staring at a\n * silently disabled button.\n */\nconst isDisabled = computed<boolean>(() => {\n const document =\n workspaceStore.workspace.documents[selectedDocument.value?.id ?? '']\n if (!nameTrimmed.value || !selectedDocument.value || !document) {\n return true\n }\n\n if (isEditMode.value && nameTrimmed.value === tag?.name) {\n return true\n }\n\n return errorMessage.value !== null\n})\n\n/**\n * Handle form submission.\n * In edit mode, emits the new name. In create mode, creates the tag via the event bus.\n */\nconst handleSubmit = (): void => {\n if (isDisabled.value || !selectedDocument.value) {\n return\n }\n\n // In edit mode, emit the new name and close\n if (isEditMode.value && tag) {\n eventBus.emit(\n 'tag:edit:tag',\n {\n tag,\n documentName: selectedDocument.value.id,\n newName: nameTrimmed.value,\n },\n { skipUnpackProxy: true },\n )\n emit('close')\n return\n }\n\n eventBus.emit('tag:create:tag', {\n name: nameTrimmed.value,\n documentName: selectedDocument.value.id,\n })\n\n emit('close')\n}\n\n/** Handle back navigation when user presses backspace on empty input */\nconst handleBack = (event: KeyboardEvent): void => {\n // Do not allow back navigation in edit mode\n if (isEditMode.value) {\n return\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 <!-- Tag name input -->\n <CommandActionInput\n v-model=\"name\"\n label=\"Tag Name\"\n placeholder=\"Tag 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-tag-error\"\n role=\"alert\">\n {{ errorMessage }}\n </p>\n\n <!-- Collection selector (hidden in edit mode) -->\n <template #options>\n <ScalarListbox\n v-if=\"!isEditMode\"\n v-model=\"selectedDocument\"\n :options=\"availableDocuments\">\n <ScalarButton\n class=\"hover:bg-b-2 max-h-8 w-fit justify-between gap-1 p-2 text-xs\"\n variant=\"outlined\">\n <span :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\n <!-- Cancel button in edit mode -->\n <ScalarButton\n v-if=\"isEditMode\"\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 Tag' }}</template>\n </CommandActionForm>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-document-from-source.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/helpers/load-document-from-source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAIpE,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;CAC7B,CAAA;AAiCD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,GACjC,gBAAgB,cAAc,EAC9B,iBAAiB,eAAe,EAChC,MAAM,MAAM,EACZ,WAAW,OAAO,KACjB,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"load-document-from-source.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/helpers/load-document-from-source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAIpE,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;CAC7B,CAAA;AAiCD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,GACjC,gBAAgB,cAAc,EAC9B,iBAAiB,eAAe,EAChC,MAAM,MAAM,EACZ,WAAW,OAAO,KACjB,OAAO,CAAC,OAAO,CAwDjB,CAAA"}
|
|
@@ -40,10 +40,11 @@ var readPostmanCollection = () => {
|
|
|
40
40
|
*/
|
|
41
41
|
var loadDocumentFromSource = async (workspaceStore, importEventData, name, watchMode) => {
|
|
42
42
|
const { source, type } = importEventData;
|
|
43
|
-
|
|
43
|
+
const normalizedSource = type === "url" ? source.trim() : source;
|
|
44
|
+
if (!normalizedSource) return false;
|
|
44
45
|
if (type === "url") return await workspaceStore.addDocument({
|
|
45
46
|
name,
|
|
46
|
-
url:
|
|
47
|
+
url: normalizedSource,
|
|
47
48
|
meta: { "x-scalar-watch-mode": watchMode }
|
|
48
49
|
});
|
|
49
50
|
if (type === "file") return await workspaceStore.addDocument({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-document-from-source.js","names":[],"sources":["../../../../../src/v2/features/command-palette/helpers/load-document-from-source.ts"],"sourcesContent":["import { isObject } from '@scalar/helpers/object/is-object'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { parseJson, parseYaml } from '@scalar/json-magic/bundle/plugins/browser'\nimport { isPostmanCollection } from '@scalar/postman-to-openapi'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\n\nimport { getOpenApiFromPostman } from '@/v2/features/command-palette/helpers/get-openapi-from-postman'\n\nexport type ImportEventData = {\n source: string\n type: 'url' | 'file' | 'raw'\n}\n\n/**\n * Loader plugin to detect and convert Postman collections into OpenAPI documents\n */\nconst readPostmanCollection = (): LoaderPlugin => {\n return {\n type: 'loader',\n validate: isPostmanCollection,\n exec: (source: string) => {\n try {\n const document = getOpenApiFromPostman(source)\n\n if (document) {\n return Promise.resolve({\n ok: true,\n data: document,\n raw: source,\n })\n }\n\n return Promise.resolve({\n ok: false,\n })\n } catch {\n return Promise.resolve({\n ok: false,\n })\n }\n },\n }\n}\n\n/**\n * Attempts to add a document to the workspace from a given source, which may be a URL or raw content.\n *\n * - If the source is a URL, adds the document by its URL and includes a watch mode flag as metadata.\n * - If the source is a Postman collection, transforms it to OpenAPI and adds it as a document.\n * - For other raw sources (such as pasted JSON or YAML), attempts to normalize and add them as a document.\n *\n * @param workspaceStore The workspace store where the document will be added.\n * @param source The document source (URL, Postman Collection, JSON, or YAML string).\n * @param name The display name for the new document.\n * @param watchMode Whether to enable watch mode (applies only to URL sources).\n * @returns Promise resolving to true if the document was successfully added, or false if the operation failed.\n */\nexport const loadDocumentFromSource = async (\n workspaceStore: WorkspaceStore,\n importEventData: ImportEventData,\n name: string,\n watchMode: boolean,\n): Promise<boolean> => {\n const { source, type } = importEventData\n if (!
|
|
1
|
+
{"version":3,"file":"load-document-from-source.js","names":[],"sources":["../../../../../src/v2/features/command-palette/helpers/load-document-from-source.ts"],"sourcesContent":["import { isObject } from '@scalar/helpers/object/is-object'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { parseJson, parseYaml } from '@scalar/json-magic/bundle/plugins/browser'\nimport { isPostmanCollection } from '@scalar/postman-to-openapi'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\n\nimport { getOpenApiFromPostman } from '@/v2/features/command-palette/helpers/get-openapi-from-postman'\n\nexport type ImportEventData = {\n source: string\n type: 'url' | 'file' | 'raw'\n}\n\n/**\n * Loader plugin to detect and convert Postman collections into OpenAPI documents\n */\nconst readPostmanCollection = (): LoaderPlugin => {\n return {\n type: 'loader',\n validate: isPostmanCollection,\n exec: (source: string) => {\n try {\n const document = getOpenApiFromPostman(source)\n\n if (document) {\n return Promise.resolve({\n ok: true,\n data: document,\n raw: source,\n })\n }\n\n return Promise.resolve({\n ok: false,\n })\n } catch {\n return Promise.resolve({\n ok: false,\n })\n }\n },\n }\n}\n\n/**\n * Attempts to add a document to the workspace from a given source, which may be a URL or raw content.\n *\n * - If the source is a URL, adds the document by its URL and includes a watch mode flag as metadata.\n * - If the source is a Postman collection, transforms it to OpenAPI and adds it as a document.\n * - For other raw sources (such as pasted JSON or YAML), attempts to normalize and add them as a document.\n *\n * @param workspaceStore The workspace store where the document will be added.\n * @param source The document source (URL, Postman Collection, JSON, or YAML string).\n * @param name The display name for the new document.\n * @param watchMode Whether to enable watch mode (applies only to URL sources).\n * @returns Promise resolving to true if the document was successfully added, or false if the operation failed.\n */\nexport const loadDocumentFromSource = async (\n workspaceStore: WorkspaceStore,\n importEventData: ImportEventData,\n name: string,\n watchMode: boolean,\n): Promise<boolean> => {\n const { source, type } = importEventData\n const normalizedSource = type === 'url' ? source.trim() : source\n\n if (!normalizedSource) {\n // No source provided, do nothing.\n return false\n }\n\n // If the source is a URL, add it directly with watch mode metadata.\n if (type === 'url') {\n return await workspaceStore.addDocument({\n name,\n url: normalizedSource,\n meta: {\n 'x-scalar-watch-mode': watchMode,\n },\n })\n }\n\n if (type === 'file') {\n return await workspaceStore.addDocument({\n name,\n path: source,\n })\n }\n\n const loaders = [readPostmanCollection(), parseJson(), parseYaml()]\n const loader = loaders.find((l) => l.validate(source))\n\n // If no loader is found, return false\n if (!loader) {\n return false\n }\n\n // Execute the loader\n const result = await loader.exec(source)\n // If the loader failed, return false\n if (!result.ok) {\n return false\n }\n\n if (!isObject(result.data)) {\n return false\n }\n\n const addDocumentResult = await workspaceStore.addDocument({\n name,\n document: result.data,\n })\n\n if (!addDocumentResult) {\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;;AAgBA,IAAM,8BAA4C;AAChD,QAAO;EACL,MAAM;EACN,UAAU;EACV,OAAO,WAAmB;AACxB,OAAI;IACF,MAAM,WAAW,sBAAsB,OAAO;AAE9C,QAAI,SACF,QAAO,QAAQ,QAAQ;KACrB,IAAI;KACJ,MAAM;KACN,KAAK;KACN,CAAC;AAGJ,WAAO,QAAQ,QAAQ,EACrB,IAAI,OACL,CAAC;WACI;AACN,WAAO,QAAQ,QAAQ,EACrB,IAAI,OACL,CAAC;;;EAGP;;;;;;;;;;;;;;;AAgBH,IAAa,yBAAyB,OACpC,gBACA,iBACA,MACA,cACqB;CACrB,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,mBAAmB,SAAS,QAAQ,OAAO,MAAM,GAAG;AAE1D,KAAI,CAAC,iBAEH,QAAO;AAIT,KAAI,SAAS,MACX,QAAO,MAAM,eAAe,YAAY;EACtC;EACA,KAAK;EACL,MAAM,EACJ,uBAAuB,WACxB;EACF,CAAC;AAGJ,KAAI,SAAS,OACX,QAAO,MAAM,eAAe,YAAY;EACtC;EACA,MAAM;EACP,CAAC;CAIJ,MAAM,SADU;EAAC,uBAAuB;EAAE,WAAW;EAAE,WAAW;EAAC,CAC5C,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC;AAGtD,KAAI,CAAC,OACH,QAAO;CAIT,MAAM,SAAS,MAAM,OAAO,KAAK,OAAO;AAExC,KAAI,CAAC,OAAO,GACV,QAAO;AAGT,KAAI,CAAC,SAAS,OAAO,KAAK,CACxB,QAAO;AAQT,KAAI,CALsB,MAAM,eAAe,YAAY;EACzD;EACA,UAAU,OAAO;EAClB,CAAC,CAGA,QAAO;AAGT,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-three-way-merge-editor.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/editor/hooks/use-three-way-merge-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAG3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AACtC,OAAO,EAAE,KAAK,gBAAgB,EAAsD,MAAM,KAAK,CAAA;AAY/F,KAAK,6BAA6B,GAAG;IACnC,uCAAuC;IACvC,YAAY,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,yDAAyD;IACzD,gBAAgB,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC3D,iCAAiC;IACjC,SAAS,EAAE,gBAAgB,CAAC,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC,CAAA;IAClE,iDAAiD;IACjD,cAAc,EAAE,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IACnE,8EAA8E;IAC9E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,qDAAqD;IACrD,KAAK,EAAE,WAAW,CAAA;IAClB,4CAA4C;IAC5C,MAAM,EAAE,WAAW,CAAA;IACnB,uDAAuD;IACvD,MAAM,EAAE,WAAW,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG;IAC9E,IAAI,EAAE,CAAC,UAAU,EAAE,6BAA6B,KAAK,IAAI,CAAA;IACzD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B,sBAAsB,EAAE,MAAM,IAAI,CAAA;CACnC,
|
|
1
|
+
{"version":3,"file":"use-three-way-merge-editor.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/editor/hooks/use-three-way-merge-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAG3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AACtC,OAAO,EAAE,KAAK,gBAAgB,EAAsD,MAAM,KAAK,CAAA;AAY/F,KAAK,6BAA6B,GAAG;IACnC,uCAAuC;IACvC,YAAY,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,yDAAyD;IACzD,gBAAgB,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC3D,iCAAiC;IACjC,SAAS,EAAE,gBAAgB,CAAC,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC,CAAA;IAClE,iDAAiD;IACjD,cAAc,EAAE,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IACnE,8EAA8E;IAC9E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,qDAAqD;IACrD,KAAK,EAAE,WAAW,CAAA;IAClB,4CAA4C;IAC5C,MAAM,EAAE,WAAW,CAAA;IACnB,uDAAuD;IACvD,MAAM,EAAE,WAAW,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG;IAC9E,IAAI,EAAE,CAAC,UAAU,EAAE,6BAA6B,KAAK,IAAI,CAAA;IACzD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B,sBAAsB,EAAE,MAAM,IAAI,CAAA;CACnC,CAoeA"}
|
|
@@ -4,8 +4,8 @@ import { ensureJsonPointerLinkSupport } from "../helpers/json/json-pointer-links
|
|
|
4
4
|
import { createJsonModel } from "../helpers/json/create-json-model.js";
|
|
5
5
|
import { computed, markRaw, ref, shallowRef, toValue, watch } from "vue";
|
|
6
6
|
import { apply } from "@scalar/json-magic/diff";
|
|
7
|
-
import * as monaco from "monaco-editor";
|
|
8
7
|
import { deepClone } from "@scalar/workspace-store/helpers/deep-clone";
|
|
8
|
+
import * as monaco from "monaco-editor";
|
|
9
9
|
//#region src/v2/features/editor/hooks/use-three-way-merge-editor.ts
|
|
10
10
|
/**
|
|
11
11
|
* Shared state and logic for a 3-way merge editor (base | local | remote → result).
|
|
@@ -69,8 +69,8 @@ function useThreeWayMergeEditor(options) {
|
|
|
69
69
|
let modelsToDispose = [];
|
|
70
70
|
const normalizeConflicts = computed(() => {
|
|
71
71
|
return toValue(conflicts).map((conflict) => {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
72
|
+
const remoteChanges = conflict[0];
|
|
73
|
+
const localChanges = conflict[1];
|
|
74
74
|
let smallestPath = localChanges[0].path;
|
|
75
75
|
for (const localChange of localChanges) if (localChange.path.length < smallestPath.length) smallestPath = localChange.path;
|
|
76
76
|
for (const remoteChange of remoteChanges) if (remoteChange.path.length < smallestPath.length) smallestPath = remoteChange.path;
|
|
@@ -88,8 +88,8 @@ function useThreeWayMergeEditor(options) {
|
|
|
88
88
|
};
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
|
-
const
|
|
92
|
-
const
|
|
91
|
+
const documentWithRemoteChanges = computed(() => apply(deepClone(toValue(resolvedDocument)), toValue(conflicts).flatMap((it) => it[0])));
|
|
92
|
+
const documentWithLocalChanges = computed(() => apply(deepClone(toValue(resolvedDocument)), toValue(conflicts).flatMap((it) => it[1])));
|
|
93
93
|
const conflictsLeft = computed(() => resolvedConflicts.value.filter((status) => status === "idle").length);
|
|
94
94
|
watch(normalizeConflicts, (nextConflicts) => {
|
|
95
95
|
resolvedConflicts.value = nextConflicts.map((_, index) => resolvedConflicts.value[index] ?? "idle");
|