@scalar/api-client 3.2.1 → 3.2.2
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 +8 -0
- package/dist/v2/blocks/operation-block/helpers/send-request.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/send-request.js +2 -2
- package/dist/v2/blocks/operation-block/helpers/send-request.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/app/app-state.d.ts +1 -1
- package/dist/v2/features/app/app-state.d.ts.map +1 -1
- package/dist/v2/features/app/app-state.js +9 -3
- package/dist/v2/features/app/app-state.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.d.ts +7 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.d.ts.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.js.map +1 -1
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js +29 -10
- package/dist/v2/features/command-palette/components/CommandPaletteImport.vue.script.js.map +1 -1
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @scalar/api-client
|
|
2
2
|
|
|
3
|
+
## 3.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#8952](https://github.com/scalar/scalar/pull/8952): Export shared Scalar custom header constants from `@scalar/helpers/http/scalar-headers` and consume them in request build/send flows.
|
|
8
|
+
- [#8950](https://github.com/scalar/scalar/pull/8950): fix: correctly handle custom file imports
|
|
9
|
+
- [#8951](https://github.com/scalar/scalar/pull/8951): fix: api client initial routing
|
|
10
|
+
|
|
3
11
|
## 3.2.1
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-request.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/operation-block/helpers/send-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAkB,MAAM,wCAAwC,CAAA;AAGnG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;
|
|
1
|
+
{"version":3,"file":"send-request.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/operation-block/helpers/send-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAkB,MAAM,wCAAwC,CAAA;AAGnG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAInE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAa7E,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG;IACzD,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,0BAA0B;IAC1B,MAAM,EAAE,UAAU,CAAA;IAClB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,CACE;IACE,wBAAwB;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE,qDAAqD;IACrD,MAAM,EAAE,2BAA2B,CAAC,UAAU,CAAC,CAAA;CAChD,CACJ,CAAA;AAKH,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG,OAAO,KAAK,CAAA;AAEtC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAU,yDAK/B;IACD,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,cAAc,CAAA;IAC9B,iEAAiE;IACjE,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;IACxB,uEAAuE;IACvE,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B,KAAG,OAAO,CACT,aAAa,CAAC;IACZ,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,QAAQ,CAAA;CAC3B,CAAC,CA0DH,CAAA"}
|
|
@@ -7,10 +7,10 @@ import { isElectron } from "@scalar/helpers/general/is-electron";
|
|
|
7
7
|
import { buildSafeBodyRequest } from "@scalar/helpers/http/can-method-have-body";
|
|
8
8
|
import { httpStatusCodes } from "@scalar/helpers/http/http-status-codes";
|
|
9
9
|
import { normalizeHeaders } from "@scalar/helpers/http/normalize-headers";
|
|
10
|
+
import { X_SCALAR_SET_COOKIE } from "@scalar/helpers/http/scalar-headers";
|
|
10
11
|
import * as cookie from "cookie";
|
|
11
12
|
import { parseSetCookie } from "set-cookie-parser";
|
|
12
13
|
//#region src/v2/blocks/operation-block/helpers/send-request.ts
|
|
13
|
-
var CUSTOM_COOKIE_HEADER = "x-scalar-set-cookie";
|
|
14
14
|
/** HTTP status codes that should not include a response body */
|
|
15
15
|
var NO_BODY_STATUS_CODES = [
|
|
16
16
|
204,
|
|
@@ -86,7 +86,7 @@ var sendRequest = async ({ isUsingProxy, requestPayload, plugins = [], customFet
|
|
|
86
86
|
* The @ts-expect-error is present due to a type mismatch between the 'cookie' parsing and serialization libraries.
|
|
87
87
|
*/
|
|
88
88
|
var getCustomCookie = (response) => {
|
|
89
|
-
const result = parseSetCookie(response.headers.get(
|
|
89
|
+
const result = parseSetCookie(response.headers.get(X_SCALAR_SET_COOKIE) ?? "").map((c) => cookie.serialize(c.name, c.value, {
|
|
90
90
|
...c,
|
|
91
91
|
sameSite: c.sameSite,
|
|
92
92
|
encode: (str) => str
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-request.js","names":[],"sources":["../../../../../src/v2/blocks/operation-block/helpers/send-request.ts"],"sourcesContent":["import { ERRORS, type ErrorResponse, normalizeError } from '@scalar/helpers/errors/normalize-error'\nimport { isElectron } from '@scalar/helpers/general/is-electron'\nimport { buildSafeBodyRequest } from '@scalar/helpers/http/can-method-have-body'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\nimport { normalizeHeaders } from '@scalar/helpers/http/normalize-headers'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { RequestPayload } from '@scalar/workspace-store/request-example'\nimport * as cookie from 'cookie'\nimport { parseSetCookie } from 'set-cookie-parser'\n\nimport { getCookieHeaderKeys } from '@/v2/blocks/operation-block/helpers/get-cookie-header-keys'\nimport { resolveResponseBodyHandler } from '@/v2/blocks/response-block/helpers/resolve-response-body-handler'\nimport {\n resolveResponseContentType,\n resolveResponseMimeType,\n} from '@/v2/blocks/response-block/helpers/resolve-response-content-type'\n\nimport { decodeBuffer } from './decode-buffer'\n\nconst CUSTOM_COOKIE_HEADER = 'x-scalar-set-cookie'\n\n/** A single set of populated values for a sent request */\nexport type ResponseInstance = Omit<Response, 'headers'> & {\n /** Store headers as an object to match what we had with axios */\n headers: Record<string, string>\n /** Keys of headers which set cookies */\n cookieHeaderKeys: string[]\n /** Time in ms the request took */\n duration: number\n /** The response status */\n status: number\n /** The response status text */\n statusText: string\n /** The response method */\n method: HttpMethod\n /** The request path */\n path: string\n} & (\n | {\n /** The response data */\n data: string | Blob\n /** The response size in bytes */\n size: number\n }\n | {\n /** A stream reader for a streamable response body */\n reader: ReadableStreamDefaultReader<Uint8Array>\n }\n )\n\n/** HTTP status codes that should not include a response body */\nconst NO_BODY_STATUS_CODES = [204, 205, 304]\n\n/** Custom fetch function signature compatible with the global fetch API */\nexport type CustomFetch = typeof fetch\n\n/**\n * Execute the built fetch request and return a structured response.\n *\n * This function handles the complete request lifecycle including plugin hooks,\n * response processing, streaming detection, and error handling. It supports both\n * standard responses and server-sent event streams.\n *\n * @param request - The request built by the buildRequest helper\n * @param operation - The OpenAPI operation being executed\n * @param plugins - Array of client plugins to execute hooks\n * @param isUsingProxy - Whether the request is being proxied for header handling\n * @returns A tuple with either an error or the response data\n */\nexport const sendRequest = async ({\n isUsingProxy,\n requestPayload,\n plugins = [],\n customFetch = fetch,\n}: {\n isUsingProxy: boolean\n requestPayload: RequestPayload\n /** Registered client plugins for custom content type handling */\n plugins?: ClientPlugin[]\n /** Optional custom fetch implementation, overrides the global fetch */\n customFetch?: CustomFetch\n}): Promise<\n ErrorResponse<{\n response: ResponseInstance\n requestPayload: RequestPayload\n timestamp: number\n originalResponse: Response\n }>\n> => {\n try {\n // Execute the request and measure duration\n const startTime = performance.now()\n\n // In electron we allow GET requests to have a body\n const response = isElectron()\n ? await customFetch(...requestPayload)\n : await customFetch(buildSafeBodyRequest(...requestPayload))\n\n const endTime = performance.now()\n const timestamp = Date.now()\n const duration = endTime - startTime\n\n // Extract response metadata early for reuse\n const contentType = response.headers.get('content-type')\n const responseHeaders = normalizeHeaders(response.headers, isUsingProxy)\n const responseUrl = new URL(response.url)\n const fullPath = responseUrl.pathname + responseUrl.search\n const statusText = response.statusText || httpStatusCodes[response.status]?.name || ''\n const method = (requestPayload[1].method ?? 'GET') as HttpMethod\n const shouldSkipBody = NO_BODY_STATUS_CODES.includes(response.status)\n\n /**\n * Handle server-sent event streams separately.\n * These responses need a reader instead of buffered data.\n * We check this early to avoid unnecessary body reading.\n */\n if (contentType?.startsWith('text/event-stream') && response.body) {\n return buildStreamingResponse({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n })\n }\n\n return buildStandardResponse({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n contentType,\n shouldSkipBody,\n plugins,\n })\n } catch (error) {\n return [normalizeError(error, ERRORS.REQUEST_FAILED), null]\n }\n}\n\n/**\n * Extracts and serializes custom cookies from the response using the custom cookie header.\n *\n * This function parses the custom cookie header (if present), serializes each cookie using the\n * 'cookie' library, and then deletes the custom cookie header from the response.\n * Returns an array of serialized cookie strings, or null if no cookies were found.\n *\n * The @ts-expect-error is present due to a type mismatch between the 'cookie' parsing and serialization libraries.\n */\nconst getCustomCookie = (response: Response): string[] | null => {\n const result = parseSetCookie(response.headers.get(CUSTOM_COOKIE_HEADER) ?? '').map((c) =>\n cookie.serialize(c.name, c.value, {\n ...c,\n sameSite: c.sameSite as boolean | 'lax' | 'strict' | 'none' | undefined,\n encode: (str: string) => str,\n }),\n )\n\n if (result.length) {\n return result\n }\n\n return null\n}\n\n/**\n * Build a streaming response for server-sent events.\n * Streaming responses use a reader instead of buffering the entire body.\n */\nconst buildStreamingResponse = ({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n}: {\n response: Response\n requestPayload: RequestPayload\n timestamp: number\n duration: number\n responseHeaders: Record<string, string>\n statusText: string\n method: HttpMethod\n fullPath: string\n}): ErrorResponse<{\n response: ResponseInstance\n requestPayload: RequestPayload\n timestamp: number\n originalResponse: Response\n}> => {\n const normalizedResponse = new Response(null, {\n status: response.status,\n statusText,\n headers: response.headers,\n })\n\n const customCookie = getCustomCookie(normalizedResponse)\n const cookieHeaderKeys = customCookie ?? getCookieHeaderKeys(normalizedResponse.headers)\n\n return [\n null,\n {\n timestamp,\n requestPayload,\n response: {\n ...normalizedResponse,\n headers: responseHeaders,\n cookieHeaderKeys,\n reader: response.body!.getReader(),\n duration,\n method,\n path: fullPath,\n },\n originalResponse: normalizedResponse.clone(),\n },\n ]\n}\n\n/**\n * Build a standard response with buffered body data.\n * This handles all non-streaming responses including JSON, text, and binary data.\n */\nconst buildStandardResponse = async ({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n contentType,\n shouldSkipBody,\n plugins,\n}: {\n response: Response\n requestPayload: RequestPayload\n timestamp: number\n duration: number\n responseHeaders: Record<string, string>\n statusText: string\n method: HttpMethod\n fullPath: string\n contentType: string | null\n shouldSkipBody: boolean\n plugins: ClientPlugin[]\n}): Promise<\n ErrorResponse<{\n response: ResponseInstance\n requestPayload: RequestPayload\n timestamp: number\n originalResponse: Response\n }>\n> => {\n /**\n * Clone the response to preserve the original for body reading.\n * Read the body once and reuse the buffer for both decoding and creating a new Response.\n */\n const clonedResponse = response.clone()\n const arrayBuffer = await clonedResponse.arrayBuffer()\n const responseType = resolveResponseContentType(contentType)\n const mimeEssence = resolveResponseMimeType(contentType).essence\n const pluginHandler = resolveResponseBodyHandler(mimeEssence, plugins)\n const responseData = await decodeBuffer(arrayBuffer, responseType, pluginHandler)\n\n /**\n * Create a new Response using the arrayBuffer we already read.\n * ArrayBuffers can be reused to create new Response objects without additional memory.\n */\n const normalizedResponse = new Response(shouldSkipBody ? null : arrayBuffer, {\n status: response.status,\n statusText,\n headers: response.headers,\n })\n\n const customCookie = getCustomCookie(normalizedResponse)\n const cookieHeaderKeys = customCookie ?? getCookieHeaderKeys(normalizedResponse.headers)\n\n return [\n null,\n {\n timestamp,\n requestPayload,\n response: {\n ...normalizedResponse,\n headers: responseHeaders,\n cookieHeaderKeys,\n data: responseData,\n size: arrayBuffer.byteLength,\n duration,\n method,\n status: response.status,\n path: fullPath,\n },\n originalResponse: response.clone(),\n },\n ]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"send-request.js","names":[],"sources":["../../../../../src/v2/blocks/operation-block/helpers/send-request.ts"],"sourcesContent":["import { ERRORS, type ErrorResponse, normalizeError } from '@scalar/helpers/errors/normalize-error'\nimport { isElectron } from '@scalar/helpers/general/is-electron'\nimport { buildSafeBodyRequest } from '@scalar/helpers/http/can-method-have-body'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\nimport { normalizeHeaders } from '@scalar/helpers/http/normalize-headers'\nimport { X_SCALAR_SET_COOKIE } from '@scalar/helpers/http/scalar-headers'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { RequestPayload } from '@scalar/workspace-store/request-example'\nimport * as cookie from 'cookie'\nimport { parseSetCookie } from 'set-cookie-parser'\n\nimport { getCookieHeaderKeys } from '@/v2/blocks/operation-block/helpers/get-cookie-header-keys'\nimport { resolveResponseBodyHandler } from '@/v2/blocks/response-block/helpers/resolve-response-body-handler'\nimport {\n resolveResponseContentType,\n resolveResponseMimeType,\n} from '@/v2/blocks/response-block/helpers/resolve-response-content-type'\n\nimport { decodeBuffer } from './decode-buffer'\n\n/** A single set of populated values for a sent request */\nexport type ResponseInstance = Omit<Response, 'headers'> & {\n /** Store headers as an object to match what we had with axios */\n headers: Record<string, string>\n /** Keys of headers which set cookies */\n cookieHeaderKeys: string[]\n /** Time in ms the request took */\n duration: number\n /** The response status */\n status: number\n /** The response status text */\n statusText: string\n /** The response method */\n method: HttpMethod\n /** The request path */\n path: string\n} & (\n | {\n /** The response data */\n data: string | Blob\n /** The response size in bytes */\n size: number\n }\n | {\n /** A stream reader for a streamable response body */\n reader: ReadableStreamDefaultReader<Uint8Array>\n }\n )\n\n/** HTTP status codes that should not include a response body */\nconst NO_BODY_STATUS_CODES = [204, 205, 304]\n\n/** Custom fetch function signature compatible with the global fetch API */\nexport type CustomFetch = typeof fetch\n\n/**\n * Execute the built fetch request and return a structured response.\n *\n * This function handles the complete request lifecycle including plugin hooks,\n * response processing, streaming detection, and error handling. It supports both\n * standard responses and server-sent event streams.\n *\n * @param request - The request built by the buildRequest helper\n * @param operation - The OpenAPI operation being executed\n * @param plugins - Array of client plugins to execute hooks\n * @param isUsingProxy - Whether the request is being proxied for header handling\n * @returns A tuple with either an error or the response data\n */\nexport const sendRequest = async ({\n isUsingProxy,\n requestPayload,\n plugins = [],\n customFetch = fetch,\n}: {\n isUsingProxy: boolean\n requestPayload: RequestPayload\n /** Registered client plugins for custom content type handling */\n plugins?: ClientPlugin[]\n /** Optional custom fetch implementation, overrides the global fetch */\n customFetch?: CustomFetch\n}): Promise<\n ErrorResponse<{\n response: ResponseInstance\n requestPayload: RequestPayload\n timestamp: number\n originalResponse: Response\n }>\n> => {\n try {\n // Execute the request and measure duration\n const startTime = performance.now()\n\n // In electron we allow GET requests to have a body\n const response = isElectron()\n ? await customFetch(...requestPayload)\n : await customFetch(buildSafeBodyRequest(...requestPayload))\n\n const endTime = performance.now()\n const timestamp = Date.now()\n const duration = endTime - startTime\n\n // Extract response metadata early for reuse\n const contentType = response.headers.get('content-type')\n const responseHeaders = normalizeHeaders(response.headers, isUsingProxy)\n const responseUrl = new URL(response.url)\n const fullPath = responseUrl.pathname + responseUrl.search\n const statusText = response.statusText || httpStatusCodes[response.status]?.name || ''\n const method = (requestPayload[1].method ?? 'GET') as HttpMethod\n const shouldSkipBody = NO_BODY_STATUS_CODES.includes(response.status)\n\n /**\n * Handle server-sent event streams separately.\n * These responses need a reader instead of buffered data.\n * We check this early to avoid unnecessary body reading.\n */\n if (contentType?.startsWith('text/event-stream') && response.body) {\n return buildStreamingResponse({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n })\n }\n\n return buildStandardResponse({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n contentType,\n shouldSkipBody,\n plugins,\n })\n } catch (error) {\n return [normalizeError(error, ERRORS.REQUEST_FAILED), null]\n }\n}\n\n/**\n * Extracts and serializes custom cookies from the response using the custom cookie header.\n *\n * This function parses the custom cookie header (if present), serializes each cookie using the\n * 'cookie' library, and then deletes the custom cookie header from the response.\n * Returns an array of serialized cookie strings, or null if no cookies were found.\n *\n * The @ts-expect-error is present due to a type mismatch between the 'cookie' parsing and serialization libraries.\n */\nconst getCustomCookie = (response: Response): string[] | null => {\n const result = parseSetCookie(response.headers.get(X_SCALAR_SET_COOKIE) ?? '').map((c) =>\n cookie.serialize(c.name, c.value, {\n ...c,\n sameSite: c.sameSite as boolean | 'lax' | 'strict' | 'none' | undefined,\n encode: (str: string) => str,\n }),\n )\n\n if (result.length) {\n return result\n }\n\n return null\n}\n\n/**\n * Build a streaming response for server-sent events.\n * Streaming responses use a reader instead of buffering the entire body.\n */\nconst buildStreamingResponse = ({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n}: {\n response: Response\n requestPayload: RequestPayload\n timestamp: number\n duration: number\n responseHeaders: Record<string, string>\n statusText: string\n method: HttpMethod\n fullPath: string\n}): ErrorResponse<{\n response: ResponseInstance\n requestPayload: RequestPayload\n timestamp: number\n originalResponse: Response\n}> => {\n const normalizedResponse = new Response(null, {\n status: response.status,\n statusText,\n headers: response.headers,\n })\n\n const customCookie = getCustomCookie(normalizedResponse)\n const cookieHeaderKeys = customCookie ?? getCookieHeaderKeys(normalizedResponse.headers)\n\n return [\n null,\n {\n timestamp,\n requestPayload,\n response: {\n ...normalizedResponse,\n headers: responseHeaders,\n cookieHeaderKeys,\n reader: response.body!.getReader(),\n duration,\n method,\n path: fullPath,\n },\n originalResponse: normalizedResponse.clone(),\n },\n ]\n}\n\n/**\n * Build a standard response with buffered body data.\n * This handles all non-streaming responses including JSON, text, and binary data.\n */\nconst buildStandardResponse = async ({\n response,\n requestPayload,\n timestamp,\n duration,\n responseHeaders,\n statusText,\n method,\n fullPath,\n contentType,\n shouldSkipBody,\n plugins,\n}: {\n response: Response\n requestPayload: RequestPayload\n timestamp: number\n duration: number\n responseHeaders: Record<string, string>\n statusText: string\n method: HttpMethod\n fullPath: string\n contentType: string | null\n shouldSkipBody: boolean\n plugins: ClientPlugin[]\n}): Promise<\n ErrorResponse<{\n response: ResponseInstance\n requestPayload: RequestPayload\n timestamp: number\n originalResponse: Response\n }>\n> => {\n /**\n * Clone the response to preserve the original for body reading.\n * Read the body once and reuse the buffer for both decoding and creating a new Response.\n */\n const clonedResponse = response.clone()\n const arrayBuffer = await clonedResponse.arrayBuffer()\n const responseType = resolveResponseContentType(contentType)\n const mimeEssence = resolveResponseMimeType(contentType).essence\n const pluginHandler = resolveResponseBodyHandler(mimeEssence, plugins)\n const responseData = await decodeBuffer(arrayBuffer, responseType, pluginHandler)\n\n /**\n * Create a new Response using the arrayBuffer we already read.\n * ArrayBuffers can be reused to create new Response objects without additional memory.\n */\n const normalizedResponse = new Response(shouldSkipBody ? null : arrayBuffer, {\n status: response.status,\n statusText,\n headers: response.headers,\n })\n\n const customCookie = getCustomCookie(normalizedResponse)\n const cookieHeaderKeys = customCookie ?? getCookieHeaderKeys(normalizedResponse.headers)\n\n return [\n null,\n {\n timestamp,\n requestPayload,\n response: {\n ...normalizedResponse,\n headers: responseHeaders,\n cookieHeaderKeys,\n data: responseData,\n size: arrayBuffer.byteLength,\n duration,\n method,\n status: response.status,\n path: fullPath,\n },\n originalResponse: response.clone(),\n },\n ]\n}\n"],"mappings":";;;;;;;;;;;;;;AAmDA,IAAM,uBAAuB;CAAC;CAAK;CAAK;CAAI;;;;;;;;;;;;;;AAkB5C,IAAa,cAAc,OAAO,EAChC,cACA,gBACA,UAAU,EAAE,EACZ,cAAc,YAeX;AACH,KAAI;EAEF,MAAM,YAAY,YAAY,KAAK;EAGnC,MAAM,WAAW,YAAY,GACzB,MAAM,YAAY,GAAG,eAAe,GACpC,MAAM,YAAY,qBAAqB,GAAG,eAAe,CAAC;EAE9D,MAAM,UAAU,YAAY,KAAK;EACjC,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,WAAW,UAAU;EAG3B,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe;EACxD,MAAM,kBAAkB,iBAAiB,SAAS,SAAS,aAAa;EACxE,MAAM,cAAc,IAAI,IAAI,SAAS,IAAI;EACzC,MAAM,WAAW,YAAY,WAAW,YAAY;EACpD,MAAM,aAAa,SAAS,cAAc,gBAAgB,SAAS,SAAS,QAAQ;EACpF,MAAM,SAAU,eAAe,GAAG,UAAU;EAC5C,MAAM,iBAAiB,qBAAqB,SAAS,SAAS,OAAO;;;;;;AAOrE,MAAI,aAAa,WAAW,oBAAoB,IAAI,SAAS,KAC3D,QAAO,uBAAuB;GAC5B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAGJ,SAAO,sBAAsB;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;UACK,OAAO;AACd,SAAO,CAAC,eAAe,OAAO,OAAO,eAAe,EAAE,KAAK;;;;;;;;;;;;AAa/D,IAAM,mBAAmB,aAAwC;CAC/D,MAAM,SAAS,eAAe,SAAS,QAAQ,IAAI,oBAAoB,IAAI,GAAG,CAAC,KAAK,MAClF,OAAO,UAAU,EAAE,MAAM,EAAE,OAAO;EAChC,GAAG;EACH,UAAU,EAAE;EACZ,SAAS,QAAgB;EAC1B,CAAC,CACH;AAED,KAAI,OAAO,OACT,QAAO;AAGT,QAAO;;;;;;AAOT,IAAM,0BAA0B,EAC9B,UACA,gBACA,WACA,UACA,iBACA,YACA,QACA,eAeI;CACJ,MAAM,qBAAqB,IAAI,SAAS,MAAM;EAC5C,QAAQ,SAAS;EACjB;EACA,SAAS,SAAS;EACnB,CAAC;CAGF,MAAM,mBADe,gBAAgB,mBAAmB,IACf,oBAAoB,mBAAmB,QAAQ;AAExF,QAAO,CACL,MACA;EACE;EACA;EACA,UAAU;GACR,GAAG;GACH,SAAS;GACT;GACA,QAAQ,SAAS,KAAM,WAAW;GAClC;GACA;GACA,MAAM;GACP;EACD,kBAAkB,mBAAmB,OAAO;EAC7C,CACF;;;;;;AAOH,IAAM,wBAAwB,OAAO,EACnC,UACA,gBACA,WACA,UACA,iBACA,YACA,QACA,UACA,aACA,gBACA,cAoBG;CAMH,MAAM,cAAc,MADG,SAAS,OAAO,CACE,aAAa;CACtD,MAAM,eAAe,2BAA2B,YAAY;CAC5D,MAAM,cAAc,wBAAwB,YAAY,CAAC;CAEzD,MAAM,eAAe,MAAM,aAAa,aAAa,cAD/B,2BAA2B,aAAa,QAAQ,CACW;;;;;CAMjF,MAAM,qBAAqB,IAAI,SAAS,iBAAiB,OAAO,aAAa;EAC3E,QAAQ,SAAS;EACjB;EACA,SAAS,SAAS;EACnB,CAAC;CAGF,MAAM,mBADe,gBAAgB,mBAAmB,IACf,oBAAoB,mBAAmB,QAAQ;AAExF,QAAO,CACL,MACA;EACE;EACA;EACA,UAAU;GACR,GAAG;GACH,SAAS;GACT;GACA,MAAM;GACN,MAAM,YAAY;GAClB;GACA;GACA,QAAQ,SAAS;GACjB,MAAM;GACP;EACD,kBAAkB,SAAS,OAAO;EACnC,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestBlock.vue.script.js","names":[],"sources":["../../../../src/v2/blocks/request-block/RequestBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarErrorBoundary } from '@scalar/components'\nimport { canMethodHaveBody } from '@scalar/helpers/http/can-method-have-body'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport { REGEX } from '@scalar/helpers/regex/regex-helpers'\nimport { replaceEnvVariables } from '@scalar/helpers/regex/replace-variables'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n AuthMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n filterGlobalCookie,\n getEnvironmentVariables,\n getExample,\n getResolvedUrl,\n type MergedSecuritySchemes,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCookie } from '@scalar/workspace-store/schemas/extensions/general/x-scalar-cookies'\nimport type {\n OpenApiDocument,\n OperationObject,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, useId, watch } from 'vue'\n\nimport SectionFilter from '@/components/SectionFilter.vue'\nimport ViewLayoutSection from '@/components/ViewLayout/ViewLayoutSection.vue'\nimport type { ClientOptionGroup } from '@/v2/blocks/operation-code-sample'\nimport RequestBody from '@/v2/blocks/request-block/components/RequestBody.vue'\nimport RequestCodeSnippet from '@/v2/blocks/request-block/components/RequestCodeSnippet.vue'\nimport RequestParams from '@/v2/blocks/request-block/components/RequestParams.vue'\nimport type { TableRow } from '@/v2/blocks/request-block/components/RequestTableRow.vue'\nimport { createParameterHandlers } from '@/v2/blocks/request-block/helpers/create-parameter-handlers'\nimport { getParameterSchema } from '@/v2/blocks/request-block/helpers/get-parameter-schema'\nimport { groupBy } from '@/v2/blocks/request-block/helpers/group-by'\nimport { isParamDisabled } from '@/v2/blocks/request-block/helpers/is-param-disabled'\nimport { AuthSelector } from '@/v2/blocks/scalar-auth-selector-block'\nimport type { OAuth2Options } from '@/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue'\nimport type { ClientLayout } from '@/v2/types/layout'\n\ntype Filter =\n | 'All'\n | 'Auth'\n | 'Variables'\n | 'Cookies'\n | 'Headers'\n | 'Query'\n | 'Body'\n\nexport type RequestBlockProps = {\n authMeta: AuthMeta\n clientOptions: ClientOptionGroup[]\n environment: XScalarEnvironment\n eventBus: WorkspaceEventBus\n exampleKey: string\n workspaceCookies: XScalarCookie[]\n documentCookies: XScalarCookie[]\n layout: ClientLayout\n method: HttpMethod\n operation: OperationObject\n path: string\n plugins: ClientPlugin[]\n proxyUrl: string\n securityRequirements: OpenApiDocument['security']\n securitySchemes: MergedSecuritySchemes\n requestBodyCompositionSelection?: Record<string, number>\n selectedClient: WorkspaceStore['workspace']['x-scalar-default-client']\n selectedSecurity: SelectedSecurity\n selectedSecuritySchemes: SecuritySchemeObjectSecret[]\n server: ServerObject | null\n defaultHeaders: Record<string, string>\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n}\n\nconst {\n authMeta = { type: 'document' },\n clientOptions,\n environment,\n eventBus,\n exampleKey,\n workspaceCookies,\n documentCookies,\n layout,\n method,\n operation,\n path,\n plugins,\n proxyUrl,\n requestBodyCompositionSelection,\n securityRequirements,\n securitySchemes,\n selectedClient,\n selectedSecurity,\n selectedSecuritySchemes,\n server,\n defaultHeaders: autoGeneratedHeaders,\n} = defineProps<RequestBlockProps>()\n\n/** Operation metadata used across event emissions */\nconst meta = computed(() => ({\n method,\n path,\n exampleKey,\n}))\n\n/** Parameters grouped by type (path, query, header, cookie) */\nconst sections = computed(() =>\n groupBy(\n operation.parameters?.map((param) => getResolvedRef(param)) ?? [],\n 'in',\n (param) => {\n const example = getExample(param, exampleKey, undefined)\n\n return {\n name: param.name,\n value: example?.value ?? '',\n description: param.description,\n schema: getParameterSchema(param),\n isRequired: param.required,\n isDisabled: isParamDisabled(param, example),\n originalParameter: param,\n } as TableRow\n },\n ),\n)\n\n// Generate a reverse map for fast lookup of headers by the name\nconst headersMap = computed(() =>\n groupBy(\n sections.value.header?.map((it) => ({\n ...it,\n name: it.name.toLowerCase(),\n })) ?? [],\n 'name',\n ),\n)\n\nconst defaultHeaders = computed(() => {\n const disableParameters =\n operation['x-scalar-disable-parameters']?.['default-headers']?.[\n exampleKey\n ] ?? {}\n\n const isOverridden = (name: string) => {\n const header = headersMap.value[name.toLowerCase()]?.[0]\n\n if (!header) {\n return false\n }\n\n return !header.isDisabled\n }\n\n return Object.entries(autoGeneratedHeaders).map(([name, value]) => {\n return {\n name,\n value,\n schema: undefined,\n isOverridden: isOverridden(name),\n isReadonly: true,\n isDisabled: disableParameters[name.toLowerCase()] ?? false,\n } satisfies TableRow\n })\n})\n\nconst headers = computed(() => [\n ...defaultHeaders.value,\n ...(sections.value.header ?? []),\n])\n\nconst defaultCookies = computed(() => {\n const environmentVariables = getEnvironmentVariables(environment)\n const resolvedUrl = getResolvedUrl({\n server,\n path,\n })\n const url = replaceEnvVariables(resolvedUrl, environmentVariables)\n\n const disabledGlobalCookies =\n operation['x-scalar-disable-parameters']?.['global-cookies']?.[\n exampleKey\n ] ?? {}\n\n const transform = (\n cookie: XScalarCookie,\n location: 'document' | 'workspace',\n ) => {\n return {\n name: cookie.name,\n value: cookie.value,\n globalRoute: { page: location, path: 'cookies' } as const,\n isReadonly: true,\n isDisabled: disabledGlobalCookies[cookie.name.toLowerCase()] ?? false,\n } satisfies TableRow\n }\n\n const globalCookies = [\n {\n location: 'workspace',\n cookies: workspaceCookies,\n },\n {\n location: 'document',\n cookies: documentCookies,\n },\n ] as const\n\n return globalCookies.flatMap(({ location, cookies }) => {\n return cookies\n .filter((cookie) =>\n filterGlobalCookie({\n cookie,\n url,\n disabledGlobalCookies: {},\n }),\n )\n .map((cookie) => transform(cookie, location))\n })\n})\n\nconst cookies = computed(() => [\n ...(defaultCookies.value ?? []),\n ...(sections.value.cookie ?? []),\n])\n\n/** Currently selected filter for the request sections */\nconst selectedFilter = ref<Filter>('All')\n\n/** Available operation sections */\nconst OPERATION_SECTIONS: readonly Filter[] = [\n 'Auth',\n 'Variables',\n 'Cookies',\n 'Headers',\n 'Query',\n 'Body',\n] as const\n\n/**\n * Pre-generated stable IDs for all possible filter sections.\n * These are created once at setup time to avoid regenerating IDs on re-render.\n */\nconst sectionIds: Record<Filter, string> = {\n All: useId(),\n Auth: useId(),\n Variables: useId(),\n Cookies: useId(),\n Headers: useId(),\n Query: useId(),\n Body: useId(),\n}\n\n/** Filters available based on operation state */\nconst filters = computed<Filter[]>(() => {\n const availableFilters = new Set<Filter>(['All', ...OPERATION_SECTIONS])\n\n if (!sections.value.path?.length) {\n availableFilters.delete('Variables')\n }\n if (!canMethodHaveBody(method)) {\n availableFilters.delete('Body')\n }\n if (isAuthHidden.value) {\n availableFilters.delete('Auth')\n }\n\n return [...availableFilters]\n})\n\n/**\n * Map available filters to their pre-generated stable IDs.\n * Only includes IDs for filters that are currently available.\n */\nconst filterIds = computed(\n () =>\n Object.fromEntries(\n filters.value.map((section) => [section, sectionIds[section]]),\n ) as Record<Filter, string>,\n)\n\n/**\n * Hide auth selector in readonly mode when no security schemes are defined.\n * This keeps the UI clean when there are no authentication options available.\n */\nconst isAuthHidden = computed(\n () => layout === 'modal' && !Object.keys(securitySchemes ?? {}).length,\n)\n\n/**\n * Keep auth available for unauthenticated operations, but collapse it by default\n * in readonly modal layouts unless a requirement or manual selection exists.\n */\nconst isAuthDefaultOpen = computed(\n () =>\n layout !== 'modal' ||\n Boolean(\n securityRequirements?.length || selectedSecurity.selectedSchemes.length,\n ),\n)\n\n/** Get a sensible placeholder for the request name input */\nconst requestNamePlaceholder = computed(() => {\n if (operation.summary) {\n return operation.summary\n }\n const cleanPath = path.replace(REGEX.PROTOCOL, '')\n return cleanPath || 'Request Name'\n})\n\n/** Check if the section should be shown based on the selected filter */\nconst isSectionVisible = (section: Filter): boolean => {\n return selectedFilter.value === 'All' || selectedFilter.value === section\n}\n\n/**\n * Reset filter to 'All' if Body filter is selected but method changes to one that cannot have a body.\n * This prevents showing an empty Body section when switching methods.\n */\nwatch(\n () => method,\n (newMethod) => {\n if (selectedFilter.value === 'Body' && !canMethodHaveBody(newMethod)) {\n selectedFilter.value = 'All'\n }\n },\n)\n\n/** Handle operation summary updates */\nconst handleSummaryUpdate = (event: Event): void => {\n const summary = (event.target as HTMLInputElement).value\n eventBus.emit('operation:update:meta', {\n meta: meta.value,\n payload: { summary: summary.trim() },\n })\n}\n\n/** Parameter handlers */\nconst parameterHandlers = computed(() => ({\n path: createParameterHandlers('path', eventBus, meta.value, {\n context: sections.value.path ?? [],\n }),\n cookie: createParameterHandlers('cookie', eventBus, meta.value, {\n context: cookies.value ?? [],\n globalParameters: defaultCookies.value.length,\n }),\n header: createParameterHandlers('header', eventBus, meta.value, {\n context: headers.value,\n defaultParameters: defaultHeaders.value.length,\n }),\n query: createParameterHandlers('query', eventBus, meta.value, {\n context: sections.value.query ?? [],\n }),\n}))\n\n/** Handle request body content type update */\nconst handleUpdateContentType = (payload: { value: string }): void =>\n eventBus.emit('operation:update:requestBody:contentType', {\n payload: { contentType: payload.value },\n meta: meta.value,\n })\n\n/** Handle request body value update */\nconst handleUpdateBodyValue = ({\n payload,\n contentType,\n}: Pick<\n ApiReferenceEvents['operation:update:requestBody:value'],\n 'payload' | 'contentType'\n>): void => {\n const debounceKey =\n typeof payload === 'string'\n ? `update:requestBody:value-${contentType}`\n : undefined\n\n eventBus.emit(\n 'operation:update:requestBody:value',\n {\n payload,\n contentType,\n meta: meta.value,\n },\n {\n debounceKey,\n },\n )\n}\n\n/** Handle request body value update */\nconst handleUpdateBodyFormValue = ({\n payload,\n contentType,\n}: Pick<\n ApiReferenceEvents['operation:update:requestBody:formValue'],\n 'payload' | 'contentType'\n>): void => {\n const debounceKey = `update:requestBody:${contentType}-form-value`\n\n eventBus.emit(\n 'operation:update:requestBody:formValue',\n {\n payload: payload.map((row) => unpackProxyObject(row, { depth: 1 })),\n contentType,\n meta: meta.value,\n },\n {\n debounceKey,\n },\n )\n}\n\nconst labelRequestNameId = useId()\n\nconst globalCookies = computed(() => [...workspaceCookies, ...documentCookies])\n\n/** Allow updating the operation extensions for the plugins */\nconst updateOperationExtension = (\n payload: ApiReferenceEvents['operation:update:extension']['payload'],\n): void =>\n eventBus.emit('operation:update:extension', { payload, meta: meta.value })\n</script>\n<template>\n <ViewLayoutSection :aria-label=\"`Request: ${operation.summary}`\">\n <template #title>\n <div\n class=\"group pointer-events-none flex flex-1 items-center gap-1 lg:pr-24\">\n <label\n v-if=\"layout !== 'modal'\"\n class=\"pointer-events-auto absolute top-0 left-0 h-full w-full cursor-text opacity-0\"\n :for=\"labelRequestNameId\" />\n <input\n v-if=\"layout !== 'modal'\"\n :id=\"labelRequestNameId\"\n class=\"text-c-1 group-hover-input pointer-events-auto relative z-10 -ml-0.5 h-8 w-full rounded pl-1.25 has-[:focus-visible]:outline md:-ml-1.25\"\n :placeholder=\"requestNamePlaceholder\"\n :value=\"operation.summary\"\n @blur=\"handleSummaryUpdate\" />\n <span\n v-else\n class=\"text-c-1 flex h-8 items-center\">\n {{ operation.summary }}\n </span>\n </div>\n <SectionFilter\n v-model=\"selectedFilter\"\n :filterIds=\"filterIds\"\n :filters=\"filters\" />\n </template>\n\n <div\n :id=\"filterIds.All\"\n class=\"request-section-content custom-scroll relative flex flex-1 flex-col\"\n :role=\"selectedFilter === 'All' ? 'tabpanel' : 'none'\">\n <!-- Auth Selector -->\n <AuthSelector\n v-show=\"isSectionVisible('Auth') && !isAuthHidden\"\n :id=\"filterIds.Auth\"\n :createAnySecurityScheme=\"layout !== 'modal'\"\n :defaultOpen=\"isAuthDefaultOpen\"\n :environment\n :eventBus\n :meta=\"authMeta\"\n :options\n :proxyUrl\n :securityRequirements\n :securitySchemes\n :selectedSecurity\n :selectedSecuritySchemes\n :server\n title=\"Authentication\" />\n\n <!-- Variables (Path Parameters) -->\n <RequestParams\n v-show=\"isSectionVisible('Variables') && sections.path?.length\"\n :id=\"filterIds.Variables\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"sections.path ?? []\"\n :showAddRowPlaceholder=\"false\"\n title=\"Variables\"\n v-on=\"parameterHandlers.path\" />\n\n <!-- Cookies -->\n <RequestParams\n v-show=\"isSectionVisible('Cookies')\"\n :id=\"filterIds.Cookies\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"cookies ?? []\"\n :showAddRowPlaceholder=\"true\"\n title=\"Cookies\"\n v-on=\"parameterHandlers.cookie\" />\n\n <!-- Headers -->\n <RequestParams\n v-show=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"headers ?? []\"\n title=\"Headers\"\n v-on=\"parameterHandlers.header\" />\n\n <!-- Query Parameters -->\n <RequestParams\n v-show=\"isSectionVisible('Query')\"\n :id=\"filterIds.Query\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"sections.query ?? []\"\n title=\"Query Parameters\"\n v-on=\"parameterHandlers.query\" />\n\n <!-- Request Body -->\n <RequestBody\n v-show=\"isSectionVisible('Body') && canMethodHaveBody(method)\"\n :id=\"filterIds.Body\"\n :environment\n :exampleKey\n :requestBody=\"getResolvedRef(operation.requestBody)\"\n :requestBodyCompositionSelection\n title=\"Request Body\"\n @update:contentType=\"handleUpdateContentType\"\n @update:formValue=\"handleUpdateBodyFormValue\"\n @update:value=\"handleUpdateBodyValue\" />\n\n <!-- Inject request section plugin components -->\n <ScalarErrorBoundary\n v-for=\"(plugin, index) in plugins\"\n :key=\"index\">\n <component\n :is=\"plugin.components.request.component\"\n v-if=\"plugin?.components?.request\"\n v-show=\"selectedFilter === 'All'\"\n :operation\n v-bind=\"plugin.components.request.additionalProps\"\n @operation:update:extension=\"updateOperationExtension\" />\n </ScalarErrorBoundary>\n\n <!-- Spacer -->\n <div class=\"flex grow\" />\n <!-- Code Snippet -->\n <RequestCodeSnippet\n v-show=\"selectedFilter === 'All'\"\n :clientOptions\n :eventBus\n :globalCookies\n integration=\"client\"\n :method\n :operation\n :path\n :securitySchemes=\"selectedSecuritySchemes\"\n :selectedClient\n :selectedContentType=\"\n getResolvedRef(operation.requestBody)?.[\n 'x-scalar-selected-content-type'\n ]?.[exampleKey]\n \"\n :selectedServer=\"server ?? undefined\" />\n </div>\n </ViewLayoutSection>\n</template>\n<style scoped>\n.request-section-content {\n --scalar-border-width: 0.5px;\n}\n.request-section-content-filter {\n box-shadow: 0 -10px 0 10px var(--scalar-background-1);\n}\n.request-item:focus-within .request-meta-buttons {\n opacity: 1;\n}\n.group-hover-input {\n border-width: var(--scalar-border-width);\n border-color: transparent;\n}\n.group:hover .group-hover-input {\n background: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n border-color: var(--scalar-border-color);\n}\n.group-hover-input:focus {\n background: transparent !important;\n border-color: var(--scalar-border-color) !important;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4GA,MAAM,OAAO,gBAAgB;GAC3B,QAAK,QAAA;GACL,MAAG,QAAA;GACH,YAAS,QAAA;GACV,EAAC;;EAGF,MAAM,WAAW,eACf,QACE,QAAA,UAAU,YAAY,KAAK,UAAU,eAAe,MAAM,CAAC,IAAI,EAAE,EACjE,OACC,UAAU;GACT,MAAM,UAAU,WAAW,OAAO,QAAA,YAAY,KAAA,EAAS;AAEvD,UAAO;IACL,MAAM,MAAM;IACZ,OAAO,SAAS,SAAS;IACzB,aAAa,MAAM;IACnB,QAAQ,mBAAmB,MAAM;IACjC,YAAY,MAAM;IAClB,YAAY,gBAAgB,OAAO,QAAQ;IAC3C,mBAAmB;IACpB;IAEJ,CACH;EAGA,MAAM,aAAa,eACjB,QACE,SAAS,MAAM,QAAQ,KAAK,QAAQ;GAClC,GAAG;GACH,MAAM,GAAG,KAAK,aAAa;GAC5B,EAAE,IAAI,EAAE,EACT,OACD,CACH;EAEA,MAAM,iBAAiB,eAAe;GACpC,MAAM,oBACJ,QAAA,UAAU,iCAAiC,qBACzC,QAAA,eACG,EAAC;GAER,MAAM,gBAAgB,SAAiB;IACrC,MAAM,SAAS,WAAW,MAAM,KAAK,aAAa,IAAI;AAEtD,QAAI,CAAC,OACH,QAAO;AAGT,WAAO,CAAC,OAAO;;AAGjB,UAAO,OAAO,QAAQ,QAAA,eAAqB,CAAC,KAAK,CAAC,MAAM,WAAW;AACjE,WAAO;KACL;KACA;KACA,QAAQ,KAAA;KACR,cAAc,aAAa,KAAK;KAChC,YAAY;KACZ,YAAY,kBAAkB,KAAK,aAAa,KAAK;KACtD;KACF;IACF;EAED,MAAM,UAAU,eAAe,CAC7B,GAAG,eAAe,OAClB,GAAI,SAAS,MAAM,UAAU,EAAE,CAChC,CAAA;EAED,MAAM,iBAAiB,eAAe;GACpC,MAAM,uBAAuB,wBAAwB,QAAA,YAAW;GAKhE,MAAM,MAAM,oBAJQ,eAAe;IACjC,QAAK,QAAA;IACL,MAAG,QAAA;IACJ,CAAA,EAC4C,qBAAoB;GAEjE,MAAM,wBACJ,QAAA,UAAU,iCAAiC,oBACzC,QAAA,eACG,EAAC;GAER,MAAM,aACJ,QACA,aACG;AACH,WAAO;KACL,MAAM,OAAO;KACb,OAAO,OAAO;KACd,aAAa;MAAE,MAAM;MAAU,MAAM;MAAW;KAChD,YAAY;KACZ,YAAY,sBAAsB,OAAO,KAAK,aAAa,KAAK;KACjE;;AAcH,UAXsB,CACpB;IACE,UAAU;IACV,SAAS,QAAA;IACV,EACD;IACE,UAAU;IACV,SAAS,QAAA;IACV,CACF,CAEoB,SAAS,EAAE,UAAU,cAAc;AACtD,WAAO,QACJ,QAAQ,WACP,mBAAmB;KACjB;KACA;KACA,uBAAuB,EAAE;KAC1B,CAAC,CACJ,CACC,KAAK,WAAW,UAAU,QAAQ,SAAS,CAAA;KAC/C;IACF;EAED,MAAM,UAAU,eAAe,CAC7B,GAAI,eAAe,SAAS,EAAE,EAC9B,GAAI,SAAS,MAAM,UAAU,EAAE,CAChC,CAAA;;EAGD,MAAM,iBAAiB,IAAY,MAAK;;EAGxC,MAAM,qBAAwC;GAC5C;GACA;GACA;GACA;GACA;GACA;GACD;;;;;EAMD,MAAM,aAAqC;GACzC,KAAK,OAAO;GACZ,MAAM,OAAO;GACb,WAAW,OAAO;GAClB,SAAS,OAAO;GAChB,SAAS,OAAO;GAChB,OAAO,OAAO;GACd,MAAM,OAAO;GACf;;EAGA,MAAM,UAAU,eAAyB;GACvC,MAAM,mBAAmB,IAAI,IAAY,CAAC,OAAO,GAAG,mBAAmB,CAAA;AAEvE,OAAI,CAAC,SAAS,MAAM,MAAM,OACxB,kBAAiB,OAAO,YAAW;AAErC,OAAI,CAAC,kBAAkB,QAAA,OAAO,CAC5B,kBAAiB,OAAO,OAAM;AAEhC,OAAI,aAAa,MACf,kBAAiB,OAAO,OAAM;AAGhC,UAAO,CAAC,GAAG,iBAAgB;IAC5B;;;;;EAMD,MAAM,YAAY,eAEd,OAAO,YACL,QAAQ,MAAM,KAAK,YAAY,CAAC,SAAS,WAAW,SAAS,CAAC,CAC/D,CACL;;;;;EAMA,MAAM,eAAe,eACb,QAAA,WAAW,WAAW,CAAC,OAAO,KAAK,QAAA,mBAAmB,EAAE,CAAC,CAAC,OAClE;;;;;EAMA,MAAM,oBAAoB,eAEtB,QAAA,WAAW,WACX,QACE,QAAA,sBAAsB,UAAU,QAAA,iBAAiB,gBAAgB,OAClE,CACL;;EAGA,MAAM,yBAAyB,eAAe;AAC5C,OAAI,QAAA,UAAU,QACZ,QAAO,QAAA,UAAU;AAGnB,UADkB,QAAA,KAAK,QAAQ,MAAM,UAAU,GAAE,IAC7B;IACrB;;EAGD,MAAM,oBAAoB,YAA6B;AACrD,UAAO,eAAe,UAAU,SAAS,eAAe,UAAU;;;;;;AAOpE,cACQ,QAAA,SACL,cAAc;AACb,OAAI,eAAe,UAAU,UAAU,CAAC,kBAAkB,UAAU,CAClE,gBAAe,QAAQ;IAG7B;;EAGA,MAAM,uBAAuB,UAAuB;GAClD,MAAM,UAAW,MAAM,OAA4B;AACnD,WAAA,SAAS,KAAK,yBAAyB;IACrC,MAAM,KAAK;IACX,SAAS,EAAE,SAAS,QAAQ,MAAM,EAAE;IACrC,CAAA;;;EAIH,MAAM,oBAAoB,gBAAgB;GACxC,MAAM,wBAAwB,QAAQ,QAAA,UAAU,KAAK,OAAO,EAC1D,SAAS,SAAS,MAAM,QAAQ,EAAE,EACnC,CAAC;GACF,QAAQ,wBAAwB,UAAU,QAAA,UAAU,KAAK,OAAO;IAC9D,SAAS,QAAQ,SAAS,EAAE;IAC5B,kBAAkB,eAAe,MAAM;IACxC,CAAC;GACF,QAAQ,wBAAwB,UAAU,QAAA,UAAU,KAAK,OAAO;IAC9D,SAAS,QAAQ;IACjB,mBAAmB,eAAe,MAAM;IACzC,CAAC;GACF,OAAO,wBAAwB,SAAS,QAAA,UAAU,KAAK,OAAO,EAC5D,SAAS,SAAS,MAAM,SAAS,EAAE,EACpC,CAAC;GACH,EAAC;;EAGF,MAAM,2BAA2B,YAC/B,QAAA,SAAS,KAAK,4CAA4C;GACxD,SAAS,EAAE,aAAa,QAAQ,OAAO;GACvC,MAAM,KAAK;GACZ,CAAA;;EAGH,MAAM,yBAAyB,EAC7B,SACA,kBAIU;GACV,MAAM,cACJ,OAAO,YAAY,WACf,4BAA4B,gBAC5B,KAAA;AAEN,WAAA,SAAS,KACP,sCACA;IACE;IACA;IACA,MAAM,KAAK;IACZ,EACD,EACE,aACD,CACH;;;EAIF,MAAM,6BAA6B,EACjC,SACA,kBAIU;GACV,MAAM,cAAc,sBAAsB,YAAY;AAEtD,WAAA,SAAS,KACP,0CACA;IACE,SAAS,QAAQ,KAAK,QAAQ,kBAAkB,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC;IACnE;IACA,MAAM,KAAK;IACZ,EACD,EACE,aACD,CACH;;EAGF,MAAM,qBAAqB,OAAM;EAEjC,MAAM,gBAAgB,eAAe,CAAC,GAAG,QAAA,kBAAkB,GAAG,QAAA,gBAAgB,CAAA;;EAG9E,MAAM,4BACJ,YAEA,QAAA,SAAS,KAAK,8BAA8B;GAAE;GAAS,MAAM,KAAK;GAAO,CAAA;;uBAGzE,YA8IoB,2BAAA,EA9IA,cAAU,YAAc,QAAA,UAAU,WAAA,EAAA;IACzC,OAAK,cAmBR,CAlBN,mBAkBM,OAlBN,YAkBM,CAfI,QAAA,WAAM,WAAA,WAAA,EADd,mBAG8B,SAAA;;KAD5B,OAAM;KACL,KAAK,MAAA,mBAAkB;6DAElB,QAAA,WAAM,WAAA,WAAA,EADd,mBAMgC,SAAA;;KAJ7B,IAAI,MAAA,mBAAkB;KACvB,OAAM;KACL,aAAa,uBAAA;KACb,OAAO,QAAA,UAAU;KACjB,QAAM;8CACT,mBAIO,QAJP,YAIO,gBADF,QAAA,UAAU,QAAO,EAAA,EAAA,EAAA,CAAA,EAGxB,YAGuB,uBAAA;iBAFZ,eAAA;iFAAc,QAAA;KACtB,WAAW,UAAA;KACX,SAAS,QAAA;;;;;;2BAqHR,CAlHN,mBAkHM,OAAA;KAjHH,IAAI,UAAA,MAAU;KACf,OAAM;KACL,MAAM,eAAA,UAAc,QAAA,aAAA;;oBAErB,YAe2B,MAAA,qBAAA,EAAA;MAbxB,IAAI,UAAA,MAAU;MACd,yBAAyB,QAAA,WAAM;MAC/B,aAAa,kBAAA;MACb,aAAA,QAAA;MACA,UAAA,QAAA;MACA,MAAM,QAAA;MACN,SAAA,QAAA;MACA,UAAA,QAAA;MACA,sBAAA,QAAA;MACA,iBAAA,QAAA;MACA,kBAAA,QAAA;MACA,yBAAA,QAAA;MACA,QAAA,QAAA;MACD,OAAM;;;;;;;;;;;;;;;kBAdE,iBAAgB,OAAA,IAAA,CAAa,aAAA,MAAY,CAAA,CAAA;oBAiBnD,YASkC,uBATlC,WASkC;MAP/B,IAAI,UAAA,MAAU;MACd,aAAA,QAAA;MACA,UAAA,QAAA;MACA,YAAA,QAAA;MACA,MAAM,SAAA,MAAS,QAAI,EAAA;MACnB,uBAAuB;MACxB,OAAM;QACN,WAAM,kBAAuB,MAAL,KAAI,CAAA,EAAA,MAAA,IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA,CAAA,EAAA,CAAA,CAAA,OARpB,iBAAgB,YAAA,IAAiB,SAAA,MAAS,MAAM,OAAM,CAAA,CAAA;oBAWhE,YASoC,uBATpC,WASoC;MAPjC,IAAI,UAAA,MAAU;MACd,aAAA,QAAA;MACA,UAAA,QAAA;MACA,YAAA,QAAA;MACA,MAAM,QAAA,SAAO,EAAA;MACb,uBAAuB;MACxB,OAAM;QACN,WAAM,kBAAyB,MAAP,OAAM,CAAA,EAAA,MAAA,IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA,CAAA,EAAA,CAAA,CAAA,OARtB,iBAAgB,UAAA,CAAA,CAAA,CAAA;oBAW1B,YAQoC,uBARpC,WAQoC;MANjC,IAAI,UAAA,MAAU;MACd,aAAA,QAAA;MACA,UAAA,QAAA;MACA,YAAA,QAAA;MACA,MAAM,QAAA,SAAO,EAAA;MACd,OAAM;QACN,WAAM,kBAAyB,MAAP,OAAM,CAAA,EAAA,MAAA,IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA,CAAA,EAAA,CAAA,CAAA,OAPtB,iBAAgB,UAAA,CAAA,CAAA,CAAA;oBAU1B,YAQmC,uBARnC,WAQmC;MANhC,IAAI,UAAA,MAAU;MACd,aAAA,QAAA;MACA,UAAA,QAAA;MACA,YAAA,QAAA;MACA,MAAM,SAAA,MAAS,SAAK,EAAA;MACrB,OAAM;QACN,WAAM,kBAAwB,MAAN,MAAK,CAAA,EAAA,MAAA,IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA,CAAA,EAAA,CAAA,CAAA,OAPrB,iBAAgB,QAAA,CAAA,CAAA,CAAA;oBAU1B,YAU0C,qBAAA;MARvC,IAAI,UAAA,MAAU;MACd,aAAA,QAAA;MACA,YAAA,QAAA;MACA,aAAa,MAAA,eAAc,CAAC,QAAA,UAAU,YAAW;MACjD,iCAAA,QAAA;MACD,OAAM;MACL,wBAAoB;MACpB,sBAAkB;MAClB,kBAAc;;;;;;;kBATP,iBAAgB,OAAA,IAAY,MAAA,kBAAiB,CAAC,QAAA,OAAM,CAAA,CAAA,CAAA;uBAY9D,mBAUsB,UAAA,MAAA,WATM,QAAA,UAAlB,QAAQ,UAAK;0BADvB,YAUsB,MAAA,oBAAA,EAAA,EARnB,KAAK,OAAK,EAAA;8BAOgD,CAJnD,QAAQ,YAAY,UAAA,gBAAA,WAAA,EAF5B,YAM2D,wBALpD,OAAO,WAAW,QAAQ,UAAS,EAD1C,WAM2D;;QAFxD,WAAA,QAAA;6BACO,OAAO,WAAW,QAAQ,iBAAe,EAChD,gCAA4B,0BAAwB,CAAA,EAAA,MAAA,IAAA,CAAA,YAAA,CAAA,GAAA,CAAA,CAAA,OAH7C,eAAA,UAAc,MAAA,CAAA,CAAA,GAAA,mBAAA,IAAA,KAAA,CAAA,CAAA;;;;+BAO1B,mBAAyB,OAAA,EAApB,OAAM,aAAW,EAAA,MAAA,GAAA;oBAEtB,YAgB0C,4BAAA;MAdvC,eAAA,QAAA;MACA,UAAA,QAAA;MACA,eAAA,cAAA;MACD,aAAY;MACX,QAAA,QAAA;MACA,WAAA,QAAA;MACA,MAAA,QAAA;MACA,iBAAiB,QAAA;MACjB,gBAAA,QAAA;MACA,qBAAgC,MAAA,eAAc,CAAC,QAAA,UAAU,YAAW,GAAA,oCAAgE,QAAA;MAKpI,gBAAgB,QAAA,UAAU,KAAA;;;;;;;;;;;;kBAfnB,eAAA,UAAc,MAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"RequestBlock.vue.script.js","names":[],"sources":["../../../../src/v2/blocks/request-block/RequestBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarErrorBoundary } from '@scalar/components'\nimport { canMethodHaveBody } from '@scalar/helpers/http/can-method-have-body'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport { REGEX } from '@scalar/helpers/regex/regex-helpers'\nimport { replaceEnvVariables } from '@scalar/helpers/regex/replace-variables'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n AuthMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n filterGlobalCookie,\n getEnvironmentVariables,\n getExample,\n getResolvedUrl,\n type MergedSecuritySchemes,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCookie } from '@scalar/workspace-store/schemas/extensions/general/x-scalar-cookies'\nimport type {\n OpenApiDocument,\n OperationObject,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, useId, watch } from 'vue'\n\nimport SectionFilter from '@/components/SectionFilter.vue'\nimport ViewLayoutSection from '@/components/ViewLayout/ViewLayoutSection.vue'\nimport type { ClientOptionGroup } from '@/v2/blocks/operation-code-sample'\nimport RequestBody from '@/v2/blocks/request-block/components/RequestBody.vue'\nimport RequestCodeSnippet from '@/v2/blocks/request-block/components/RequestCodeSnippet.vue'\nimport RequestParams from '@/v2/blocks/request-block/components/RequestParams.vue'\nimport type { TableRow } from '@/v2/blocks/request-block/components/RequestTableRow.vue'\nimport { createParameterHandlers } from '@/v2/blocks/request-block/helpers/create-parameter-handlers'\nimport { getParameterSchema } from '@/v2/blocks/request-block/helpers/get-parameter-schema'\nimport { groupBy } from '@/v2/blocks/request-block/helpers/group-by'\nimport { isParamDisabled } from '@/v2/blocks/request-block/helpers/is-param-disabled'\nimport { AuthSelector } from '@/v2/blocks/scalar-auth-selector-block'\nimport type { OAuth2Options } from '@/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue'\nimport type { ClientLayout } from '@/v2/types/layout'\n\ntype Filter =\n | 'All'\n | 'Auth'\n | 'Variables'\n | 'Cookies'\n | 'Headers'\n | 'Query'\n | 'Body'\n\nexport type RequestBlockProps = {\n authMeta: AuthMeta\n clientOptions: ClientOptionGroup[]\n environment: XScalarEnvironment\n eventBus: WorkspaceEventBus\n exampleKey: string\n workspaceCookies: XScalarCookie[]\n documentCookies: XScalarCookie[]\n layout: ClientLayout\n method: HttpMethod\n operation: OperationObject\n path: string\n plugins: ClientPlugin[]\n proxyUrl: string\n securityRequirements: OpenApiDocument['security']\n securitySchemes: MergedSecuritySchemes\n requestBodyCompositionSelection?: Record<string, number>\n selectedClient: WorkspaceStore['workspace']['x-scalar-default-client']\n selectedSecurity: SelectedSecurity\n selectedSecuritySchemes: SecuritySchemeObjectSecret[]\n server: ServerObject | null\n defaultHeaders: Record<string, string>\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n}\n\nconst {\n authMeta = { type: 'document' },\n clientOptions,\n environment,\n eventBus,\n exampleKey,\n workspaceCookies,\n documentCookies,\n layout,\n method,\n operation,\n path,\n plugins,\n proxyUrl,\n requestBodyCompositionSelection,\n securityRequirements,\n securitySchemes,\n selectedClient,\n selectedSecurity,\n selectedSecuritySchemes,\n server,\n defaultHeaders: autoGeneratedHeaders,\n} = defineProps<RequestBlockProps>()\n\n/** Operation metadata used across event emissions */\nconst meta = computed(() => ({\n method,\n path,\n exampleKey,\n}))\n\n/** Parameters grouped by type (path, query, header, cookie) */\nconst sections = computed(() =>\n groupBy(\n operation.parameters?.map((param) => getResolvedRef(param)) ?? [],\n 'in',\n (param) => {\n const example = getExample(param, exampleKey, undefined)\n\n return {\n name: param.name,\n value: example?.value ?? '',\n description: param.description,\n schema: getParameterSchema(param),\n isRequired: param.required,\n isDisabled: isParamDisabled(param, example),\n originalParameter: param,\n } as TableRow\n },\n ),\n)\n\n// Generate a reverse map for fast lookup of headers by the name\nconst headersMap = computed(() =>\n groupBy(\n sections.value.header?.map((it) => ({\n ...it,\n name: it.name.toLowerCase(),\n })) ?? [],\n 'name',\n ),\n)\n\nconst defaultHeaders = computed(() => {\n const disableParameters =\n operation['x-scalar-disable-parameters']?.['default-headers']?.[\n exampleKey\n ] ?? {}\n\n const isOverridden = (name: string) => {\n const header = headersMap.value[name.toLowerCase()]?.[0]\n\n if (!header) {\n return false\n }\n\n return !header.isDisabled\n }\n\n return Object.entries(autoGeneratedHeaders).map(([name, value]) => {\n return {\n name,\n value,\n schema: undefined,\n isOverridden: isOverridden(name),\n isReadonly: true,\n isDisabled: disableParameters[name.toLowerCase()] ?? false,\n } satisfies TableRow\n })\n})\n\nconst headers = computed(() => [\n ...defaultHeaders.value,\n ...(sections.value.header ?? []),\n])\n\nconst defaultCookies = computed(() => {\n const environmentVariables = getEnvironmentVariables(environment)\n const resolvedUrl = getResolvedUrl({\n server,\n path,\n })\n const url = replaceEnvVariables(resolvedUrl, environmentVariables)\n\n const disabledGlobalCookies =\n operation['x-scalar-disable-parameters']?.['global-cookies']?.[\n exampleKey\n ] ?? {}\n\n const transform = (\n cookie: XScalarCookie,\n location: 'document' | 'workspace',\n ) => {\n return {\n name: cookie.name,\n value: cookie.value,\n globalRoute: { page: location, path: 'cookies' } as const,\n isReadonly: true,\n isDisabled: disabledGlobalCookies[cookie.name.toLowerCase()] ?? false,\n } satisfies TableRow\n }\n\n const globalCookies = [\n {\n location: 'workspace',\n cookies: workspaceCookies,\n },\n {\n location: 'document',\n cookies: documentCookies,\n },\n ] as const\n\n return globalCookies.flatMap(({ location, cookies }) => {\n return cookies\n .filter((cookie) =>\n filterGlobalCookie({\n cookie,\n url,\n disabledGlobalCookies: {},\n }),\n )\n .map((cookie) => transform(cookie, location))\n })\n})\n\nconst cookies = computed(() => [\n ...(defaultCookies.value ?? []),\n ...(sections.value.cookie ?? []),\n])\n\n/** Currently selected filter for the request sections */\nconst selectedFilter = ref<Filter>('All')\n\n/** Available operation sections */\nconst OPERATION_SECTIONS: readonly Filter[] = [\n 'Auth',\n 'Variables',\n 'Cookies',\n 'Headers',\n 'Query',\n 'Body',\n] as const\n\n/**\n * Pre-generated stable IDs for all possible filter sections.\n * These are created once at setup time to avoid regenerating IDs on re-render.\n */\nconst sectionIds: Record<Filter, string> = {\n All: useId(),\n Auth: useId(),\n Variables: useId(),\n Cookies: useId(),\n Headers: useId(),\n Query: useId(),\n Body: useId(),\n}\n\n/** Filters available based on operation state */\nconst filters = computed<Filter[]>(() => {\n const availableFilters = new Set<Filter>(['All', ...OPERATION_SECTIONS])\n\n if (!sections.value.path?.length) {\n availableFilters.delete('Variables')\n }\n if (!canMethodHaveBody(method)) {\n availableFilters.delete('Body')\n }\n if (isAuthHidden.value) {\n availableFilters.delete('Auth')\n }\n\n return [...availableFilters]\n})\n\n/**\n * Map available filters to their pre-generated stable IDs.\n * Only includes IDs for filters that are currently available.\n */\nconst filterIds = computed(\n () =>\n Object.fromEntries(\n filters.value.map((section) => [section, sectionIds[section]]),\n ) as Record<Filter, string>,\n)\n\n/**\n * Hide auth selector in readonly mode when no security schemes are defined.\n * This keeps the UI clean when there are no authentication options available.\n */\nconst isAuthHidden = computed(\n () => layout === 'modal' && !Object.keys(securitySchemes ?? {}).length,\n)\n\n/**\n * Keep auth available for unauthenticated operations, but collapse it by default\n * in readonly modal layouts unless a requirement or manual selection exists.\n */\nconst isAuthDefaultOpen = computed(\n () =>\n layout !== 'modal' ||\n Boolean(\n securityRequirements?.length || selectedSecurity.selectedSchemes.length,\n ),\n)\n\n/** Get a sensible placeholder for the request name input */\nconst requestNamePlaceholder = computed(() => {\n if (operation.summary) {\n return operation.summary\n }\n const cleanPath = path.replace(REGEX.PROTOCOL, '')\n return cleanPath || 'Request Name'\n})\n\n/** Check if the section should be shown based on the selected filter */\nconst isSectionVisible = (section: Filter): boolean => {\n return selectedFilter.value === 'All' || selectedFilter.value === section\n}\n\n/**\n * Reset filter to 'All' if Body filter is selected but method changes to one that cannot have a body.\n * This prevents showing an empty Body section when switching methods.\n */\nwatch(\n () => method,\n (newMethod) => {\n if (selectedFilter.value === 'Body' && !canMethodHaveBody(newMethod)) {\n selectedFilter.value = 'All'\n }\n },\n)\n\n/** Handle operation summary updates */\nconst handleSummaryUpdate = (event: Event): void => {\n const summary = (event.target as HTMLInputElement).value\n eventBus.emit('operation:update:meta', {\n meta: meta.value,\n payload: { summary: summary.trim() },\n })\n}\n\n/** Parameter handlers */\nconst parameterHandlers = computed(() => ({\n path: createParameterHandlers('path', eventBus, meta.value, {\n context: sections.value.path ?? [],\n }),\n cookie: createParameterHandlers('cookie', eventBus, meta.value, {\n context: cookies.value ?? [],\n globalParameters: defaultCookies.value.length,\n }),\n header: createParameterHandlers('header', eventBus, meta.value, {\n context: headers.value,\n defaultParameters: defaultHeaders.value.length,\n }),\n query: createParameterHandlers('query', eventBus, meta.value, {\n context: sections.value.query ?? [],\n }),\n}))\n\n/** Handle request body content type update */\nconst handleUpdateContentType = (payload: { value: string }): void =>\n eventBus.emit('operation:update:requestBody:contentType', {\n payload: { contentType: payload.value },\n meta: meta.value,\n })\n\n/** Handle request body value update */\nconst handleUpdateBodyValue = ({\n payload,\n contentType,\n}: Pick<\n ApiReferenceEvents['operation:update:requestBody:value'],\n 'payload' | 'contentType'\n>): void => {\n const debounceKey =\n typeof payload === 'string'\n ? `update:requestBody:value-${contentType}`\n : undefined\n\n eventBus.emit(\n 'operation:update:requestBody:value',\n {\n payload,\n contentType,\n meta: meta.value,\n },\n {\n debounceKey,\n },\n )\n}\n\n/** Handle request body value update */\nconst handleUpdateBodyFormValue = ({\n payload,\n contentType,\n}: Pick<\n ApiReferenceEvents['operation:update:requestBody:formValue'],\n 'payload' | 'contentType'\n>): void => {\n const debounceKey = `update:requestBody:${contentType}-form-value`\n\n eventBus.emit(\n 'operation:update:requestBody:formValue',\n {\n payload: payload.map((row) => unpackProxyObject(row, { depth: 1 })),\n contentType,\n meta: meta.value,\n },\n {\n debounceKey,\n },\n )\n}\n\nconst labelRequestNameId = useId()\n\nconst globalCookies = computed(() => [...workspaceCookies, ...documentCookies])\n\n/** Allow updating the operation extensions for the plugins */\nconst updateOperationExtension = (\n payload: ApiReferenceEvents['operation:update:extension']['payload'],\n): void =>\n eventBus.emit('operation:update:extension', { payload, meta: meta.value })\n</script>\n<template>\n <ViewLayoutSection :aria-label=\"`Request: ${operation.summary}`\">\n <template #title>\n <div\n class=\"group pointer-events-none flex flex-1 items-center gap-1 lg:pr-24\">\n <label\n v-if=\"layout !== 'modal'\"\n class=\"pointer-events-auto absolute top-0 left-0 h-full w-full cursor-text opacity-0\"\n :for=\"labelRequestNameId\" />\n <input\n v-if=\"layout !== 'modal'\"\n :id=\"labelRequestNameId\"\n class=\"text-c-1 group-hover-input pointer-events-auto relative z-10 -ml-0.5 h-8 w-full rounded pl-1.25 has-[:focus-visible]:outline md:-ml-1.25\"\n :placeholder=\"requestNamePlaceholder\"\n :value=\"operation.summary\"\n @blur=\"handleSummaryUpdate\" />\n <span\n v-else\n class=\"text-c-1 flex h-8 items-center\">\n {{ operation.summary }}\n </span>\n </div>\n <SectionFilter\n v-model=\"selectedFilter\"\n :filterIds=\"filterIds\"\n :filters=\"filters\" />\n </template>\n\n <div\n :id=\"filterIds.All\"\n class=\"request-section-content custom-scroll relative flex flex-1 flex-col\"\n :role=\"selectedFilter === 'All' ? 'tabpanel' : 'none'\">\n <!-- Auth Selector -->\n <AuthSelector\n v-show=\"isSectionVisible('Auth') && !isAuthHidden\"\n :id=\"filterIds.Auth\"\n :createAnySecurityScheme=\"layout !== 'modal'\"\n :defaultOpen=\"isAuthDefaultOpen\"\n :environment\n :eventBus\n :meta=\"authMeta\"\n :options\n :proxyUrl\n :securityRequirements\n :securitySchemes\n :selectedSecurity\n :selectedSecuritySchemes\n :server\n title=\"Authentication\" />\n\n <!-- Variables (Path Parameters) -->\n <RequestParams\n v-show=\"isSectionVisible('Variables') && sections.path?.length\"\n :id=\"filterIds.Variables\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"sections.path ?? []\"\n :showAddRowPlaceholder=\"false\"\n title=\"Variables\"\n v-on=\"parameterHandlers.path\" />\n\n <!-- Cookies -->\n <RequestParams\n v-show=\"isSectionVisible('Cookies')\"\n :id=\"filterIds.Cookies\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"cookies ?? []\"\n :showAddRowPlaceholder=\"true\"\n title=\"Cookies\"\n v-on=\"parameterHandlers.cookie\" />\n\n <!-- Headers -->\n <RequestParams\n v-show=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"headers ?? []\"\n title=\"Headers\"\n v-on=\"parameterHandlers.header\" />\n\n <!-- Query Parameters -->\n <RequestParams\n v-show=\"isSectionVisible('Query')\"\n :id=\"filterIds.Query\"\n :environment\n :eventBus\n :exampleKey\n :rows=\"sections.query ?? []\"\n title=\"Query Parameters\"\n v-on=\"parameterHandlers.query\" />\n\n <!-- Request Body -->\n <RequestBody\n v-show=\"isSectionVisible('Body') && canMethodHaveBody(method)\"\n :id=\"filterIds.Body\"\n :environment\n :exampleKey\n :requestBody=\"getResolvedRef(operation.requestBody)\"\n :requestBodyCompositionSelection\n title=\"Request Body\"\n @update:contentType=\"handleUpdateContentType\"\n @update:formValue=\"handleUpdateBodyFormValue\"\n @update:value=\"handleUpdateBodyValue\" />\n\n <!-- Inject request section plugin components -->\n <ScalarErrorBoundary\n v-for=\"(plugin, index) in plugins\"\n :key=\"index\">\n <component\n :is=\"plugin.components.request.component\"\n v-if=\"plugin?.components?.request\"\n v-show=\"selectedFilter === 'All'\"\n :operation\n v-bind=\"plugin.components.request.additionalProps\"\n @operation:update:extension=\"updateOperationExtension\" />\n </ScalarErrorBoundary>\n\n <!-- Spacer -->\n <div class=\"flex grow\" />\n <!-- Code Snippet -->\n <RequestCodeSnippet\n v-show=\"selectedFilter === 'All'\"\n :clientOptions\n :eventBus\n :globalCookies\n integration=\"client\"\n :method\n :operation\n :path\n :securitySchemes=\"selectedSecuritySchemes\"\n :selectedClient\n :selectedContentType=\"\n getResolvedRef(operation.requestBody)?.[\n 'x-scalar-selected-content-type'\n ]?.[exampleKey]\n \"\n :selectedServer=\"server ?? undefined\" />\n </div>\n </ViewLayoutSection>\n</template>\n<style scoped>\n.request-section-content {\n --scalar-border-width: 0.5px;\n}\n.request-section-content-filter {\n box-shadow: 0 -10px 0 10px var(--scalar-background-1);\n}\n.request-item:focus-within .request-meta-buttons {\n opacity: 1;\n}\n.group-hover-input {\n border-width: var(--scalar-border-width);\n border-color: transparent;\n}\n.group:hover .group-hover-input {\n background: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n border-color: var(--scalar-border-color);\n}\n.group-hover-input:focus {\n background: transparent !important;\n border-color: var(--scalar-border-color) !important;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthSelector.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarComboboxMultiselect,\n ScalarIconButton,\n ScalarListboxCheckbox,\n useModal,\n type Icon,\n type ScalarButton as ScalarButtonType,\n} from '@scalar/components'\nimport { ScalarIconCaretDown, ScalarIconTrash } from '@scalar/icons'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type {\n AuthMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n isAuthOptional,\n type MergedSecuritySchemes,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type {\n OpenApiDocument,\n SecurityRequirementObject,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, useId } from 'vue'\n\nimport DeleteRequestAuthModal from '@/v2/blocks/scalar-auth-selector-block/components/DeleteRequestAuthModal.vue'\nimport type { OAuth2Options } from '@/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue'\nimport {\n formatComplexScheme,\n formatScheme,\n getSecuritySchemeOptions,\n type SecuritySchemeOption,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/security-scheme'\nimport { CollapsibleSection } from '@/v2/components/layout'\n\nimport RequestAuthDataTable from './RequestAuthDataTable.vue'\n\nconst {\n environment,\n eventBus,\n createAnySecurityScheme = false,\n defaultOpen = true,\n isStatic = false,\n meta,\n proxyUrl,\n securityRequirements,\n securitySchemes,\n selectedSecurity,\n server,\n title,\n options,\n} = defineProps<{\n environment: XScalarEnvironment\n eventBus: WorkspaceEventBus\n /** Allows adding authentication which is not in the document */\n createAnySecurityScheme?: boolean\n /** Whether the authentication disclosure should start expanded */\n defaultOpen?: boolean\n /** Creates a static disclosure that cannot be collapsed */\n isStatic?: boolean\n meta: AuthMeta\n proxyUrl: string\n securityRequirements: OpenApiDocument['security']\n securitySchemes: MergedSecuritySchemes\n selectedSecurity: SelectedSecurity | undefined\n server: ServerObject | null\n title: string\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n}>()\n\nconst titleId = useId()\nconst comboboxButtonRef = ref<typeof ScalarButtonType | null>(null)\nconst isDisclosureOpen = ref(false)\n\nconst deleteModal = useModal()\nconst schemeToDelete = ref<{\n label: string\n payload: SecurityRequirementObject\n} | null>(null)\n\n/**\n * Determines if authentication is required or optional\n *\n * Auth is optional when there is an empty security requirement and no complex requirements.\n * Complex requirements have multiple auth schemes combined (e.g., API key + OAuth).\n */\nconst authIndicator = computed<{ icon: Icon; text: string } | null>(() => {\n if (!securityRequirements?.length) {\n return null\n }\n\n const isOptional = isAuthOptional(securityRequirements)\n\n return {\n icon: isOptional ? 'Unlock' : 'Lock',\n text: isOptional ? 'Optional' : 'Required',\n }\n})\n\n/** All available auth scheme options for the dropdown */\nconst availableSchemeOptions = computed(() =>\n getSecuritySchemeOptions(\n securityRequirements ?? [],\n securitySchemes ?? {},\n selectedSecurity?.selectedSchemes ?? [],\n createAnySecurityScheme,\n ),\n)\n\n/** Currently active auth schemes selected for this operation or collection */\nconst activeSchemeOptions = computed<SecuritySchemeOption[]>(() => {\n const schemes = selectedSecurity?.selectedSchemes\n if (!schemes?.length) {\n return []\n }\n\n return schemes.flatMap((requirement) => {\n const schemeNames = Object.keys(requirement)\n\n if (schemeNames.length === 0) {\n return []\n }\n\n // Complex auth requirement with multiple schemes\n if (schemeNames.length > 1) {\n return formatComplexScheme(requirement)\n }\n\n // Simple auth requirement with single scheme\n const schemeName = schemeNames[0]\n if (!schemeName) {\n return []\n }\n\n const scheme = getResolvedRef(securitySchemes?.[schemeName])\n if (!scheme) {\n return []\n }\n\n return formatScheme({\n name: schemeName,\n value: requirement,\n })\n })\n})\n\n/**\n * Opens the combobox dropdown when clicking the auth indicator badge.\n * Prevents the disclosure from toggling if it is already open.\n */\nconst handleAuthIndicatorClick = (event: Event): void => {\n if (isDisclosureOpen.value) {\n event.stopPropagation()\n }\n comboboxButtonRef.value?.$el.click()\n}\n\n/**\n * Updates the selected auth schemes.\n * Separates existing schemes from newly created ones for the event payload.\n */\nconst handleSchemeSelection = (selected: SecuritySchemeOption[]): void => {\n const existingSchemes = selected\n .filter((option) => option.payload === undefined)\n .map((option) => unpackProxyObject(option.value, { depth: 2 }))\n\n const newSchemes = selected\n .filter((option) => option.payload !== undefined)\n .map((option) => ({\n name: option.label,\n scheme: option.payload!,\n }))\n\n eventBus.emit('auth:update:selected-security-schemes', {\n selectedRequirements: existingSchemes,\n newSchemes,\n meta,\n })\n}\n\n/** Shows the delete confirmation modal for the selected scheme */\nconst handleDeleteRequest = (option: {\n label: string\n value: SecurityRequirementObject\n}): void => {\n schemeToDelete.value = { label: option.label, payload: option.value }\n deleteModal.show()\n}\n\n/** Deletes the selected auth scheme after confirmation */\nconst handleDeleteConfirm = (): void => {\n if (!schemeToDelete.value) {\n return\n }\n\n eventBus.emit('auth:delete:security-scheme', {\n names: Object.keys(schemeToDelete.value.payload),\n })\n\n schemeToDelete.value = null\n deleteModal.hide()\n}\n\ndefineExpose({\n authIndicator,\n selectedSchemeOptions: activeSchemeOptions,\n schemeOptions: availableSchemeOptions,\n})\n</script>\n<template>\n <CollapsibleSection\n class=\"group/params relative\"\n :defaultOpen\n :isStatic=\"isStatic\"\n :itemCount=\"activeSchemeOptions.length\"\n @update:modelValue=\"(open) => (isDisclosureOpen = open)\">\n <template #title>\n <div\n :id=\"titleId\"\n class=\"inline-flex items-center gap-0.5 leading-[20px]\">\n <span>{{ title }}</span>\n\n <span\n v-if=\"authIndicator\"\n class=\"text-c-3 hover:bg-b-3 hover:text-c-1 -my-0.5 -mr-1 cursor-pointer rounded px-1 py-0.5 leading-[normal] font-normal\"\n :class=\"{ 'text-c-1': authIndicator.text === 'Required' }\"\n data-testid=\"auth-indicator\"\n @click=\"handleAuthIndicatorClick\">\n {{ authIndicator.text }}\n </span>\n </div>\n </template>\n\n <!-- Auth Dropdown (hidden when only one scheme is available) -->\n <template #actions>\n <ScalarComboboxMultiselect\n class=\"w-72 text-xs\"\n :modelValue=\"activeSchemeOptions\"\n multiple\n :options=\"availableSchemeOptions\"\n placement=\"bottom-end\"\n teleport\n @delete=\"handleDeleteRequest\"\n @update:modelValue=\"handleSchemeSelection\">\n <ScalarButton\n ref=\"comboboxButtonRef\"\n :aria-describedby=\"titleId\"\n class=\"group/combobox-button hover:text-c-1 text-c-2 flex h-fit w-full items-center gap-1 px-0.75 py-0.25 text-base font-normal\"\n variant=\"ghost\">\n <!-- Single auth scheme selected -->\n <template v-if=\"activeSchemeOptions.length === 1\">\n <span class=\"sr-only\">Selected Auth Type:</span>\n {{ activeSchemeOptions[0]?.label }}\n </template>\n\n <!-- Multiple auth schemes selected -->\n <template v-else-if=\"activeSchemeOptions.length > 1\">\n Multiple\n <span class=\"sr-only\">Auth Types Selected</span>\n </template>\n\n <!-- No auth schemes selected -->\n <template v-else>\n <span class=\"sr-only\">Select</span>\n Auth Type\n </template>\n\n <ScalarIconCaretDown\n class=\"size-3 shrink-0 transition-transform duration-100 group-aria-expanded/combobox-button:rotate-180\"\n weight=\"bold\" />\n </ScalarButton>\n\n <template #option=\"{ option, selected }\">\n <ScalarListboxCheckbox\n multiselect\n :selected=\"selected\" />\n <div class=\"min-w-0 flex-1 truncate\">\n {{ option.label }}\n </div>\n <ScalarIconButton\n v-if=\"option.isDeletable\"\n class=\"-m-0.5 shrink-0 p-0.5 opacity-0 group-hover/item:opacity-100\"\n :icon=\"ScalarIconTrash\"\n :label=\"`Delete ${option.label}`\"\n size=\"xs\"\n @click.stop=\"handleDeleteRequest(option)\" />\n </template>\n </ScalarComboboxMultiselect>\n </template>\n\n <!-- Auth Table -->\n <RequestAuthDataTable\n :activeAuthIndex=\"selectedSecurity?.selectedIndex ?? 0\"\n :environment\n :eventBus\n :isStatic\n :meta\n :options\n :proxyUrl\n :securitySchemes\n :selectedSchemeOptions=\"activeSchemeOptions\"\n :server />\n\n <!-- Delete Auth Modal -->\n <DeleteRequestAuthModal\n v-if=\"schemeToDelete\"\n :label=\"schemeToDelete.label\"\n :scheme=\"schemeToDelete\"\n :state=\"deleteModal\"\n @close=\"deleteModal.hide()\"\n @delete=\"handleDeleteConfirm\" />\n </CollapsibleSection>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuthSelector.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarButton,\n ScalarComboboxMultiselect,\n ScalarIconButton,\n ScalarListboxCheckbox,\n useModal,\n type Icon,\n type ScalarButton as ScalarButtonType,\n} from '@scalar/components'\nimport { ScalarIconCaretDown, ScalarIconTrash } from '@scalar/icons'\nimport type { SelectedSecurity } from '@scalar/workspace-store/entities/auth'\nimport type {\n AuthMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { unpackProxyObject } from '@scalar/workspace-store/helpers/unpack-proxy'\nimport {\n isAuthOptional,\n type MergedSecuritySchemes,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type {\n OpenApiDocument,\n SecurityRequirementObject,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, useId } from 'vue'\n\nimport DeleteRequestAuthModal from '@/v2/blocks/scalar-auth-selector-block/components/DeleteRequestAuthModal.vue'\nimport type { OAuth2Options } from '@/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue'\nimport {\n formatComplexScheme,\n formatScheme,\n getSecuritySchemeOptions,\n type SecuritySchemeOption,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/security-scheme'\nimport { CollapsibleSection } from '@/v2/components/layout'\n\nimport RequestAuthDataTable from './RequestAuthDataTable.vue'\n\nconst {\n environment,\n eventBus,\n createAnySecurityScheme = false,\n defaultOpen = true,\n isStatic = false,\n meta,\n proxyUrl,\n securityRequirements,\n securitySchemes,\n selectedSecurity,\n server,\n title,\n options,\n} = defineProps<{\n environment: XScalarEnvironment\n eventBus: WorkspaceEventBus\n /** Allows adding authentication which is not in the document */\n createAnySecurityScheme?: boolean\n /** Whether the authentication disclosure should start expanded */\n defaultOpen?: boolean\n /** Creates a static disclosure that cannot be collapsed */\n isStatic?: boolean\n meta: AuthMeta\n proxyUrl: string\n securityRequirements: OpenApiDocument['security']\n securitySchemes: MergedSecuritySchemes\n selectedSecurity: SelectedSecurity | undefined\n server: ServerObject | null\n title: string\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n}>()\n\nconst titleId = useId()\nconst comboboxButtonRef = ref<typeof ScalarButtonType | null>(null)\nconst isDisclosureOpen = ref(false)\n\nconst deleteModal = useModal()\nconst schemeToDelete = ref<{\n label: string\n payload: SecurityRequirementObject\n} | null>(null)\n\n/**\n * Determines if authentication is required or optional\n *\n * Auth is optional when there is an empty security requirement and no complex requirements.\n * Complex requirements have multiple auth schemes combined (e.g., API key + OAuth).\n */\nconst authIndicator = computed<{ icon: Icon; text: string } | null>(() => {\n if (!securityRequirements?.length) {\n return null\n }\n\n const isOptional = isAuthOptional(securityRequirements)\n\n return {\n icon: isOptional ? 'Unlock' : 'Lock',\n text: isOptional ? 'Optional' : 'Required',\n }\n})\n\n/** All available auth scheme options for the dropdown */\nconst availableSchemeOptions = computed(() =>\n getSecuritySchemeOptions(\n securityRequirements ?? [],\n securitySchemes ?? {},\n selectedSecurity?.selectedSchemes ?? [],\n createAnySecurityScheme,\n ),\n)\n\n/** Currently active auth schemes selected for this operation or collection */\nconst activeSchemeOptions = computed<SecuritySchemeOption[]>(() => {\n const schemes = selectedSecurity?.selectedSchemes\n if (!schemes?.length) {\n return []\n }\n\n return schemes.flatMap((requirement) => {\n const schemeNames = Object.keys(requirement)\n\n if (schemeNames.length === 0) {\n return []\n }\n\n // Complex auth requirement with multiple schemes\n if (schemeNames.length > 1) {\n return formatComplexScheme(requirement)\n }\n\n // Simple auth requirement with single scheme\n const schemeName = schemeNames[0]\n if (!schemeName) {\n return []\n }\n\n const scheme = getResolvedRef(securitySchemes?.[schemeName])\n if (!scheme) {\n return []\n }\n\n return formatScheme({\n name: schemeName,\n value: requirement,\n })\n })\n})\n\n/**\n * Opens the combobox dropdown when clicking the auth indicator badge.\n * Prevents the disclosure from toggling if it is already open.\n */\nconst handleAuthIndicatorClick = (event: Event): void => {\n if (isDisclosureOpen.value) {\n event.stopPropagation()\n }\n comboboxButtonRef.value?.$el.click()\n}\n\n/**\n * Updates the selected auth schemes.\n * Separates existing schemes from newly created ones for the event payload.\n */\nconst handleSchemeSelection = (selected: SecuritySchemeOption[]): void => {\n const existingSchemes = selected\n .filter((option) => option.payload === undefined)\n .map((option) => unpackProxyObject(option.value, { depth: 2 }))\n\n const newSchemes = selected\n .filter((option) => option.payload !== undefined)\n .map((option) => ({\n name: option.label,\n scheme: option.payload!,\n }))\n\n eventBus.emit('auth:update:selected-security-schemes', {\n selectedRequirements: existingSchemes,\n newSchemes,\n meta,\n })\n}\n\n/** Shows the delete confirmation modal for the selected scheme */\nconst handleDeleteRequest = (option: {\n label: string\n value: SecurityRequirementObject\n}): void => {\n schemeToDelete.value = { label: option.label, payload: option.value }\n deleteModal.show()\n}\n\n/** Deletes the selected auth scheme after confirmation */\nconst handleDeleteConfirm = (): void => {\n if (!schemeToDelete.value) {\n return\n }\n\n eventBus.emit('auth:delete:security-scheme', {\n names: Object.keys(schemeToDelete.value.payload),\n })\n\n schemeToDelete.value = null\n deleteModal.hide()\n}\n\ndefineExpose({\n authIndicator,\n selectedSchemeOptions: activeSchemeOptions,\n schemeOptions: availableSchemeOptions,\n})\n</script>\n<template>\n <CollapsibleSection\n class=\"group/params relative\"\n :defaultOpen\n :isStatic=\"isStatic\"\n :itemCount=\"activeSchemeOptions.length\"\n @update:modelValue=\"(open) => (isDisclosureOpen = open)\">\n <template #title>\n <div\n :id=\"titleId\"\n class=\"inline-flex items-center gap-0.5 leading-[20px]\">\n <span>{{ title }}</span>\n\n <span\n v-if=\"authIndicator\"\n class=\"text-c-3 hover:bg-b-3 hover:text-c-1 -my-0.5 -mr-1 cursor-pointer rounded px-1 py-0.5 leading-[normal] font-normal\"\n :class=\"{ 'text-c-1': authIndicator.text === 'Required' }\"\n data-testid=\"auth-indicator\"\n @click=\"handleAuthIndicatorClick\">\n {{ authIndicator.text }}\n </span>\n </div>\n </template>\n\n <!-- Auth Dropdown (hidden when only one scheme is available) -->\n <template #actions>\n <ScalarComboboxMultiselect\n class=\"w-72 text-xs\"\n :modelValue=\"activeSchemeOptions\"\n multiple\n :options=\"availableSchemeOptions\"\n placement=\"bottom-end\"\n teleport\n @delete=\"handleDeleteRequest\"\n @update:modelValue=\"handleSchemeSelection\">\n <ScalarButton\n ref=\"comboboxButtonRef\"\n :aria-describedby=\"titleId\"\n class=\"group/combobox-button hover:text-c-1 text-c-2 flex h-fit w-full items-center gap-1 px-0.75 py-0.25 text-base font-normal\"\n variant=\"ghost\">\n <!-- Single auth scheme selected -->\n <template v-if=\"activeSchemeOptions.length === 1\">\n <span class=\"sr-only\">Selected Auth Type:</span>\n {{ activeSchemeOptions[0]?.label }}\n </template>\n\n <!-- Multiple auth schemes selected -->\n <template v-else-if=\"activeSchemeOptions.length > 1\">\n Multiple\n <span class=\"sr-only\">Auth Types Selected</span>\n </template>\n\n <!-- No auth schemes selected -->\n <template v-else>\n <span class=\"sr-only\">Select</span>\n Auth Type\n </template>\n\n <ScalarIconCaretDown\n class=\"size-3 shrink-0 transition-transform duration-100 group-aria-expanded/combobox-button:rotate-180\"\n weight=\"bold\" />\n </ScalarButton>\n\n <template #option=\"{ option, selected }\">\n <ScalarListboxCheckbox\n multiselect\n :selected=\"selected\" />\n <div class=\"min-w-0 flex-1 truncate\">\n {{ option.label }}\n </div>\n <ScalarIconButton\n v-if=\"option.isDeletable\"\n class=\"-m-0.5 shrink-0 p-0.5 opacity-0 group-hover/item:opacity-100\"\n :icon=\"ScalarIconTrash\"\n :label=\"`Delete ${option.label}`\"\n size=\"xs\"\n @click.stop=\"handleDeleteRequest(option)\" />\n </template>\n </ScalarComboboxMultiselect>\n </template>\n\n <!-- Auth Table -->\n <RequestAuthDataTable\n :activeAuthIndex=\"selectedSecurity?.selectedIndex ?? 0\"\n :environment\n :eventBus\n :isStatic\n :meta\n :options\n :proxyUrl\n :securitySchemes\n :selectedSchemeOptions=\"activeSchemeOptions\"\n :server />\n\n <!-- Delete Auth Modal -->\n <DeleteRequestAuthModal\n v-if=\"schemeToDelete\"\n :label=\"schemeToDelete.label\"\n :scheme=\"schemeToDelete\"\n :state=\"deleteModal\"\n @close=\"deleteModal.hide()\"\n @delete=\"handleDeleteConfirm\" />\n </CollapsibleSection>\n</template>\n"],"mappings":"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"}
|
package/dist/v2/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/v2/constants.ts
|
|
2
2
|
/** The version number taken from the package.json. Consumers can override at build time via define (e.g. OVERRIDE_PACKAGE_VERSION: JSON.stringify('1.2.3')). */
|
|
3
|
-
var APP_VERSION = typeof OVERRIDE_PACKAGE_VERSION !== "undefined" ? OVERRIDE_PACKAGE_VERSION : "3.2.
|
|
3
|
+
var APP_VERSION = typeof OVERRIDE_PACKAGE_VERSION !== "undefined" ? OVERRIDE_PACKAGE_VERSION : "3.2.2";
|
|
4
4
|
//#endregion
|
|
5
5
|
export { APP_VERSION };
|
|
6
6
|
|
|
@@ -4,7 +4,6 @@ import type { LoaderPlugin } from '@scalar/json-magic/bundle';
|
|
|
4
4
|
import { createSidebarState } from '@scalar/sidebar';
|
|
5
5
|
import type { Theme } from '@scalar/themes';
|
|
6
6
|
import { type WorkspaceStore } from '@scalar/workspace-store/client';
|
|
7
|
-
import type { ApiClientAppOptions } from '../../../v2/features/app/helpers/create-api-client-app.js';
|
|
8
7
|
import { type WorkspaceEventBus } from '@scalar/workspace-store/events';
|
|
9
8
|
import type { WorkspaceDocument } from '@scalar/workspace-store/schemas';
|
|
10
9
|
import type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments';
|
|
@@ -12,6 +11,7 @@ import type { Tab } from '@scalar/workspace-store/schemas/extensions/workspace/x
|
|
|
12
11
|
import type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation';
|
|
13
12
|
import { type ComputedRef, type MaybeRefOrGetter, type Ref, type ShallowRef } from 'vue';
|
|
14
13
|
import type { RouteLocationNormalizedGeneric, Router } from 'vue-router';
|
|
14
|
+
import type { ApiClientAppOptions } from '../../../v2/features/app/helpers/create-api-client-app.js';
|
|
15
15
|
export type GetEntryByLocation = (location: {
|
|
16
16
|
document: string;
|
|
17
17
|
path?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-state.d.ts","sourceRoot":"","sources":["../../../../src/v2/features/app/app-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAAE,kBAAkB,EAAwB,MAAM,iBAAiB,CAAA;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"app-state.d.ts","sourceRoot":"","sources":["../../../../src/v2/features/app/app-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -230,7 +230,9 @@ var createAppState = async ({ router, fileLoader, fallbackThemeSlug = () => "def
|
|
|
230
230
|
* - If found, navigates to the active tab path (if available).
|
|
231
231
|
* - If not found, creates the default workspace and navigates to it.
|
|
232
232
|
*/
|
|
233
|
-
const changeWorkspace = async (namespace, slug) => {
|
|
233
|
+
const changeWorkspace = async (namespace, slug, to) => {
|
|
234
|
+
/** For initial load we want to fall through to our router default behaviour */
|
|
235
|
+
const isInitialLoad = activeWorkspace.value === null;
|
|
234
236
|
store.value = null;
|
|
235
237
|
isSyncingWorkspace.value = true;
|
|
236
238
|
const result = await loadWorkspace(namespace, slug);
|
|
@@ -238,7 +240,7 @@ var createAppState = async ({ router, fileLoader, fallbackThemeSlug = () => "def
|
|
|
238
240
|
const index = result.workspace["x-scalar-active-tab"] ?? 0;
|
|
239
241
|
const tabs = result.workspace["x-scalar-tabs"];
|
|
240
242
|
const tab = tabs?.[index];
|
|
241
|
-
if (tab) await router.replace({
|
|
243
|
+
if (tab && !isInitialLoad) await router.replace({
|
|
242
244
|
path: tab.path,
|
|
243
245
|
query: currentRoute.value?.query ?? {}
|
|
244
246
|
});
|
|
@@ -247,6 +249,10 @@ var createAppState = async ({ router, fileLoader, fallbackThemeSlug = () => "def
|
|
|
247
249
|
"x-scalar-tabs": [createTabFromRoute(currentRoute.value)],
|
|
248
250
|
"x-scalar-active-tab": 0
|
|
249
251
|
});
|
|
252
|
+
if (isInitialLoad && to) {
|
|
253
|
+
syncTabs(to);
|
|
254
|
+
syncSidebar(to);
|
|
255
|
+
}
|
|
250
256
|
isSyncingWorkspace.value = false;
|
|
251
257
|
return;
|
|
252
258
|
}
|
|
@@ -521,7 +527,7 @@ var createAppState = async ({ router, fileLoader, fallbackThemeSlug = () => "def
|
|
|
521
527
|
path.value = getRouteParam("pathEncoded", to);
|
|
522
528
|
exampleName.value = getRouteParam("exampleName", to);
|
|
523
529
|
if (to.path !== "") workspaceStorage.setCurrentPath(to.path);
|
|
524
|
-
if (getWorkspaceId(namespace.value, slug) !== activeWorkspace.value?.id) return changeWorkspace(namespace.value, slug);
|
|
530
|
+
if (getWorkspaceId(namespace.value, slug) !== activeWorkspace.value?.id) return changeWorkspace(namespace.value, slug, to);
|
|
525
531
|
if (document && document !== store.value?.workspace[extensions.workspace.activeDocument]) store?.value?.update("x-scalar-active-document", document);
|
|
526
532
|
syncTabs(to);
|
|
527
533
|
syncSidebar(to);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-state.js","names":[],"sources":["../../../../src/v2/features/app/app-state.ts"],"sourcesContent":["import type { ScalarListboxOption, WorkspaceGroup } from '@scalar/components'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport { sortByOrder } from '@scalar/helpers/array/sort-by-order'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { migrateLocalStorageToIndexDb } from '@scalar/oas-utils/migrations'\nimport { createSidebarState, generateReverseIndex } from '@scalar/sidebar'\nimport type { Theme } from '@scalar/themes'\nimport { type WorkspaceStore, createWorkspaceStore } from '@scalar/workspace-store/client'\n\nimport type { ApiClientAppOptions } from '@/v2/features/app/helpers/create-api-client-app'\nimport {\n type OperationExampleMeta,\n type WorkspaceEventBus,\n createWorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { generateUniqueValue } from '@scalar/workspace-store/helpers/generate-unique-value'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport { createWorkspaceStorePersistence, getWorkspaceId } from '@scalar/workspace-store/persistence'\nimport { persistencePlugin } from '@scalar/workspace-store/plugins/client'\nimport { getActiveEnvironment } from '@scalar/workspace-store/request-example'\nimport type { Workspace, WorkspaceDocument } from '@scalar/workspace-store/schemas'\nimport { extensions } from '@scalar/workspace-store/schemas/extensions'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { Tab } from '@scalar/workspace-store/schemas/extensions/workspace/x-scalar-tabs'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport {\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n type ShallowRef,\n computed,\n readonly,\n ref,\n shallowRef,\n watch,\n} from 'vue'\nimport type { RouteLocationNormalizedGeneric, RouteLocationRaw, Router } from 'vue-router'\n\nimport { getRouteParam } from '@/v2/features/app/helpers/get-route-param'\nimport { groupWorkspacesByTeam } from '@/v2/features/app/helpers/group-workspaces'\nimport { useTheme } from '@/v2/features/app/hooks/use-theme'\nimport { getTabDetails } from '@/v2/helpers/get-tab-details'\nimport { slugify } from '@/v2/helpers/slugify'\nimport { workspaceStorage } from '@/v2/helpers/storage'\n\nimport { initializeAppEventHandlers } from './app-events'\nimport { canLoadWorkspace, filterWorkspacesByTeam } from './helpers/filter-workspaces'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\nexport type GetEntryByLocation = (location: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n}) =>\n | (TraversedEntry & {\n parent?: TraversedEntry | undefined\n })\n | undefined\n\ntype WorkspaceOption = ScalarListboxOption & { teamUid: string; namespace: string; slug: string }\n\n/** Defines the overall application state structure and its main feature modules */\nexport type AppState = {\n /** The workspace store */\n store: ShallowRef<WorkspaceStore | null>\n /** The sidebar management */\n sidebar: {\n /** The sidebar state */\n state: ReturnType<typeof createSidebarState<TraversedEntry>>\n /** The width of the sidebar */\n width: ComputedRef<number>\n /** Whether the sidebar is open */\n isOpen: Ref<boolean>\n /** Handles the selection of an item in the sidebar */\n handleSelectItem: (id: string) => void\n /** Handles the width update of the sidebar */\n handleSidebarWidthUpdate: (width: number) => void\n /** Gets the entry by location */\n getEntryByLocation: GetEntryByLocation\n }\n /** The tabs management */\n tabs: {\n /** The tabs state */\n state: Ref<Tab[]>\n /** The active tab index */\n activeTabIndex: Ref<number>\n /** Copies the URL of the tab at the given index to the clipboard */\n copyTabUrl: (index: number) => Promise<void>\n }\n /** The workspace management */\n workspace: {\n /** Creates a new workspace and navigates to it */\n create: (payload: {\n teamUid?: string\n namespace?: string\n slug?: string\n name: string\n }) => Promise<{ name: string; namespace: string; slug: string; teamUid: string } | undefined>\n /** All workspace list */\n workspaceList: Ref<WorkspaceOption[]>\n /** Filtered workspace list, based on the current teamUid */\n filteredWorkspaceList: ComputedRef<WorkspaceOption[]>\n /**\n * Groups workspaces into team and local categories for display in the workspace picker.\n * Team workspaces are shown first (when not on local team), followed by local workspaces.\n */\n workspaceGroups: ComputedRef<WorkspaceGroup[]>\n /** The currently active workspace */\n activeWorkspace: ShallowRef<{ id: string; label: string } | null>\n /** Navigates to the specified workspace */\n navigateToWorkspace: (namespace?: string, slug?: string) => Promise<void>\n /** Whether the workspace page is open */\n isOpen: ComputedRef<boolean>\n }\n /** The workspace event bus for handling workspace-level events */\n eventBus: WorkspaceEventBus\n /** The router instance */\n router: Router\n /** The current route derived from the router */\n currentRoute: Ref<RouteLocationNormalizedGeneric | null>\n /** Whether the workspace is currently syncing */\n loading: Ref<boolean>\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n /** The currently active entities */\n activeEntities: {\n /** The namespace of the current entity, e.g. \"default\" or a custom namespace */\n namespace: Ref<string | undefined>\n /** The slug identifying the current workspace */\n workspaceSlug: Ref<string | undefined>\n /** The slug of the currently selected document in the workspace */\n documentSlug: Ref<string | undefined>\n /** The API path currently selected (e.g. \"/users/{id}\") */\n path: Ref<string | undefined>\n /** The HTTP method for the currently selected API path (e.g. GET, POST) */\n method: Ref<HttpMethod | undefined>\n /** The name of the currently selected example (for examples within an endpoint) */\n exampleName: Ref<string | undefined>\n /** The unique identifier for the selected team context (read-only; use setTeamUid to change) */\n teamUid: Readonly<Ref<string>>\n /** Sets the current team context by team UID */\n setTeamUid: (value: string) => void\n }\n /** The currently active environment */\n environment: ComputedRef<XScalarEnvironment>\n /** The currently active document */\n document: ComputedRef<WorkspaceDocument | null>\n /** Whether the current color mode is dark */\n isDarkMode: ComputedRef<boolean>\n /** The currently active theme */\n theme: {\n /** The computed CSS styles for the current theme, as a string */\n styles: ComputedRef<{ themeStyles: string; themeSlug: string }>\n /** The computed value for the <style> tag containing the current theme styles */\n themeStyleTag: ComputedRef<string>\n /** The custom themes to use */\n customThemes: MaybeRefOrGetter<Theme[]>\n }\n telemetry: Ref<boolean>\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n/** Default debounce delay in milliseconds for workspace store persistence. */\nconst DEFAULT_DEBOUNCE_DELAY = 1000\n/** Default sidebar width in pixels. */\nconst DEFAULT_SIDEBAR_WIDTH = 288\n\n// ---------------------------------------------------------------------------\n// App State\n// ---------------------------------------------------------------------------\nexport const createAppState = async ({\n router,\n fileLoader,\n fallbackThemeSlug = () => 'default',\n customThemes = () => [],\n telemetryDefault,\n options,\n}: {\n router: Router\n fileLoader?: LoaderPlugin\n customThemes?: MaybeRefOrGetter<Theme[]>\n fallbackThemeSlug?: MaybeRefOrGetter<string>\n telemetryDefault?: boolean\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n}): Promise<AppState> => {\n /** Workspace event bus for handling workspace-level events. */\n const eventBus = createWorkspaceEventBus({\n debug: import.meta.env.DEV,\n })\n\n const { workspace: persistence } = await createWorkspaceStorePersistence()\n\n /**\n * Run migration from localStorage to IndexedDB if needed\n * This happens once per user and transforms old data structure to new workspace format\n */\n await migrateLocalStorageToIndexDb()\n\n // ---------------------------------------------------------------------------\n // Active entities\n // ---------------------------------------------------------------------------\n const teamUid = ref<string>('local')\n const namespace = ref<string | undefined>(undefined)\n const workspaceSlug = ref<string | undefined>(undefined)\n const documentSlug = ref<string | undefined>(undefined)\n const method = ref<HttpMethod | undefined>(undefined)\n const path = ref<string | undefined>(undefined)\n const exampleName = ref<string | undefined>(undefined)\n\n // ---------------------------------------------------------------------------\n // Loading states\n const isSyncingWorkspace = ref(false)\n\n // ---------------------------------------------------------------------------\n // Router state\n router.afterEach((to) => handleRouteChange(to))\n const currentRoute = computed(() => router.currentRoute.value ?? null)\n\n // ---------------------------------------------------------------------------\n // Workspace persistence state management\n const activeWorkspace = shallowRef<{ id: string; label: string } | null>(null)\n const workspaces = ref<WorkspaceOption[]>([])\n const filteredWorkspaces = computed(() => filterWorkspacesByTeam(workspaces.value, teamUid.value))\n const workspaceGroups = computed(() => groupWorkspacesByTeam(filteredWorkspaces.value, teamUid.value))\n const store = shallowRef<WorkspaceStore | null>(null)\n\n // Load persisted telemetry preference, falling back to the provided default\n const persistedTelemetry = workspaceStorage.getTelemetry()\n const telemetry = ref(persistedTelemetry !== null ? persistedTelemetry : Boolean(telemetryDefault))\n watch(telemetry, (value) => workspaceStorage.setTelemetry(value))\n\n const activeDocument = computed(() => {\n return store.value?.workspace.documents[documentSlug.value ?? ''] || null\n })\n\n /**\n * Merged environment variables from workspace and document levels.\n * Variables from both sources are combined, with document variables\n * taking precedence in case of naming conflicts.\n */\n const environment = computed<XScalarEnvironment>(\n () => getActiveEnvironment(store.value, activeDocument.value).environment,\n )\n\n /** Update the workspace list when the component is mounted */\n workspaces.value = await persistence.getAll().then((w) =>\n w.map(({ teamUid, namespace, slug, name }) => ({\n id: getWorkspaceId(namespace, slug),\n teamUid,\n namespace,\n slug,\n label: name,\n })),\n )\n\n /**\n * Renames the currently active workspace.\n * Updates the workspace name in persistence and updates activeWorkspace if successful.\n * Returns early if namespace or workspaceSlug is not set, or if update fails.\n */\n const renameWorkspace = async (name: string) => {\n const namespaceValue = namespace.value\n const slugValue = workspaceSlug.value\n if (!namespaceValue || !slugValue) {\n return\n }\n const workspaceId = getWorkspaceId(namespaceValue, slugValue)\n const updateResult = await persistence.updateName({ namespace: namespaceValue, slug: slugValue }, name)\n\n // If the update fails, return early\n if (updateResult === undefined) {\n return\n }\n\n // Update the workspace list\n workspaces.value = workspaces.value.map((workspace) => {\n // If the workspace is the currently active workspace, update the label\n if (workspace.id === workspaceId) {\n return { ...workspace, label: name }\n }\n return workspace\n })\n activeWorkspace.value = { id: workspaceId, label: name }\n }\n\n /**\n * Creates a client-side workspace store with persistence enabled for the given workspace id.\n */\n const createClientStore = async ({\n namespace,\n slug,\n }: {\n namespace: string\n slug: string\n }): Promise<WorkspaceStore> => {\n return createWorkspaceStore({\n plugins: [\n await persistencePlugin({\n workspaceId: getWorkspaceId(namespace, slug),\n debounceDelay: DEFAULT_DEBOUNCE_DELAY,\n }),\n ],\n fileLoader,\n fetch: options?.customFetch,\n })\n }\n\n /**\n * Attempts to load and activate a workspace by id.\n * Returns true when the workspace was found and activated.\n */\n const loadWorkspace = async (\n namespace: string,\n slug: string,\n ): Promise<{ success: true; workspace: Workspace } | { success: false }> => {\n const workspace = await persistence.getItem({ namespace, slug })\n\n if (!workspace) {\n return {\n success: false,\n }\n }\n\n const client = await createClientStore({ namespace, slug })\n client.loadWorkspace(workspace.workspace)\n activeWorkspace.value = { id: getWorkspaceId(workspace.namespace, workspace.slug), label: workspace.name }\n store.value = client\n\n return {\n success: true,\n workspace: client.workspace,\n }\n }\n\n /**\n * Creates and persists the default workspace with a blank draft document.\n * Used when no workspaces exist yet.\n */\n const createAndPersistWorkspace = async ({\n name,\n teamUid,\n namespace,\n slug,\n }: {\n name: string\n teamUid?: string\n namespace?: string\n slug: string\n }) => {\n const draftStore = createWorkspaceStore()\n await draftStore.addDocument({\n name: 'drafts',\n document: {\n openapi: '3.1.0',\n info: {\n title: 'Drafts',\n version: '1.0.0',\n },\n paths: {\n '/': {\n get: {},\n },\n },\n 'x-scalar-original-document-hash': 'drafts',\n 'x-scalar-icon': 'interface-edit-tool-pencil',\n },\n })\n\n // Persist the workspace\n const workspace = await persistence.setItem(\n { namespace, slug },\n {\n name: name,\n teamUid,\n workspace: draftStore.exportWorkspace(),\n },\n )\n\n // Update the workspace list\n workspaces.value.push({\n id: getWorkspaceId(workspace.namespace, workspace.slug),\n teamUid: workspace.teamUid,\n namespace: workspace.namespace,\n slug: workspace.slug,\n label: workspace.name,\n })\n return workspace\n }\n\n /**\n * Navigates to the overview page of the specified workspace.\n *\n * @param namespace - The workspace namespace.\n * @param slug - The unique workspace slug (identifier).\n */\n const navigateToWorkspace = async (namespace?: string, slug?: string): Promise<void> => {\n if (!namespace || !slug) {\n await router.push('/')\n return\n }\n\n // We should always have this drafts document available in a new workspace\n await router.push({\n name: 'example',\n params: {\n namespace,\n workspaceSlug: slug,\n documentSlug: 'drafts',\n pathEncoded: encodeURIComponent('/'),\n method: 'get',\n exampleName: 'default',\n },\n })\n }\n\n /**\n * Creates a new workspace with the provided name.\n * - Generates a unique slug for the workspace (uses the provided slug if it is unique, otherwise generates a unique slug).\n * - Adds a default blank document (\"drafts\") to the workspace.\n * - Persists the workspace and navigates to it.\n *\n * Example usage:\n * await createWorkspace({ name: 'My Awesome API' })\n * // -> Navigates to /workspace/my-awesome-api (if available)\n */\n const createWorkspace = async ({\n teamUid,\n namespace,\n slug,\n name,\n }: {\n teamUid?: string\n namespace?: string\n slug?: string\n name: string\n }) => {\n // Clear up the current store, in order to show the loading state\n store.value = null\n\n // Generate a unique slug/id for the workspace, based on the name.\n const newWorkspaceSlug = await generateUniqueValue({\n defaultValue: slug ?? name, // Use the provided id if it exists, otherwise use the name\n validation: async (value) => !(await persistence.has({ namespace: namespace ?? 'local', slug: value })),\n maxRetries: 100,\n transformation: slugify,\n })\n\n // Failed to generate a unique workspace id, so we can't create the workspace.\n if (!newWorkspaceSlug) {\n return undefined\n }\n\n const newWorkspaceDetails = {\n teamUid,\n namespace,\n slug: newWorkspaceSlug,\n name,\n }\n\n // Create a new client store with the workspace ID and add a default document.\n const createdWorkspace = await createAndPersistWorkspace(newWorkspaceDetails)\n\n // Navigate to the newly created workspace.\n await navigateToWorkspace(createdWorkspace.namespace, createdWorkspace.slug)\n return createdWorkspace\n }\n\n /**\n * Handles changing the active workspace when the workspace slug changes in the route.\n * This function:\n * - Clears the current workspace store and sets loading state.\n * - Attempts to load the workspace by slug.\n * - If found, navigates to the active tab path (if available).\n * - If not found, creates the default workspace and navigates to it.\n */\n const changeWorkspace = async (namespace: string, slug: string) => {\n // Clear the current store and set loading to true before loading new workspace.\n store.value = null\n isSyncingWorkspace.value = true\n\n // Try to load the workspace\n const result = await loadWorkspace(namespace, slug)\n\n if (result.success) {\n // Navigate to the correct tab if the workspace has a tab already\n const index = result.workspace['x-scalar-active-tab'] ?? 0\n const tabs = result.workspace['x-scalar-tabs']\n const tab = tabs?.[index]\n\n if (tab) {\n // Preserve query parameters when navigating to the active tab\n await router.replace({\n path: tab.path,\n query: currentRoute.value?.query ?? {},\n })\n }\n\n // Heal the active tab index if it is out of bounds\n if (tabs && index >= tabs.length) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-active-tab': 0,\n })\n }\n\n // Initialize the tabs if they does not exist\n if (!tabs) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-tabs': [createTabFromRoute(currentRoute.value)],\n 'x-scalar-active-tab': 0,\n })\n }\n\n isSyncingWorkspace.value = false\n return\n }\n\n // Navigate to the default workspace, or fall back to the first available workspace\n const targetWorkspace =\n filteredWorkspaces.value.find((workspace) => workspace.teamUid === 'local' && workspace.slug === 'default') ??\n filteredWorkspaces.value[0]\n\n if (targetWorkspace) {\n return navigateToWorkspace(targetWorkspace.namespace, targetWorkspace.slug)\n }\n\n // If loading failed (workspace does not exist), create the default workspace and navigate to it.\n const createResult = await createWorkspace({\n name: 'Default Workspace',\n slug: 'default',\n })\n\n isSyncingWorkspace.value = false\n\n if (!createResult) {\n return console.error('Failed to create the default workspace, something went wrong, can not load the workspace')\n }\n\n // Must reset the sidebar state when the workspace changes\n sidebarState.reset()\n }\n\n /**\n * Sets the current team context. If the active workspace is not accessible\n * with the new team, navigates to the default workspace for that team.\n */\n const setTeamUid = (value: string) => {\n // Update the new teamUid\n teamUid.value = value\n\n // Find the current workspace\n const workspace = filteredWorkspaces.value.find(\n (w) => w.namespace === namespace.value && w.slug === workspaceSlug.value,\n )\n\n // Check if new teamUid is accessible to the current workspace\n if (workspace && canLoadWorkspace(workspace.teamUid, value)) {\n return\n }\n\n return navigateToWorkspace('local', 'default')\n }\n\n // ---------------------------------------------------------------------------\n // Sidebar state management\n\n const entries = computed(() => {\n const activeStore = store.value\n if (!activeStore) {\n return []\n }\n\n const order = activeStore.workspace['x-scalar-order'] ?? Object.keys(activeStore.workspace.documents)\n\n return sortByOrder(Object.keys(activeStore.workspace.documents), order, (item) => item)\n .map((doc) => activeStore.workspace.documents[doc]?.['x-scalar-navigation'])\n .filter(isDefined) as TraversedEntry[]\n })\n\n const sidebarState = createSidebarState(entries)\n\n /**\n * Generates a unique string ID for an API location, based on the document, path, method, and example.\n * Filters out undefined values and serializes the composite array into a stable string.\n *\n * @param params - An object containing document, path, method, and optional example name.\n * @returns A stringified array representing the unique location identifier.\n *\n * Example:\n * generateId({ document: 'mydoc', path: '/users', method: 'get', example: 'default' })\n * // => '[\"mydoc\",\"/users\",\"get\",\"default\"]'\n */\n const generateId = ({\n document,\n path,\n method,\n example,\n }: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n }) => {\n return JSON.stringify([document, path, method, example].filter(isDefined))\n }\n\n /**\n * Computed index for fast lookup of sidebar nodes by their unique API location.\n *\n * - Only indexes nodes of type 'document', 'operation', or 'example'.\n * - The lookup key is a serialized array of: [documentName, operationPath, operationMethod, exampleName?].\n * - Supports precise resolution of sidebar entries given an API \"location\".\n */\n const locationIndex = computed(() =>\n generateReverseIndex({\n items: entries.value,\n nestedKey: 'children',\n filter: (node) => node.type === 'document' || node.type === 'operation' || node.type === 'example',\n getId: (node) => {\n const document = getParentEntry('document', node)\n const operation = getParentEntry('operation', node)\n return generateId({\n document: document?.name ?? '',\n path: operation?.path,\n method: operation?.method,\n example: node.type === 'example' ? node.name : undefined,\n })\n },\n }),\n )\n\n /**\n * Looks up a sidebar entry by its unique API location.\n * - First tries to find an entry matching all provided properties (including example).\n * - If not found, falls back to matching only the operation (ignores example field).\n * This allows resolving either examples, operations, or documents as appropriate.\n *\n * @param location - Object specifying the document name, path, method, and optional example name.\n * @returns The matching sidebar entry, or undefined if none found.\n *\n * Example:\n * const entry = getEntryByLocation({\n * document: 'pets',\n * path: '/pets',\n * method: 'get',\n * example: 'default',\n * })\n */\n const getEntryByLocation: GetEntryByLocation = (location) => {\n // Try to find an entry with the most-specific location (including example)\n const entryWithExample = locationIndex.value.get(generateId(location))\n\n if (entryWithExample) {\n return entryWithExample\n }\n\n // Fallback to the operation (ignoring example) if an example wasn't found or specified\n return locationIndex.value.get(\n generateId({\n document: location.document,\n path: location.path,\n method: location.method,\n }),\n )\n }\n\n /**\n * Handles item selection from the sidebar and routes navigation accordingly.\n *\n * Example:\n * handleSelectItem('id-of-entry')\n */\n const handleSelectItem = (id: string) => {\n const entry = sidebarState.getEntryById(id)\n\n if (!entry) {\n console.warn(`Could not find sidebar entry with id ${id} to select`)\n return\n }\n\n /** Close sidebar and navigate. Used for every branch that performs navigation. */\n const navigate = (route: RouteLocationRaw) => {\n isSidebarOpen.value = false\n return router.push(route)\n }\n\n // Navigate to the document overview page\n if (entry.type === 'document') {\n // If we are already in the document, just toggle expansion\n if (sidebarState.selectedItem.value === id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select it\n sidebarState.setSelected(id)\n sidebarState.setExpanded(id, true)\n return navigate({\n name: 'document.overview',\n params: { documentSlug: entry.name },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'operation') {\n // If we are already in an operation child we just want to toggle the explanstion\n if (sidebarState.isSelected(id) && sidebarState.selectedItem.value !== id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select the first example\n const firstExample = entry.children?.find((child) => child.type === 'example')\n\n if (firstExample) {\n sidebarState.setSelected(firstExample.id)\n sidebarState.setExpanded(firstExample.id, true)\n } else {\n sidebarState.setSelected(id)\n }\n\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(entry.path),\n method: entry.method,\n exampleName: firstExample?.name ?? 'default',\n },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'example') {\n sidebarState.setSelected(id)\n const operation = getParentEntry('operation', entry)\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(operation?.path ?? ''),\n method: operation?.method,\n exampleName: entry.name,\n },\n })\n }\n\n if (entry.type === 'text') {\n return navigate({\n name: 'document.overview',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n },\n })\n }\n\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n /**\n * Navigates to the currently active tab's path using the router.\n * Returns early if the workspace store or active tab is unavailable.\n */\n const navigateToCurrentTab = async (): Promise<void> => {\n if (!store.value) {\n return\n }\n\n const activeTabIndex = store.value.workspace['x-scalar-active-tab'] ?? 0\n const activeTab = store.value.workspace['x-scalar-tabs']?.[activeTabIndex]\n if (!activeTab) {\n return\n }\n\n await router.replace(activeTab.path)\n }\n\n /**\n * Rebuilds the sidebar for the given document.\n * This is used to refresh the sidebar state after structural changes (e.g. after adding or removing items).\n *\n * @param documentName - The name (id) of the document for which to rebuild the sidebar\n */\n const rebuildSidebar = (documentName: string | undefined) => {\n if (documentName) {\n store.value?.buildSidebar(documentName)\n }\n }\n\n /**\n * Ensures the sidebar is refreshed after a new example is created.\n *\n * If the sidebar entry for the new example does not exist or is of a different type,\n * this will rebuild the sidebar for the current document. This helps keep the sidebar state\n * consistent (e.g., after adding a new example via the UI).\n */\n const refreshSidebarAfterExampleCreation = (payload: OperationExampleMeta & { documentName?: string }) => {\n const documentName = payload.documentName ?? activeDocument.value?.['x-scalar-navigation']?.name\n if (!documentName) {\n return\n }\n\n const entry = getEntryByLocation({\n document: documentName,\n path: payload.path,\n method: payload.method,\n example: payload.exampleKey,\n })\n\n if (!entry || entry.type !== 'example') {\n // Sidebar entry for this example doesn't exist, so rebuild sidebar for consistency.\n rebuildSidebar(documentName)\n if (currentRoute.value) {\n syncSidebar(currentRoute.value)\n }\n }\n return\n }\n\n /** Width of the sidebar, with fallback to default. */\n const sidebarWidth = computed(() => store.value?.workspace?.['x-scalar-sidebar-width'] ?? DEFAULT_SIDEBAR_WIDTH)\n\n /** Handler for sidebar width changes. */\n const handleSidebarWidthUpdate = (width: number) => store.value?.update('x-scalar-sidebar-width', width)\n\n /** Controls the visibility of the sidebar. */\n const isSidebarOpen = ref(false)\n // ---------------------------------------------------------------------------\n // Tab Management\n\n /** Constants for workspace store keys */\n const TABS_KEY = 'x-scalar-tabs' as const\n const ACTIVE_TAB_KEY = 'x-scalar-active-tab' as const\n\n /**\n * Creates a tab object based on the current route and workspace state.\n * Used as a fallback when no tabs exist or when creating new tabs.\n */\n const createTabFromRoute = (to: RouteLocationNormalizedGeneric | null): Tab => {\n const method = getRouteParam('method', to)\n const path = getRouteParam('pathEncoded', to)\n const document = getRouteParam('documentSlug', to)\n const workspace = getRouteParam('workspaceSlug', to)\n return {\n ...getTabDetails({\n workspace,\n document,\n path,\n method,\n getEntryByLocation,\n }),\n path: currentRoute.value?.path ?? '',\n }\n }\n\n const tabs = computed(() => {\n return store.value?.workspace[TABS_KEY] ?? [createTabFromRoute(currentRoute.value)]\n })\n\n const activeTabIndex = computed(() => {\n return store.value?.workspace[ACTIVE_TAB_KEY] ?? 0\n })\n\n /**\n * Copies the URL of the tab at the given index to the clipboard.\n * Constructs the full URL using the current origin and the tab path.\n *\n * Note: Will silently fail if clipboard API is unavailable or the tab does not exist.\n */\n const copyTabUrl = async (index: number): Promise<void> => {\n const tab = tabs.value[index]\n\n if (!tab) {\n console.warn(`Cannot copy URL: tab at index ${index} does not exist`)\n return\n }\n\n const url = `${window.location.origin}${tab.path}`\n\n try {\n await navigator.clipboard.writeText(url)\n } catch (error) {\n console.error('Failed to copy URL to clipboard:', error)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Path syncing\n\n /** When the route changes we need to update the active entities in the store */\n const handleRouteChange = (to: RouteLocationNormalizedGeneric) => {\n const slug = getRouteParam('workspaceSlug', to)\n const document = getRouteParam('documentSlug', to)\n const namespaceValue = getRouteParam('namespace', to)\n\n // Must have an active workspace to syncs\n if (!namespaceValue || !slug) {\n return\n }\n\n // Try to see if the user can load this workspace based on the teamUid and namespace\n const workspace = workspaces.value.find(\n (workspace) => workspace.slug === slug && workspace.namespace === namespaceValue,\n )\n\n // If the workspace is not found or the teamUid does not match, try to redirect to the default workspace\n if (workspace && !canLoadWorkspace(workspace.teamUid, teamUid.value)) {\n // try to redirect to the default workspace\n return navigateToWorkspace('local', 'default')\n }\n\n namespace.value = namespaceValue\n workspaceSlug.value = slug\n documentSlug.value = document\n method.value = getRouteParam('method', to)\n path.value = getRouteParam('pathEncoded', to)\n exampleName.value = getRouteParam('exampleName', to)\n\n // Save the current path to the persistence storage\n if (to.path !== '') {\n workspaceStorage.setCurrentPath(to.path)\n }\n\n if (getWorkspaceId(namespace.value, slug) !== activeWorkspace.value?.id) {\n return changeWorkspace(namespace.value, slug)\n }\n\n // Update the active document if the document slug has changes\n if (document && document !== store.value?.workspace[extensions.workspace.activeDocument]) {\n store?.value?.update('x-scalar-active-document', document)\n }\n\n syncTabs(to)\n syncSidebar(to)\n return\n }\n\n /** Aligns the tabs with any potential slug changes */\n const syncTabs = (to: RouteLocationNormalizedGeneric) => {\n const tabs = store.value?.workspace['x-scalar-tabs'] ?? []\n const index = store.value?.workspace['x-scalar-active-tab'] ?? 0\n\n const tab = tabs[index]\n\n // If there is no tab or the tab path is the same we leave the tab state alone\n if (!tab || tab.path === to.path) {\n // Already on the correct path, do nothing\n return\n }\n\n // Otherwise we replace the tab content with the new route\n tabs[index] = createTabFromRoute(to)\n }\n\n /** Aligns the sidebar state with any potential slug changes */\n const syncSidebar = (to: RouteLocationNormalizedGeneric) => {\n const document = getRouteParam('documentSlug', to)\n\n if (!document) {\n // Reset selection if no document is selected\n sidebarState.setSelected(null)\n return\n }\n\n const entry = getEntryByLocation({\n document,\n path: getRouteParam('pathEncoded', to),\n method: getRouteParam('method', to),\n example: getRouteParam('exampleName', to),\n })\n\n if (entry) {\n sidebarState.setSelected(entry.id)\n sidebarState.setExpanded(entry.id, true)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Events handling\n\n initializeAppEventHandlers({\n eventBus,\n router,\n store,\n navigateToCurrentTab,\n rebuildSidebar,\n onAfterExampleCreation: refreshSidebarAfterExampleCreation,\n onSelectSidebarItem: handleSelectItem,\n onCopyTabUrl: (index) => copyTabUrl(index),\n onToggleSidebar: () => (isSidebarOpen.value = !isSidebarOpen.value),\n closeSidebar: () => (isSidebarOpen.value = false),\n renameWorkspace,\n })\n\n const theme = useTheme({\n fallbackThemeSlug,\n customThemes,\n store: store,\n })\n\n const isDarkMode = computed(() => {\n const colorMode = store.value?.workspace['x-scalar-color-mode'] ?? 'system'\n if (colorMode === 'system') {\n return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false\n }\n return colorMode === 'dark'\n })\n\n return {\n /** Active workspace store */\n store,\n sidebar: {\n state: sidebarState,\n width: sidebarWidth,\n isOpen: isSidebarOpen,\n handleSelectItem,\n handleSidebarWidthUpdate,\n getEntryByLocation,\n },\n tabs: {\n state: tabs,\n activeTabIndex,\n copyTabUrl,\n },\n workspace: {\n create: createWorkspace,\n workspaceList: workspaces,\n filteredWorkspaceList: filteredWorkspaces,\n workspaceGroups,\n activeWorkspace,\n navigateToWorkspace,\n isOpen: computed(() => Boolean(workspaceSlug.value && !documentSlug.value)),\n },\n eventBus,\n router,\n currentRoute,\n loading: isSyncingWorkspace,\n activeEntities: {\n namespace,\n workspaceSlug,\n documentSlug,\n path,\n method,\n exampleName,\n teamUid: readonly(teamUid),\n setTeamUid,\n },\n environment,\n document: activeDocument,\n isDarkMode,\n theme: {\n styles: theme.themeStyles,\n themeStyleTag: theme.themeStyleTag,\n customThemes,\n },\n telemetry,\n options,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-state.js","names":[],"sources":["../../../../src/v2/features/app/app-state.ts"],"sourcesContent":["import type { ScalarListboxOption, WorkspaceGroup } from '@scalar/components'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport { sortByOrder } from '@scalar/helpers/array/sort-by-order'\nimport type { HttpMethod } from '@scalar/helpers/http/http-methods'\nimport type { LoaderPlugin } from '@scalar/json-magic/bundle'\nimport { migrateLocalStorageToIndexDb } from '@scalar/oas-utils/migrations'\nimport { createSidebarState, generateReverseIndex } from '@scalar/sidebar'\nimport type { Theme } from '@scalar/themes'\nimport { type WorkspaceStore, createWorkspaceStore } from '@scalar/workspace-store/client'\nimport {\n type OperationExampleMeta,\n type WorkspaceEventBus,\n createWorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport { generateUniqueValue } from '@scalar/workspace-store/helpers/generate-unique-value'\nimport { getParentEntry } from '@scalar/workspace-store/navigation'\nimport { createWorkspaceStorePersistence, getWorkspaceId } from '@scalar/workspace-store/persistence'\nimport { persistencePlugin } from '@scalar/workspace-store/plugins/client'\nimport { getActiveEnvironment } from '@scalar/workspace-store/request-example'\nimport type { Workspace, WorkspaceDocument } from '@scalar/workspace-store/schemas'\nimport { extensions } from '@scalar/workspace-store/schemas/extensions'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { Tab } from '@scalar/workspace-store/schemas/extensions/workspace/x-scalar-tabs'\nimport type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\nimport {\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n type ShallowRef,\n computed,\n readonly,\n ref,\n shallowRef,\n watch,\n} from 'vue'\nimport type { RouteLocationNormalizedGeneric, RouteLocationRaw, Router } from 'vue-router'\n\nimport type { ApiClientAppOptions } from '@/v2/features/app/helpers/create-api-client-app'\nimport { getRouteParam } from '@/v2/features/app/helpers/get-route-param'\nimport { groupWorkspacesByTeam } from '@/v2/features/app/helpers/group-workspaces'\nimport { useTheme } from '@/v2/features/app/hooks/use-theme'\nimport { getTabDetails } from '@/v2/helpers/get-tab-details'\nimport { slugify } from '@/v2/helpers/slugify'\nimport { workspaceStorage } from '@/v2/helpers/storage'\n\nimport { initializeAppEventHandlers } from './app-events'\nimport { canLoadWorkspace, filterWorkspacesByTeam } from './helpers/filter-workspaces'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\nexport type GetEntryByLocation = (location: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n}) =>\n | (TraversedEntry & {\n parent?: TraversedEntry | undefined\n })\n | undefined\n\ntype WorkspaceOption = ScalarListboxOption & { teamUid: string; namespace: string; slug: string }\n\n/** Defines the overall application state structure and its main feature modules */\nexport type AppState = {\n /** The workspace store */\n store: ShallowRef<WorkspaceStore | null>\n /** The sidebar management */\n sidebar: {\n /** The sidebar state */\n state: ReturnType<typeof createSidebarState<TraversedEntry>>\n /** The width of the sidebar */\n width: ComputedRef<number>\n /** Whether the sidebar is open */\n isOpen: Ref<boolean>\n /** Handles the selection of an item in the sidebar */\n handleSelectItem: (id: string) => void\n /** Handles the width update of the sidebar */\n handleSidebarWidthUpdate: (width: number) => void\n /** Gets the entry by location */\n getEntryByLocation: GetEntryByLocation\n }\n /** The tabs management */\n tabs: {\n /** The tabs state */\n state: Ref<Tab[]>\n /** The active tab index */\n activeTabIndex: Ref<number>\n /** Copies the URL of the tab at the given index to the clipboard */\n copyTabUrl: (index: number) => Promise<void>\n }\n /** The workspace management */\n workspace: {\n /** Creates a new workspace and navigates to it */\n create: (payload: {\n teamUid?: string\n namespace?: string\n slug?: string\n name: string\n }) => Promise<{ name: string; namespace: string; slug: string; teamUid: string } | undefined>\n /** All workspace list */\n workspaceList: Ref<WorkspaceOption[]>\n /** Filtered workspace list, based on the current teamUid */\n filteredWorkspaceList: ComputedRef<WorkspaceOption[]>\n /**\n * Groups workspaces into team and local categories for display in the workspace picker.\n * Team workspaces are shown first (when not on local team), followed by local workspaces.\n */\n workspaceGroups: ComputedRef<WorkspaceGroup[]>\n /** The currently active workspace */\n activeWorkspace: ShallowRef<{ id: string; label: string } | null>\n /** Navigates to the specified workspace */\n navigateToWorkspace: (namespace?: string, slug?: string) => Promise<void>\n /** Whether the workspace page is open */\n isOpen: ComputedRef<boolean>\n }\n /** The workspace event bus for handling workspace-level events */\n eventBus: WorkspaceEventBus\n /** The router instance */\n router: Router\n /** The current route derived from the router */\n currentRoute: Ref<RouteLocationNormalizedGeneric | null>\n /** Whether the workspace is currently syncing */\n loading: Ref<boolean>\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n /** The currently active entities */\n activeEntities: {\n /** The namespace of the current entity, e.g. \"default\" or a custom namespace */\n namespace: Ref<string | undefined>\n /** The slug identifying the current workspace */\n workspaceSlug: Ref<string | undefined>\n /** The slug of the currently selected document in the workspace */\n documentSlug: Ref<string | undefined>\n /** The API path currently selected (e.g. \"/users/{id}\") */\n path: Ref<string | undefined>\n /** The HTTP method for the currently selected API path (e.g. GET, POST) */\n method: Ref<HttpMethod | undefined>\n /** The name of the currently selected example (for examples within an endpoint) */\n exampleName: Ref<string | undefined>\n /** The unique identifier for the selected team context (read-only; use setTeamUid to change) */\n teamUid: Readonly<Ref<string>>\n /** Sets the current team context by team UID */\n setTeamUid: (value: string) => void\n }\n /** The currently active environment */\n environment: ComputedRef<XScalarEnvironment>\n /** The currently active document */\n document: ComputedRef<WorkspaceDocument | null>\n /** Whether the current color mode is dark */\n isDarkMode: ComputedRef<boolean>\n /** The currently active theme */\n theme: {\n /** The computed CSS styles for the current theme, as a string */\n styles: ComputedRef<{ themeStyles: string; themeSlug: string }>\n /** The computed value for the <style> tag containing the current theme styles */\n themeStyleTag: ComputedRef<string>\n /** The custom themes to use */\n customThemes: MaybeRefOrGetter<Theme[]>\n }\n telemetry: Ref<boolean>\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n/** Default debounce delay in milliseconds for workspace store persistence. */\nconst DEFAULT_DEBOUNCE_DELAY = 1000\n/** Default sidebar width in pixels. */\nconst DEFAULT_SIDEBAR_WIDTH = 288\n\n// ---------------------------------------------------------------------------\n// App State\n// ---------------------------------------------------------------------------\nexport const createAppState = async ({\n router,\n fileLoader,\n fallbackThemeSlug = () => 'default',\n customThemes = () => [],\n telemetryDefault,\n options,\n}: {\n router: Router\n fileLoader?: LoaderPlugin\n customThemes?: MaybeRefOrGetter<Theme[]>\n fallbackThemeSlug?: MaybeRefOrGetter<string>\n telemetryDefault?: boolean\n /** Runtime behaviour overrides */\n options?: ApiClientAppOptions\n}): Promise<AppState> => {\n /** Workspace event bus for handling workspace-level events. */\n const eventBus = createWorkspaceEventBus({\n debug: import.meta.env.DEV,\n })\n\n const { workspace: persistence } = await createWorkspaceStorePersistence()\n\n /**\n * Run migration from localStorage to IndexedDB if needed\n * This happens once per user and transforms old data structure to new workspace format\n */\n await migrateLocalStorageToIndexDb()\n\n // ---------------------------------------------------------------------------\n // Active entities\n // ---------------------------------------------------------------------------\n const teamUid = ref<string>('local')\n const namespace = ref<string | undefined>(undefined)\n const workspaceSlug = ref<string | undefined>(undefined)\n const documentSlug = ref<string | undefined>(undefined)\n const method = ref<HttpMethod | undefined>(undefined)\n const path = ref<string | undefined>(undefined)\n const exampleName = ref<string | undefined>(undefined)\n\n // ---------------------------------------------------------------------------\n // Loading states\n const isSyncingWorkspace = ref(false)\n\n // ---------------------------------------------------------------------------\n // Router state\n router.afterEach((to) => handleRouteChange(to))\n const currentRoute = computed(() => router.currentRoute.value ?? null)\n\n // ---------------------------------------------------------------------------\n // Workspace persistence state management\n const activeWorkspace = shallowRef<{ id: string; label: string } | null>(null)\n const workspaces = ref<WorkspaceOption[]>([])\n const filteredWorkspaces = computed(() => filterWorkspacesByTeam(workspaces.value, teamUid.value))\n const workspaceGroups = computed(() => groupWorkspacesByTeam(filteredWorkspaces.value, teamUid.value))\n const store = shallowRef<WorkspaceStore | null>(null)\n\n // Load persisted telemetry preference, falling back to the provided default\n const persistedTelemetry = workspaceStorage.getTelemetry()\n const telemetry = ref(persistedTelemetry !== null ? persistedTelemetry : Boolean(telemetryDefault))\n watch(telemetry, (value) => workspaceStorage.setTelemetry(value))\n\n const activeDocument = computed(() => {\n return store.value?.workspace.documents[documentSlug.value ?? ''] || null\n })\n\n /**\n * Merged environment variables from workspace and document levels.\n * Variables from both sources are combined, with document variables\n * taking precedence in case of naming conflicts.\n */\n const environment = computed<XScalarEnvironment>(\n () => getActiveEnvironment(store.value, activeDocument.value).environment,\n )\n\n /** Update the workspace list when the component is mounted */\n workspaces.value = await persistence.getAll().then((w) =>\n w.map(({ teamUid, namespace, slug, name }) => ({\n id: getWorkspaceId(namespace, slug),\n teamUid,\n namespace,\n slug,\n label: name,\n })),\n )\n\n /**\n * Renames the currently active workspace.\n * Updates the workspace name in persistence and updates activeWorkspace if successful.\n * Returns early if namespace or workspaceSlug is not set, or if update fails.\n */\n const renameWorkspace = async (name: string) => {\n const namespaceValue = namespace.value\n const slugValue = workspaceSlug.value\n if (!namespaceValue || !slugValue) {\n return\n }\n const workspaceId = getWorkspaceId(namespaceValue, slugValue)\n const updateResult = await persistence.updateName({ namespace: namespaceValue, slug: slugValue }, name)\n\n // If the update fails, return early\n if (updateResult === undefined) {\n return\n }\n\n // Update the workspace list\n workspaces.value = workspaces.value.map((workspace) => {\n // If the workspace is the currently active workspace, update the label\n if (workspace.id === workspaceId) {\n return { ...workspace, label: name }\n }\n return workspace\n })\n activeWorkspace.value = { id: workspaceId, label: name }\n }\n\n /**\n * Creates a client-side workspace store with persistence enabled for the given workspace id.\n */\n const createClientStore = async ({\n namespace,\n slug,\n }: {\n namespace: string\n slug: string\n }): Promise<WorkspaceStore> => {\n return createWorkspaceStore({\n plugins: [\n await persistencePlugin({\n workspaceId: getWorkspaceId(namespace, slug),\n debounceDelay: DEFAULT_DEBOUNCE_DELAY,\n }),\n ],\n fileLoader,\n fetch: options?.customFetch,\n })\n }\n\n /**\n * Attempts to load and activate a workspace by id.\n * Returns true when the workspace was found and activated.\n */\n const loadWorkspace = async (\n namespace: string,\n slug: string,\n ): Promise<{ success: true; workspace: Workspace } | { success: false }> => {\n const workspace = await persistence.getItem({ namespace, slug })\n\n if (!workspace) {\n return {\n success: false,\n }\n }\n\n const client = await createClientStore({ namespace, slug })\n client.loadWorkspace(workspace.workspace)\n activeWorkspace.value = { id: getWorkspaceId(workspace.namespace, workspace.slug), label: workspace.name }\n store.value = client\n\n return {\n success: true,\n workspace: client.workspace,\n }\n }\n\n /**\n * Creates and persists the default workspace with a blank draft document.\n * Used when no workspaces exist yet.\n */\n const createAndPersistWorkspace = async ({\n name,\n teamUid,\n namespace,\n slug,\n }: {\n name: string\n teamUid?: string\n namespace?: string\n slug: string\n }) => {\n const draftStore = createWorkspaceStore()\n await draftStore.addDocument({\n name: 'drafts',\n document: {\n openapi: '3.1.0',\n info: {\n title: 'Drafts',\n version: '1.0.0',\n },\n paths: {\n '/': {\n get: {},\n },\n },\n 'x-scalar-original-document-hash': 'drafts',\n 'x-scalar-icon': 'interface-edit-tool-pencil',\n },\n })\n\n // Persist the workspace\n const workspace = await persistence.setItem(\n { namespace, slug },\n {\n name: name,\n teamUid,\n workspace: draftStore.exportWorkspace(),\n },\n )\n\n // Update the workspace list\n workspaces.value.push({\n id: getWorkspaceId(workspace.namespace, workspace.slug),\n teamUid: workspace.teamUid,\n namespace: workspace.namespace,\n slug: workspace.slug,\n label: workspace.name,\n })\n return workspace\n }\n\n /**\n * Navigates to the overview page of the specified workspace.\n *\n * @param namespace - The workspace namespace.\n * @param slug - The unique workspace slug (identifier).\n */\n const navigateToWorkspace = async (namespace?: string, slug?: string): Promise<void> => {\n if (!namespace || !slug) {\n await router.push('/')\n return\n }\n\n // We should always have this drafts document available in a new workspace\n await router.push({\n name: 'example',\n params: {\n namespace,\n workspaceSlug: slug,\n documentSlug: 'drafts',\n pathEncoded: encodeURIComponent('/'),\n method: 'get',\n exampleName: 'default',\n },\n })\n }\n\n /**\n * Creates a new workspace with the provided name.\n * - Generates a unique slug for the workspace (uses the provided slug if it is unique, otherwise generates a unique slug).\n * - Adds a default blank document (\"drafts\") to the workspace.\n * - Persists the workspace and navigates to it.\n *\n * Example usage:\n * await createWorkspace({ name: 'My Awesome API' })\n * // -> Navigates to /workspace/my-awesome-api (if available)\n */\n const createWorkspace = async ({\n teamUid,\n namespace,\n slug,\n name,\n }: {\n teamUid?: string\n namespace?: string\n slug?: string\n name: string\n }) => {\n // Clear up the current store, in order to show the loading state\n store.value = null\n\n // Generate a unique slug/id for the workspace, based on the name.\n const newWorkspaceSlug = await generateUniqueValue({\n defaultValue: slug ?? name, // Use the provided id if it exists, otherwise use the name\n validation: async (value) => !(await persistence.has({ namespace: namespace ?? 'local', slug: value })),\n maxRetries: 100,\n transformation: slugify,\n })\n\n // Failed to generate a unique workspace id, so we can't create the workspace.\n if (!newWorkspaceSlug) {\n return undefined\n }\n\n const newWorkspaceDetails = {\n teamUid,\n namespace,\n slug: newWorkspaceSlug,\n name,\n }\n\n // Create a new client store with the workspace ID and add a default document.\n const createdWorkspace = await createAndPersistWorkspace(newWorkspaceDetails)\n\n // Navigate to the newly created workspace.\n await navigateToWorkspace(createdWorkspace.namespace, createdWorkspace.slug)\n return createdWorkspace\n }\n\n /**\n * Handles changing the active workspace when the workspace slug changes in the route.\n * This function:\n * - Clears the current workspace store and sets loading state.\n * - Attempts to load the workspace by slug.\n * - If found, navigates to the active tab path (if available).\n * - If not found, creates the default workspace and navigates to it.\n */\n const changeWorkspace = async (namespace: string, slug: string, to?: RouteLocationNormalizedGeneric) => {\n /** For initial load we want to fall through to our router default behaviour */\n const isInitialLoad = activeWorkspace.value === null\n\n // Clear the current store and set loading to true before loading new workspace.\n store.value = null\n isSyncingWorkspace.value = true\n\n // Try to load the workspace\n const result = await loadWorkspace(namespace, slug)\n\n if (result.success) {\n // Navigate to the correct tab if the workspace has a tab already\n const index = result.workspace['x-scalar-active-tab'] ?? 0\n const tabs = result.workspace['x-scalar-tabs']\n const tab = tabs?.[index]\n\n // On initial load let the URL-based routing (catch-all → getLastPath) take precedence\n if (tab && !isInitialLoad) {\n // Preserve query parameters when navigating to the active tab\n await router.replace({\n path: tab.path,\n query: currentRoute.value?.query ?? {},\n })\n }\n\n // Heal the active tab index if it is out of bounds\n if (tabs && index >= tabs.length) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-active-tab': 0,\n })\n }\n\n // Initialize the tabs if they does not exist\n if (!tabs) {\n eventBus.emit('tabs:update:tabs', {\n 'x-scalar-tabs': [createTabFromRoute(currentRoute.value)],\n 'x-scalar-active-tab': 0,\n })\n }\n\n // On initial load the router.replace above is skipped, so syncTabs/syncSidebar\n // are never reached via handleRouteChange's normal flow. Call them here to\n // align the tab bar and sidebar with the URL-based route.\n if (isInitialLoad && to) {\n syncTabs(to)\n syncSidebar(to)\n }\n\n isSyncingWorkspace.value = false\n return\n }\n\n // Navigate to the default workspace, or fall back to the first available workspace\n const targetWorkspace =\n filteredWorkspaces.value.find((workspace) => workspace.teamUid === 'local' && workspace.slug === 'default') ??\n filteredWorkspaces.value[0]\n\n if (targetWorkspace) {\n return navigateToWorkspace(targetWorkspace.namespace, targetWorkspace.slug)\n }\n\n // If loading failed (workspace does not exist), create the default workspace and navigate to it.\n const createResult = await createWorkspace({\n name: 'Default Workspace',\n slug: 'default',\n })\n\n isSyncingWorkspace.value = false\n\n if (!createResult) {\n return console.error('Failed to create the default workspace, something went wrong, can not load the workspace')\n }\n\n // Must reset the sidebar state when the workspace changes\n sidebarState.reset()\n }\n\n /**\n * Sets the current team context. If the active workspace is not accessible\n * with the new team, navigates to the default workspace for that team.\n */\n const setTeamUid = (value: string) => {\n // Update the new teamUid\n teamUid.value = value\n\n // Find the current workspace\n const workspace = filteredWorkspaces.value.find(\n (w) => w.namespace === namespace.value && w.slug === workspaceSlug.value,\n )\n\n // Check if new teamUid is accessible to the current workspace\n if (workspace && canLoadWorkspace(workspace.teamUid, value)) {\n return\n }\n\n return navigateToWorkspace('local', 'default')\n }\n\n // ---------------------------------------------------------------------------\n // Sidebar state management\n\n const entries = computed(() => {\n const activeStore = store.value\n if (!activeStore) {\n return []\n }\n\n const order = activeStore.workspace['x-scalar-order'] ?? Object.keys(activeStore.workspace.documents)\n\n return sortByOrder(Object.keys(activeStore.workspace.documents), order, (item) => item)\n .map((doc) => activeStore.workspace.documents[doc]?.['x-scalar-navigation'])\n .filter(isDefined) as TraversedEntry[]\n })\n\n const sidebarState = createSidebarState(entries)\n\n /**\n * Generates a unique string ID for an API location, based on the document, path, method, and example.\n * Filters out undefined values and serializes the composite array into a stable string.\n *\n * @param params - An object containing document, path, method, and optional example name.\n * @returns A stringified array representing the unique location identifier.\n *\n * Example:\n * generateId({ document: 'mydoc', path: '/users', method: 'get', example: 'default' })\n * // => '[\"mydoc\",\"/users\",\"get\",\"default\"]'\n */\n const generateId = ({\n document,\n path,\n method,\n example,\n }: {\n document: string\n path?: string\n method?: HttpMethod\n example?: string\n }) => {\n return JSON.stringify([document, path, method, example].filter(isDefined))\n }\n\n /**\n * Computed index for fast lookup of sidebar nodes by their unique API location.\n *\n * - Only indexes nodes of type 'document', 'operation', or 'example'.\n * - The lookup key is a serialized array of: [documentName, operationPath, operationMethod, exampleName?].\n * - Supports precise resolution of sidebar entries given an API \"location\".\n */\n const locationIndex = computed(() =>\n generateReverseIndex({\n items: entries.value,\n nestedKey: 'children',\n filter: (node) => node.type === 'document' || node.type === 'operation' || node.type === 'example',\n getId: (node) => {\n const document = getParentEntry('document', node)\n const operation = getParentEntry('operation', node)\n return generateId({\n document: document?.name ?? '',\n path: operation?.path,\n method: operation?.method,\n example: node.type === 'example' ? node.name : undefined,\n })\n },\n }),\n )\n\n /**\n * Looks up a sidebar entry by its unique API location.\n * - First tries to find an entry matching all provided properties (including example).\n * - If not found, falls back to matching only the operation (ignores example field).\n * This allows resolving either examples, operations, or documents as appropriate.\n *\n * @param location - Object specifying the document name, path, method, and optional example name.\n * @returns The matching sidebar entry, or undefined if none found.\n *\n * Example:\n * const entry = getEntryByLocation({\n * document: 'pets',\n * path: '/pets',\n * method: 'get',\n * example: 'default',\n * })\n */\n const getEntryByLocation: GetEntryByLocation = (location) => {\n // Try to find an entry with the most-specific location (including example)\n const entryWithExample = locationIndex.value.get(generateId(location))\n\n if (entryWithExample) {\n return entryWithExample\n }\n\n // Fallback to the operation (ignoring example) if an example wasn't found or specified\n return locationIndex.value.get(\n generateId({\n document: location.document,\n path: location.path,\n method: location.method,\n }),\n )\n }\n\n /**\n * Handles item selection from the sidebar and routes navigation accordingly.\n *\n * Example:\n * handleSelectItem('id-of-entry')\n */\n const handleSelectItem = (id: string) => {\n const entry = sidebarState.getEntryById(id)\n\n if (!entry) {\n console.warn(`Could not find sidebar entry with id ${id} to select`)\n return\n }\n\n /** Close sidebar and navigate. Used for every branch that performs navigation. */\n const navigate = (route: RouteLocationRaw) => {\n isSidebarOpen.value = false\n return router.push(route)\n }\n\n // Navigate to the document overview page\n if (entry.type === 'document') {\n // If we are already in the document, just toggle expansion\n if (sidebarState.selectedItem.value === id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select it\n sidebarState.setSelected(id)\n sidebarState.setExpanded(id, true)\n return navigate({\n name: 'document.overview',\n params: { documentSlug: entry.name },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'operation') {\n // If we are already in an operation child we just want to toggle the explanstion\n if (sidebarState.isSelected(id) && sidebarState.selectedItem.value !== id) {\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n // Otherwise, select the first example\n const firstExample = entry.children?.find((child) => child.type === 'example')\n\n if (firstExample) {\n sidebarState.setSelected(firstExample.id)\n sidebarState.setExpanded(firstExample.id, true)\n } else {\n sidebarState.setSelected(id)\n }\n\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(entry.path),\n method: entry.method,\n exampleName: firstExample?.name ?? 'default',\n },\n })\n }\n\n // Navigate to the example page\n if (entry.type === 'example') {\n sidebarState.setSelected(id)\n const operation = getParentEntry('operation', entry)\n return navigate({\n name: 'example',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n pathEncoded: encodeURIComponent(operation?.path ?? ''),\n method: operation?.method,\n exampleName: entry.name,\n },\n })\n }\n\n if (entry.type === 'text') {\n return navigate({\n name: 'document.overview',\n params: {\n documentSlug: getParentEntry('document', entry)?.name,\n },\n })\n }\n\n sidebarState.setExpanded(id, !sidebarState.isExpanded(id))\n return\n }\n\n /**\n * Navigates to the currently active tab's path using the router.\n * Returns early if the workspace store or active tab is unavailable.\n */\n const navigateToCurrentTab = async (): Promise<void> => {\n if (!store.value) {\n return\n }\n\n const activeTabIndex = store.value.workspace['x-scalar-active-tab'] ?? 0\n const activeTab = store.value.workspace['x-scalar-tabs']?.[activeTabIndex]\n if (!activeTab) {\n return\n }\n\n await router.replace(activeTab.path)\n }\n\n /**\n * Rebuilds the sidebar for the given document.\n * This is used to refresh the sidebar state after structural changes (e.g. after adding or removing items).\n *\n * @param documentName - The name (id) of the document for which to rebuild the sidebar\n */\n const rebuildSidebar = (documentName: string | undefined) => {\n if (documentName) {\n store.value?.buildSidebar(documentName)\n }\n }\n\n /**\n * Ensures the sidebar is refreshed after a new example is created.\n *\n * If the sidebar entry for the new example does not exist or is of a different type,\n * this will rebuild the sidebar for the current document. This helps keep the sidebar state\n * consistent (e.g., after adding a new example via the UI).\n */\n const refreshSidebarAfterExampleCreation = (payload: OperationExampleMeta & { documentName?: string }) => {\n const documentName = payload.documentName ?? activeDocument.value?.['x-scalar-navigation']?.name\n if (!documentName) {\n return\n }\n\n const entry = getEntryByLocation({\n document: documentName,\n path: payload.path,\n method: payload.method,\n example: payload.exampleKey,\n })\n\n if (!entry || entry.type !== 'example') {\n // Sidebar entry for this example doesn't exist, so rebuild sidebar for consistency.\n rebuildSidebar(documentName)\n if (currentRoute.value) {\n syncSidebar(currentRoute.value)\n }\n }\n return\n }\n\n /** Width of the sidebar, with fallback to default. */\n const sidebarWidth = computed(() => store.value?.workspace?.['x-scalar-sidebar-width'] ?? DEFAULT_SIDEBAR_WIDTH)\n\n /** Handler for sidebar width changes. */\n const handleSidebarWidthUpdate = (width: number) => store.value?.update('x-scalar-sidebar-width', width)\n\n /** Controls the visibility of the sidebar. */\n const isSidebarOpen = ref(false)\n // ---------------------------------------------------------------------------\n // Tab Management\n\n /** Constants for workspace store keys */\n const TABS_KEY = 'x-scalar-tabs' as const\n const ACTIVE_TAB_KEY = 'x-scalar-active-tab' as const\n\n /**\n * Creates a tab object based on the current route and workspace state.\n * Used as a fallback when no tabs exist or when creating new tabs.\n */\n const createTabFromRoute = (to: RouteLocationNormalizedGeneric | null): Tab => {\n const method = getRouteParam('method', to)\n const path = getRouteParam('pathEncoded', to)\n const document = getRouteParam('documentSlug', to)\n const workspace = getRouteParam('workspaceSlug', to)\n return {\n ...getTabDetails({\n workspace,\n document,\n path,\n method,\n getEntryByLocation,\n }),\n path: currentRoute.value?.path ?? '',\n }\n }\n\n const tabs = computed(() => {\n return store.value?.workspace[TABS_KEY] ?? [createTabFromRoute(currentRoute.value)]\n })\n\n const activeTabIndex = computed(() => {\n return store.value?.workspace[ACTIVE_TAB_KEY] ?? 0\n })\n\n /**\n * Copies the URL of the tab at the given index to the clipboard.\n * Constructs the full URL using the current origin and the tab path.\n *\n * Note: Will silently fail if clipboard API is unavailable or the tab does not exist.\n */\n const copyTabUrl = async (index: number): Promise<void> => {\n const tab = tabs.value[index]\n\n if (!tab) {\n console.warn(`Cannot copy URL: tab at index ${index} does not exist`)\n return\n }\n\n const url = `${window.location.origin}${tab.path}`\n\n try {\n await navigator.clipboard.writeText(url)\n } catch (error) {\n console.error('Failed to copy URL to clipboard:', error)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Path syncing\n\n /** When the route changes we need to update the active entities in the store */\n const handleRouteChange = (to: RouteLocationNormalizedGeneric) => {\n const slug = getRouteParam('workspaceSlug', to)\n const document = getRouteParam('documentSlug', to)\n const namespaceValue = getRouteParam('namespace', to)\n\n // Must have an active workspace to syncs\n if (!namespaceValue || !slug) {\n return\n }\n\n // Try to see if the user can load this workspace based on the teamUid and namespace\n const workspace = workspaces.value.find(\n (workspace) => workspace.slug === slug && workspace.namespace === namespaceValue,\n )\n\n // If the workspace is not found or the teamUid does not match, try to redirect to the default workspace\n if (workspace && !canLoadWorkspace(workspace.teamUid, teamUid.value)) {\n // try to redirect to the default workspace\n return navigateToWorkspace('local', 'default')\n }\n\n namespace.value = namespaceValue\n workspaceSlug.value = slug\n documentSlug.value = document\n method.value = getRouteParam('method', to)\n path.value = getRouteParam('pathEncoded', to)\n exampleName.value = getRouteParam('exampleName', to)\n\n // Save the current path to the persistence storage\n if (to.path !== '') {\n workspaceStorage.setCurrentPath(to.path)\n }\n\n if (getWorkspaceId(namespace.value, slug) !== activeWorkspace.value?.id) {\n return changeWorkspace(namespace.value, slug, to)\n }\n\n // Update the active document if the document slug has changes\n if (document && document !== store.value?.workspace[extensions.workspace.activeDocument]) {\n store?.value?.update('x-scalar-active-document', document)\n }\n\n syncTabs(to)\n syncSidebar(to)\n return\n }\n\n /** Aligns the tabs with any potential slug changes */\n const syncTabs = (to: RouteLocationNormalizedGeneric) => {\n const tabs = store.value?.workspace['x-scalar-tabs'] ?? []\n const index = store.value?.workspace['x-scalar-active-tab'] ?? 0\n\n const tab = tabs[index]\n\n // If there is no tab or the tab path is the same we leave the tab state alone\n if (!tab || tab.path === to.path) {\n // Already on the correct path, do nothing\n return\n }\n\n // Otherwise we replace the tab content with the new route\n tabs[index] = createTabFromRoute(to)\n }\n\n /** Aligns the sidebar state with any potential slug changes */\n const syncSidebar = (to: RouteLocationNormalizedGeneric) => {\n const document = getRouteParam('documentSlug', to)\n\n if (!document) {\n // Reset selection if no document is selected\n sidebarState.setSelected(null)\n return\n }\n\n const entry = getEntryByLocation({\n document,\n path: getRouteParam('pathEncoded', to),\n method: getRouteParam('method', to),\n example: getRouteParam('exampleName', to),\n })\n\n if (entry) {\n sidebarState.setSelected(entry.id)\n sidebarState.setExpanded(entry.id, true)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Events handling\n\n initializeAppEventHandlers({\n eventBus,\n router,\n store,\n navigateToCurrentTab,\n rebuildSidebar,\n onAfterExampleCreation: refreshSidebarAfterExampleCreation,\n onSelectSidebarItem: handleSelectItem,\n onCopyTabUrl: (index) => copyTabUrl(index),\n onToggleSidebar: () => (isSidebarOpen.value = !isSidebarOpen.value),\n closeSidebar: () => (isSidebarOpen.value = false),\n renameWorkspace,\n })\n\n const theme = useTheme({\n fallbackThemeSlug,\n customThemes,\n store: store,\n })\n\n const isDarkMode = computed(() => {\n const colorMode = store.value?.workspace['x-scalar-color-mode'] ?? 'system'\n if (colorMode === 'system') {\n return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false\n }\n return colorMode === 'dark'\n })\n\n return {\n /** Active workspace store */\n store,\n sidebar: {\n state: sidebarState,\n width: sidebarWidth,\n isOpen: isSidebarOpen,\n handleSelectItem,\n handleSidebarWidthUpdate,\n getEntryByLocation,\n },\n tabs: {\n state: tabs,\n activeTabIndex,\n copyTabUrl,\n },\n workspace: {\n create: createWorkspace,\n workspaceList: workspaces,\n filteredWorkspaceList: filteredWorkspaces,\n workspaceGroups,\n activeWorkspace,\n navigateToWorkspace,\n isOpen: computed(() => Boolean(workspaceSlug.value && !documentSlug.value)),\n },\n eventBus,\n router,\n currentRoute,\n loading: isSyncingWorkspace,\n activeEntities: {\n namespace,\n workspaceSlug,\n documentSlug,\n path,\n method,\n exampleName,\n teamUid: readonly(teamUid),\n setTeamUid,\n },\n environment,\n document: activeDocument,\n isDarkMode,\n theme: {\n styles: theme.themeStyles,\n themeStyleTag: theme.themeStyleTag,\n customThemes,\n },\n telemetry,\n options,\n }\n}\n"],"mappings":"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"}
|
|
@@ -33,7 +33,13 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
|
|
|
33
33
|
onClose?: (() => any) | undefined;
|
|
34
34
|
onBack?: ((keyboardEvent: KeyboardEvent) => any) | undefined;
|
|
35
35
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Slot for custom file upload component that can trigger import.
|
|
38
|
+
*
|
|
39
|
+
* The provided `import` function automatically detects Postman collections
|
|
40
|
+
* and routes them to the Postman import modal, matching the behavior of the
|
|
41
|
+
* default file picker.
|
|
42
|
+
*/
|
|
37
43
|
fileUpload(props: {
|
|
38
44
|
/** Function to trigger import with source content and type */
|
|
39
45
|
import: (source: string, type: "file" | "raw") => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPaletteImport.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/features/command-palette/components/CommandPaletteImport.vue"],"names":[],"mappings":"
|
|
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 +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 /** Slot for custom file upload component that can trigger import */\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 * 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 if (isPostmanCollection(text)) {\n eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: text,\n },\n })\n await loader.clear()\n return\n }\n await handleImport(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=\"handleImport\"\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'\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":""}
|
|
@@ -98,6 +98,33 @@ var CommandPaletteImport_vue_vue_type_script_setup_true_lang_default = /* @__PUR
|
|
|
98
98
|
});
|
|
99
99
|
};
|
|
100
100
|
/**
|
|
101
|
+
* Import a file, routing Postman collections to the Postman import modal and
|
|
102
|
+
* everything else through the OpenAPI import flow.
|
|
103
|
+
*
|
|
104
|
+
* Shared between the default file picker and the `fileUpload` slot so custom
|
|
105
|
+
* path-based importers get the same Postman detection behavior.
|
|
106
|
+
*
|
|
107
|
+
* When `type` is `'file'` the `source` is a file path resolved through the
|
|
108
|
+
* configured `fileLoader`; when `type` is `'raw'` the `source` is treated as
|
|
109
|
+
* the file's text content directly.
|
|
110
|
+
*/
|
|
111
|
+
const handleFileImport = async (source, type = "raw") => {
|
|
112
|
+
const rawContent = await (async () => {
|
|
113
|
+
if (type === "raw") return source;
|
|
114
|
+
const result = await __props.fileLoader?.exec(source);
|
|
115
|
+
return result?.ok ? result.raw : "";
|
|
116
|
+
})();
|
|
117
|
+
if (isPostmanCollection(rawContent)) {
|
|
118
|
+
__props.eventBus.emit("ui:open:command-palette", {
|
|
119
|
+
action: "import-postman-collection",
|
|
120
|
+
payload: { inputValue: rawContent }
|
|
121
|
+
});
|
|
122
|
+
await loader.clear();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await handleImport(source, type);
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
101
128
|
* Handle file selection and import from file dialog.
|
|
102
129
|
* Reads the file as text and imports it as OpenAPI or Postman collection.
|
|
103
130
|
* Shows loading state during the import process.
|
|
@@ -109,15 +136,7 @@ var CommandPaletteImport_vue_vue_type_script_setup_true_lang_default = /* @__PUR
|
|
|
109
136
|
loader.start();
|
|
110
137
|
const onLoad = async (event) => {
|
|
111
138
|
const text = event.target?.result;
|
|
112
|
-
|
|
113
|
-
__props.eventBus.emit("ui:open:command-palette", {
|
|
114
|
-
action: "import-postman-collection",
|
|
115
|
-
payload: { inputValue: text }
|
|
116
|
-
});
|
|
117
|
-
await loader.clear();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
await handleImport(text, "raw");
|
|
139
|
+
await handleFileImport(text, "raw");
|
|
121
140
|
};
|
|
122
141
|
const reader = new FileReader();
|
|
123
142
|
reader.onload = onLoad;
|
|
@@ -152,7 +171,7 @@ var CommandPaletteImport_vue_vue_type_script_setup_true_lang_default = /* @__PUR
|
|
|
152
171
|
loader: unref(loader),
|
|
153
172
|
onSubmit: _cache[2] || (_cache[2] = ($event) => handleImport(inputContent.value))
|
|
154
173
|
}, {
|
|
155
|
-
options: withCtx(() => [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "fileUpload", { import:
|
|
174
|
+
options: withCtx(() => [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "fileUpload", { import: handleFileImport }, () => [createVNode(unref(ScalarButton), {
|
|
156
175
|
class: "hover:bg-b-2 relative max-h-8 gap-1.5 p-2 text-xs",
|
|
157
176
|
variant: "outlined",
|
|
158
177
|
onClick: unref(openSpecFileDialog)
|
|
@@ -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 /** Slot for custom file upload component that can trigger import */\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 * 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 if (isPostmanCollection(text)) {\n eventBus.emit('ui:open:command-palette', {\n action: 'import-postman-collection',\n payload: {\n inputValue: text,\n },\n })\n await loader.clear()\n return\n }\n await handleImport(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=\"handleImport\"\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'\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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"}
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"rest",
|
|
19
19
|
"testing"
|
|
20
20
|
],
|
|
21
|
-
"version": "3.2.
|
|
21
|
+
"version": "3.2.2",
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=22"
|
|
24
24
|
},
|
|
@@ -332,21 +332,21 @@
|
|
|
332
332
|
"vue-router": "5.0.4",
|
|
333
333
|
"yaml": "^2.8.0",
|
|
334
334
|
"zod": "^4.3.5",
|
|
335
|
-
"@scalar/components": "0.22.
|
|
336
|
-
"@scalar/helpers": "0.5.
|
|
335
|
+
"@scalar/components": "0.22.3",
|
|
336
|
+
"@scalar/helpers": "0.5.2",
|
|
337
|
+
"@scalar/json-magic": "0.12.8",
|
|
337
338
|
"@scalar/icons": "0.7.2",
|
|
338
|
-
"@scalar/oas-utils": "0.13.
|
|
339
|
+
"@scalar/oas-utils": "0.13.2",
|
|
339
340
|
"@scalar/openapi-types": "0.8.0",
|
|
340
|
-
"@scalar/
|
|
341
|
-
"@scalar/postman-to-openapi": "0.7.
|
|
342
|
-
"@scalar/snippetz": "0.9.1",
|
|
343
|
-
"@scalar/sidebar": "0.9.3",
|
|
341
|
+
"@scalar/sidebar": "0.9.4",
|
|
342
|
+
"@scalar/postman-to-openapi": "0.7.1",
|
|
344
343
|
"@scalar/themes": "0.15.3",
|
|
345
|
-
"@scalar/
|
|
346
|
-
"@scalar/
|
|
344
|
+
"@scalar/snippetz": "0.9.2",
|
|
345
|
+
"@scalar/types": "0.9.2",
|
|
346
|
+
"@scalar/workspace-store": "0.46.3",
|
|
347
347
|
"@scalar/use-hooks": "0.4.3",
|
|
348
|
-
"@scalar/
|
|
349
|
-
"@scalar/
|
|
348
|
+
"@scalar/use-toasts": "0.10.2",
|
|
349
|
+
"@scalar/use-codemirror": "0.14.11"
|
|
350
350
|
},
|
|
351
351
|
"devDependencies": {
|
|
352
352
|
"@tailwindcss/vite": "^4.2.0",
|
|
@@ -361,7 +361,7 @@
|
|
|
361
361
|
"vite": "8.0.0",
|
|
362
362
|
"vite-svg-loader": "5.1.1",
|
|
363
363
|
"vitest": "4.1.0",
|
|
364
|
-
"@scalar/pre-post-request-scripts": "0.4.
|
|
364
|
+
"@scalar/pre-post-request-scripts": "0.4.5"
|
|
365
365
|
},
|
|
366
366
|
"scripts": {
|
|
367
367
|
"build": "vite build && vue-tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|