@scalar/api-client 3.20.0 → 3.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/components/HttpMethod/HttpMethod.vue.d.ts +2 -2
- package/dist/components/HttpMethod/HttpMethod.vue.d.ts.map +1 -1
- package/dist/components/HttpMethod/HttpMethod.vue.js +1 -1
- package/dist/components/HttpMethod/HttpMethod.vue.js.map +1 -1
- package/dist/components/HttpMethod/HttpMethod.vue.script.js +15 -7
- package/dist/components/HttpMethod/HttpMethod.vue.script.js.map +1 -1
- package/dist/style.css +47 -42
- package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts +1 -2
- package/dist/v2/blocks/operation-block/OperationBlock.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js +11 -0
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.d.ts +1 -2
- package/dist/v2/blocks/operation-block/components/Header.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.js +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/Header.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/OpenApiClientButton.vue.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/components/OpenApiClientButton.vue.js +1 -1
- package/dist/v2/blocks/operation-block/components/OpenApiClientButton.vue.js.map +1 -1
- package/dist/v2/blocks/operation-block/components/OpenApiClientButton.vue.script.js +2 -1
- package/dist/v2/blocks/operation-block/components/OpenApiClientButton.vue.script.js.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/har-to-fetch-response.d.ts +1 -2
- package/dist/v2/blocks/operation-block/helpers/har-to-fetch-response.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/har-to-fetch-response.js.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/send-request.d.ts +1 -2
- package/dist/v2/blocks/operation-block/helpers/send-request.d.ts.map +1 -1
- package/dist/v2/blocks/operation-block/helpers/send-request.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.d.ts +3 -2
- package/dist/v2/blocks/request-block/RequestBlock.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.js +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js +17 -2
- package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.d.ts +4 -0
- package/dist/v2/blocks/request-block/components/RequestBody.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.js +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js +8 -5
- package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBodyForm.vue.d.ts +2 -0
- package/dist/v2/blocks/request-block/components/RequestBodyForm.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBodyForm.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBodyForm.vue.script.js +12 -4
- package/dist/v2/blocks/request-block/components/RequestBodyForm.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestCodeSnippet.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestCodeSnippet.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestCodeSnippet.vue.script.js +13 -3
- package/dist/v2/blocks/request-block/components/RequestCodeSnippet.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestParams.vue.d.ts +2 -0
- package/dist/v2/blocks/request-block/components/RequestParams.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestParams.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestParams.vue.script.js +7 -2
- package/dist/v2/blocks/request-block/components/RequestParams.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTable.vue.d.ts +4 -4
- package/dist/v2/blocks/request-block/components/RequestTable.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTable.vue.js +1 -1
- package/dist/v2/blocks/request-block/components/RequestTable.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTable.vue.script.js +1 -0
- package/dist/v2/blocks/request-block/components/RequestTable.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTableRow.vue.d.ts +4 -4
- package/dist/v2/blocks/request-block/components/RequestTableRow.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestTableRow.vue.script.js +2 -2
- package/dist/v2/blocks/request-block/components/RequestTableRow.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/helpers/create-parameter-rows.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/helpers/create-parameter-rows.js +14 -6
- package/dist/v2/blocks/request-block/helpers/create-parameter-rows.js.map +1 -1
- package/dist/v2/blocks/request-block/helpers/get-form-body-rows.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/helpers/get-form-body-rows.js +5 -3
- package/dist/v2/blocks/request-block/helpers/get-form-body-rows.js.map +1 -1
- package/dist/v2/blocks/response-block/ResponseBlock.vue.d.ts.map +1 -1
- package/dist/v2/blocks/response-block/ResponseBlock.vue.js +1 -1
- package/dist/v2/blocks/response-block/ResponseBlock.vue.js.map +1 -1
- package/dist/v2/blocks/response-block/ResponseBlock.vue.script.js +4 -1
- package/dist/v2/blocks/response-block/ResponseBlock.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.d.ts +2 -3
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.js +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.script.js +2 -2
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBar.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.d.ts +1 -2
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.js +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.script.js +7 -6
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.script.js.map +1 -1
- package/dist/v2/components/data-table/DataTable.vue.d.ts.map +1 -1
- package/dist/v2/components/data-table/DataTable.vue.js.map +1 -1
- package/dist/v2/components/data-table/DataTable.vue.script.js +2 -1
- package/dist/v2/components/data-table/DataTable.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/localization/locales/ar.d.ts +4 -0
- package/dist/v2/features/localization/locales/ar.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/ar.js +8 -2
- package/dist/v2/features/localization/locales/ar.js.map +1 -1
- package/dist/v2/features/localization/locales/de.d.ts +4 -0
- package/dist/v2/features/localization/locales/de.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/de.js +8 -2
- package/dist/v2/features/localization/locales/de.js.map +1 -1
- package/dist/v2/features/localization/locales/es.d.ts +4 -0
- package/dist/v2/features/localization/locales/es.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/es.js +8 -2
- package/dist/v2/features/localization/locales/es.js.map +1 -1
- package/dist/v2/features/localization/locales/fr.d.ts +4 -0
- package/dist/v2/features/localization/locales/fr.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/fr.js +8 -2
- package/dist/v2/features/localization/locales/fr.js.map +1 -1
- package/dist/v2/features/localization/locales/pt.d.ts +4 -0
- package/dist/v2/features/localization/locales/pt.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/pt.js +8 -2
- package/dist/v2/features/localization/locales/pt.js.map +1 -1
- package/dist/v2/features/localization/locales/ru.d.ts +4 -0
- package/dist/v2/features/localization/locales/ru.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/ru.js +8 -2
- package/dist/v2/features/localization/locales/ru.js.map +1 -1
- package/dist/v2/features/localization/locales/zh-cn.d.ts +4 -0
- package/dist/v2/features/localization/locales/zh-cn.d.ts.map +1 -1
- package/dist/v2/features/localization/locales/zh-cn.js +8 -2
- package/dist/v2/features/localization/locales/zh-cn.js.map +1 -1
- package/dist/v2/features/localization/translations.d.ts +4 -0
- package/dist/v2/features/localization/translations.d.ts.map +1 -1
- package/dist/v2/features/localization/translations.js +8 -2
- package/dist/v2/features/localization/translations.js.map +1 -1
- package/dist/v2/features/modal/Modal.vue.d.ts +1 -2
- package/dist/v2/features/modal/Modal.vue.d.ts.map +1 -1
- package/dist/v2/features/modal/Modal.vue.js.map +1 -1
- package/dist/v2/features/modal/Modal.vue.script.js.map +1 -1
- package/dist/v2/features/modal/helpers/resolve-route-parameters.d.ts +2 -3
- package/dist/v2/features/modal/helpers/resolve-route-parameters.d.ts.map +1 -1
- package/dist/v2/features/modal/helpers/resolve-route-parameters.js +8 -7
- package/dist/v2/features/modal/helpers/resolve-route-parameters.js.map +1 -1
- package/dist/v2/features/modal/hooks/use-modal-sidebar.d.ts +2 -3
- package/dist/v2/features/modal/hooks/use-modal-sidebar.d.ts.map +1 -1
- package/dist/v2/features/modal/hooks/use-modal-sidebar.js.map +1 -1
- package/dist/v2/features/operation/Operation.vue.d.ts +1 -2
- package/dist/v2/features/operation/Operation.vue.d.ts.map +1 -1
- package/dist/v2/features/operation/Operation.vue.js.map +1 -1
- package/dist/v2/features/operation/Operation.vue.script.js.map +1 -1
- package/dist/v2/helpers/generate-location-id.d.ts +1 -2
- package/dist/v2/helpers/generate-location-id.d.ts.map +1 -1
- package/dist/v2/helpers/generate-location-id.js.map +1 -1
- package/dist/vue-styles.css +40 -40
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-parameter-rows.js","names":[],"sources":["../../../../../src/v2/blocks/request-block/helpers/create-parameter-rows.ts"],"sourcesContent":["import { getValueAtPath } from '@scalar/helpers/object/get-value-at-path'\nimport { isObject } from '@scalar/helpers/object/is-object'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport { deSerializeParameter, getExample, isParamDisabled } from '@scalar/workspace-store/request-example'\nimport type {\n ParameterObject,\n ParameterWithSchemaObject,\n ReferenceType,\n SchemaObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { isArraySchema, isObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/type-guards'\n\nimport type { TableRow } from '../components/RequestTableRow.vue'\nimport { getParameterSchema } from './get-parameter-schema'\n\ntype ParameterWithRequiredSchema = ParameterWithSchemaObject & {\n schema: ReferenceType<SchemaObject>\n}\n\n/** Serialization mode for object-typed query parameters that we expand into multiple rows. */\ntype ExpansionMode = 'form' | 'deepObject'\n\nconst isParameterWithSchema = (parameter: ParameterObject): parameter is ParameterWithRequiredSchema =>\n 'schema' in parameter && parameter.schema !== undefined\n\nconst resolveSchema = (schema: unknown): SchemaObject | undefined => {\n const resolvedSchema = getResolvedRef(schema as SchemaObject | { '$ref': string; '$ref-value': SchemaObject })\n\n return resolvedSchema\n}\n\n/**\n * Resolves the OpenAPI serialization style and explode flag for a parameter, falling back to the\n * spec defaults (style: 'form', explode: true when style is 'form').\n */\nconst getParameterStyleAndExplode = (parameter: ParameterObject): { style: string; explode: boolean } => {\n const style = 'style' in parameter && parameter.style ? parameter.style : 'form'\n const explode = 'explode' in parameter && parameter.explode !== undefined ? parameter.explode : style === 'form'\n\n return { style, explode }\n}\n\n/**\n * Decides whether an object-typed query parameter should be expanded into one row per property,\n * and which expansion mode to use. Returns null when the parameter should stay as a single row\n * (non-query, non-object schema, or a serialization style we do not expand).\n */\nconst getExpansionMode = (parameter: ParameterObject, schema: SchemaObject | undefined): ExpansionMode | null => {\n if (parameter.in !== 'query' || !isParameterWithSchema(parameter) || !schema || !isObjectSchema(schema)) {\n return null\n }\n\n const { style, explode } = getParameterStyleAndExplode(parameter)\n if (!explode) {\n return null\n }\n\n if (style === 'form') {\n return 'form'\n }\n if (style === 'deepObject') {\n return 'deepObject'\n }\n\n return null\n}\n\nconst toTableValue = (value: unknown): string => {\n if (value === undefined || value === null) {\n return ''\n }\n\n if (Array.isArray(value)) {\n return value.join(',')\n }\n\n if (typeof value === 'object') {\n return JSON.stringify(value)\n }\n\n return String(value)\n}\n\nconst toPathKey = (path: readonly string[]): string => path.join('\\u0000')\n\nconst toTableRow = ({\n parameter,\n name,\n value,\n description,\n schema,\n isRequired,\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath,\n}: {\n parameter: ParameterObject\n name: string\n value: unknown\n description: string | undefined\n schema: SchemaObject | undefined\n isRequired: boolean | undefined\n isDisabled: boolean\n isDisabledByDefault?: boolean\n sourceParameterValuePath?: string[]\n}): TableRow => ({\n name,\n value: toTableValue(value),\n description,\n schema,\n isRequired,\n isDisabled,\n ...(isDisabledByDefault ? { isDisabledByDefault: true } : {}),\n originalParameter: parameter,\n sourceParameterValuePath,\n})\n\n/**\n * Builds the row name for an expanded property at a given value path. deepObject rows mirror the\n * wire format (`parent[a][b]`); form-style explode uses the bare top-level key.\n */\nconst getExpandedRowName = (parameter: ParameterObject, path: string[], mode: ExpansionMode): string =>\n mode === 'deepObject' ? `${parameter.name}${path.map((segment) => `[${segment}]`).join('')}` : (path[0] ?? '')\n\n/**\n * Appends the display-only trailing `[]` to deepObject array leaves so the name matches the\n * serialized `parent[ids][]=1&parent[ids][]=2` form. The bracket is display-only — the row's\n * `sourceParameterValuePath` stays bracket-free. It also lets write-back recognize the leaf as an\n * array even on renamed and unmapped rows, which carry no schema.\n */\nconst withArrayBracket = (name: string, mode: ExpansionMode, isArray: boolean): string =>\n mode === 'deepObject' && isArray ? `${name}[]` : name\n\n/** Build the single-row representation used when a parameter is not expanded. */\nconst toSingleParameterRow = (\n parameter: ParameterObject,\n schema: SchemaObject | undefined,\n value: unknown,\n isDisabled: boolean,\n isDisabledByDefault: boolean,\n): TableRow =>\n toTableRow({\n parameter,\n name: parameter.name,\n value,\n description: parameter.description,\n schema,\n isRequired: parameter.required,\n isDisabled,\n isDisabledByDefault,\n })\n\n/**\n * Walks an object schema and produces one row per property.\n *\n * - In `form` mode (the OpenAPI default for query parameters) only the top-level properties are\n * flattened, and each row is named after the property itself (`status`).\n * - In `deepObject` mode we recurse into nested objects and emit bracketed names that mirror the\n * serialization on the wire (`filter[user][id]`).\n *\n * `pathPrefix` tracks the path inside the parent parameter value so the handler can later\n * reassemble all sibling rows back into a single object payload.\n */\nconst getExpandedPropertyRows = ({\n parameter,\n schema,\n value,\n pathPrefix,\n namePrefix,\n mode,\n isDisabled,\n isDisabledByDefault,\n hiddenValuePaths,\n renamedValuePaths,\n renameTargetPaths,\n}: {\n parameter: ParameterObject\n schema: Extract<SchemaObject, { type: 'object' }>\n value: unknown\n pathPrefix: string[]\n namePrefix: string\n mode: ExpansionMode\n isDisabled: boolean\n isDisabledByDefault: boolean\n hiddenValuePaths: ReadonlySet<string>\n renamedValuePaths: ReadonlyMap<string, string[]>\n renameTargetPaths: ReadonlySet<string>\n}): TableRow[] => {\n if (!schema.properties) {\n return []\n }\n\n const requiredProperties = new Set(schema.required ?? [])\n\n return Object.entries(schema.properties).flatMap(([propertyName, propertySchema]) => {\n const resolvedPropertySchema = resolveSchema(propertySchema)\n if (!resolvedPropertySchema) {\n return []\n }\n\n const path = [...pathPrefix, propertyName]\n const name = namePrefix ? `${namePrefix}[${propertyName}]` : propertyName\n\n // When this property was renamed, the value lives at the typed key instead of the schema path.\n // Keep the row in its original slot so it does not jump to the end of the table.\n const renamedTo = renamedValuePaths.get(toPathKey(path))\n const effectivePath = renamedTo ?? path\n\n if (hiddenValuePaths.has(toPathKey(effectivePath))) {\n return []\n }\n\n if (renamedTo) {\n // The value move is debounced, so until it lands the value still sits at the old schema path.\n // Fall back to it so the renamed row shows its value immediately instead of flickering empty.\n const renamedValue = getValueAtPath(value, renamedTo)\n const effectiveValue = renamedValue === undefined ? getValueAtPath(value, path) : renamedValue\n\n return [\n toTableRow({\n parameter,\n // The renamed key carries no schema, so the value's array-ness drives the `[]` marker.\n name: withArrayBracket(getExpandedRowName(parameter, renamedTo, mode), mode, Array.isArray(effectiveValue)),\n value: effectiveValue,\n description: parameter.description,\n // The renamed key no longer maps to the schema property, so it carries no schema.\n schema: undefined,\n isRequired: false,\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath: renamedTo,\n }),\n ]\n }\n\n // Another property was renamed onto this path (for example \"a\" renamed to an existing \"b\").\n // The renamed row already occupies this slot, so skip the schema-derived row to avoid rendering\n // two rows for the same value path.\n if (renameTargetPaths.has(toPathKey(path))) {\n return []\n }\n\n // Only deepObject style recurses into nested objects. The OpenAPI 3.1 spec says form-style\n // explode flattens only the top-level properties.\n const shouldRecurse =\n mode === 'deepObject' && isObjectSchema(resolvedPropertySchema) && Boolean(resolvedPropertySchema.properties)\n\n if (shouldRecurse) {\n return getExpandedPropertyRows({\n parameter,\n schema: resolvedPropertySchema,\n value,\n pathPrefix: path,\n namePrefix: name,\n mode,\n isDisabled,\n isDisabledByDefault,\n hiddenValuePaths,\n renamedValuePaths,\n renameTargetPaths,\n })\n }\n\n const leafValue = getValueAtPath(value, path)\n\n // deepObject array values serialize with the trailing-bracket convention\n // (filter[ids][]=1&filter[ids][]=2), so mirror that in the displayed name. We base this on the\n // resolved value too, since the schema can be a composition (anyOf) that hides the array type.\n const displayName = withArrayBracket(name, mode, isArraySchema(resolvedPropertySchema) || Array.isArray(leafValue))\n\n return [\n toTableRow({\n parameter,\n name: displayName,\n value: leafValue,\n description: resolvedPropertySchema.description ?? parameter.description,\n schema: resolvedPropertySchema,\n isRequired: requiredProperties.has(propertyName),\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath: path,\n }),\n ]\n })\n}\n\n/**\n * Collects the leaf paths of an object value. In `form` mode only the top-level keys are leaves\n * (form-style explode does not flatten nested objects); in `deepObject` mode we recurse into nested\n * objects, mirroring how the rows and serialization are built.\n */\nconst collectValueLeafPaths = (value: unknown, mode: ExpansionMode): string[][] => {\n if (!isObject(value)) {\n return []\n }\n\n const paths: string[][] = []\n\n const walk = (node: Record<string, unknown>, prefix: string[]): void => {\n for (const [key, val] of Object.entries(node)) {\n const path = [...prefix, key]\n\n if (mode === 'deepObject' && isObject(val)) {\n walk(val, path)\n } else {\n paths.push(path)\n }\n }\n }\n\n walk(value, [])\n\n return paths\n}\n\n/**\n * Builds rows for value paths that the schema does not describe. This keeps user-edited keys (for\n * example a property that was renamed in the table) visible instead of snapping back to the\n * schema-derived rows on the next render.\n */\nconst getUnmappedValueRows = ({\n parameter,\n value,\n schemaRows,\n mode,\n isDisabled,\n isDisabledByDefault,\n renamedValuePaths,\n}: {\n parameter: ParameterObject\n value: unknown\n schemaRows: TableRow[]\n mode: ExpansionMode\n isDisabled: boolean\n isDisabledByDefault: boolean\n renamedValuePaths: ReadonlyMap<string, string[]>\n}): TableRow[] => {\n // Skip the renamed-away source paths too: the value move is debounced, so the old key lingers in\n // the value for a moment and would otherwise flash as a duplicate row next to the renamed one.\n const seen = new Set([\n ...schemaRows.map((row) => toPathKey(row.sourceParameterValuePath ?? [])),\n ...renamedValuePaths.keys(),\n ])\n\n // These rows always render: a key that carries a value should stay visible. The hidden-path set\n // only suppresses empty schema suggestions, so it is intentionally not applied here.\n return collectValueLeafPaths(value, mode)\n .filter((path) => !seen.has(toPathKey(path)))\n .map((path) => {\n const leafValue = getValueAtPath(value, path)\n\n return toTableRow({\n parameter,\n // No schema describes these rows, so the value's array-ness drives the `[]` marker.\n name: withArrayBracket(getExpandedRowName(parameter, path, mode), mode, Array.isArray(leafValue)),\n value: leafValue,\n description: parameter.description,\n schema: undefined,\n isRequired: false,\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath: path,\n })\n })\n}\n\n/**\n * Turns a single OpenAPI parameter into one or more table rows.\n *\n * For object-typed query parameters with `form`/`explode: true` (the default) or `deepObject`\n * serialization, each property becomes its own row so the user can edit them individually,\n * matching how tools like Postman present the same parameter. Every other parameter passes\n * through as a single row.\n */\nexport const createParameterRows = (\n parameter: ParameterObject,\n exampleKey: string,\n options: {\n hiddenValuePaths?: readonly string[][]\n renamedValuePaths?: readonly { from: string[]; to: string[] }[]\n } = {},\n): TableRow[] => {\n const example = getExample(parameter, exampleKey, undefined)\n const isDisabled = isParamDisabled(parameter, example)\n // Preserve type-to-enable behavior until the user explicitly chooses a checkbox state.\n const isDisabledByDefault = !parameter.required && parameter.in !== 'path' && example?.['x-disabled'] === undefined\n const schema = getParameterSchema(parameter)\n const mode = getExpansionMode(parameter, schema)\n\n // Non-expandable parameters: render as a single row.\n if (mode === null || !schema || !isObjectSchema(schema)) {\n return [toSingleParameterRow(parameter, schema, example?.value, isDisabled, isDisabledByDefault)]\n }\n\n // Expand into per-property rows. The deserialized value is used so existing per-property values\n // can be displayed in the table even when the example arrives as a serialized string.\n const value = example?.value === undefined ? undefined : deSerializeParameter(example.value, parameter)\n\n // Fall back to a single row only when the schema has no properties to expand.\n if (!schema.properties) {\n return [toSingleParameterRow(parameter, schema, example?.value, isDisabled, isDisabledByDefault)]\n }\n\n const hiddenValuePaths = new Set(options.hiddenValuePaths?.map(toPathKey) ?? [])\n const renamedValuePaths = new Map((options.renamedValuePaths ?? []).map(({ from, to }) => [toPathKey(from), to]))\n // Paths a property was renamed onto, used to suppress the colliding schema-derived row.\n const renameTargetPaths = new Set((options.renamedValuePaths ?? []).map(({ to }) => toPathKey(to)))\n\n const rows = getExpandedPropertyRows({\n parameter,\n schema,\n value,\n pathPrefix: [],\n // deepObject names every row with a `parent[child]` prefix; form-style names use bare property names.\n namePrefix: mode === 'deepObject' ? parameter.name : '',\n mode,\n isDisabled,\n isDisabledByDefault,\n hiddenValuePaths,\n renamedValuePaths,\n renameTargetPaths,\n })\n\n // Surface keys present in the value but not in the schema (for example a renamed property) so the\n // edited key keeps showing up instead of snapping back to the schema-derived rows.\n const unmappedRows = getUnmappedValueRows({\n parameter,\n value,\n schemaRows: rows,\n mode,\n isDisabled,\n isDisabledByDefault,\n renamedValuePaths,\n })\n\n return [...rows, ...unmappedRows]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-parameter-rows.js","names":[],"sources":["../../../../../src/v2/blocks/request-block/helpers/create-parameter-rows.ts"],"sourcesContent":["import { getValueAtPath } from '@scalar/helpers/object/get-value-at-path'\nimport { isObject } from '@scalar/helpers/object/is-object'\nimport { getParameterExample } from '@scalar/workspace-store/helpers/get-parameter-example'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport {\n getQuerystringParameter,\n serializeQuerystringParameter,\n} from '@scalar/workspace-store/helpers/querystring-parameter'\nimport { deSerializeParameter, isParamDisabled } from '@scalar/workspace-store/request-example'\nimport type {\n ParameterObject,\n ParameterWithSchemaObject,\n ReferenceType,\n SchemaObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { isArraySchema, isObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/type-guards'\n\nimport type { TableRow } from '../components/RequestTableRow.vue'\nimport { getParameterSchema } from './get-parameter-schema'\n\ntype ParameterWithRequiredSchema = ParameterWithSchemaObject & {\n schema: ReferenceType<SchemaObject>\n}\n\n/** Serialization mode for object-typed query parameters that we expand into multiple rows. */\ntype ExpansionMode = 'form' | 'deepObject'\n\nconst isParameterWithSchema = (parameter: ParameterObject): parameter is ParameterWithRequiredSchema =>\n 'schema' in parameter && parameter.schema !== undefined\n\nconst resolveSchema = (schema: unknown): SchemaObject | undefined => {\n const resolvedSchema = getResolvedRef(schema as SchemaObject | { '$ref': string; '$ref-value': SchemaObject })\n\n return resolvedSchema\n}\n\n/**\n * Resolves the OpenAPI serialization style and explode flag for a parameter, falling back to the\n * spec defaults (style: 'form', explode: true when style is 'form').\n */\nconst getParameterStyleAndExplode = (parameter: ParameterObject): { style: string; explode: boolean } => {\n const style = 'style' in parameter && parameter.style ? parameter.style : 'form'\n const explode = 'explode' in parameter && parameter.explode !== undefined ? parameter.explode : style === 'form'\n\n return { style, explode }\n}\n\n/**\n * Decides whether an object-typed query parameter should be expanded into one row per property,\n * and which expansion mode to use. Returns null when the parameter should stay as a single row\n * (non-query, non-object schema, or a serialization style we do not expand).\n */\nconst getExpansionMode = (parameter: ParameterObject, schema: SchemaObject | undefined): ExpansionMode | null => {\n if (parameter.in !== 'query' || !isParameterWithSchema(parameter) || !schema || !isObjectSchema(schema)) {\n return null\n }\n\n const { style, explode } = getParameterStyleAndExplode(parameter)\n if (!explode) {\n return null\n }\n\n if (style === 'form') {\n return 'form'\n }\n if (style === 'deepObject') {\n return 'deepObject'\n }\n\n return null\n}\n\nconst toTableValue = (value: unknown): string => {\n if (value === undefined || value === null) {\n return ''\n }\n\n if (Array.isArray(value)) {\n return value.join(',')\n }\n\n if (typeof value === 'object') {\n return JSON.stringify(value)\n }\n\n return String(value)\n}\n\nconst toPathKey = (path: readonly string[]): string => path.join('\\u0000')\n\nconst toTableRow = ({\n parameter,\n name,\n value,\n description,\n schema,\n isRequired,\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath,\n}: {\n parameter: ParameterObject\n name: string\n value: unknown\n description: string | undefined\n schema: SchemaObject | undefined\n isRequired: boolean | undefined\n isDisabled: boolean\n isDisabledByDefault?: boolean\n sourceParameterValuePath?: string[]\n}): TableRow => ({\n name,\n value: toTableValue(value),\n description,\n schema,\n isRequired,\n isDisabled,\n ...(isDisabledByDefault ? { isDisabledByDefault: true } : {}),\n originalParameter: parameter,\n sourceParameterValuePath,\n})\n\n/**\n * Builds the row name for an expanded property at a given value path. deepObject rows mirror the\n * wire format (`parent[a][b]`); form-style explode uses the bare top-level key.\n */\nconst getExpandedRowName = (parameter: ParameterObject, path: string[], mode: ExpansionMode): string =>\n mode === 'deepObject' ? `${parameter.name}${path.map((segment) => `[${segment}]`).join('')}` : (path[0] ?? '')\n\n/**\n * Appends the display-only trailing `[]` to deepObject array leaves so the name matches the\n * serialized `parent[ids][]=1&parent[ids][]=2` form. The bracket is display-only — the row's\n * `sourceParameterValuePath` stays bracket-free. It also lets write-back recognize the leaf as an\n * array even on renamed and unmapped rows, which carry no schema.\n */\nconst withArrayBracket = (name: string, mode: ExpansionMode, isArray: boolean): string =>\n mode === 'deepObject' && isArray ? `${name}[]` : name\n\n/** Build the single-row representation used when a parameter is not expanded. */\nconst toSingleParameterRow = (\n parameter: ParameterObject,\n schema: SchemaObject | undefined,\n value: unknown,\n isDisabled: boolean,\n isDisabledByDefault: boolean,\n): TableRow =>\n toTableRow({\n parameter,\n name: parameter.name,\n value,\n description: parameter.description,\n schema,\n isRequired: parameter.required,\n isDisabled,\n isDisabledByDefault,\n })\n\n/**\n * Walks an object schema and produces one row per property.\n *\n * - In `form` mode (the OpenAPI default for query parameters) only the top-level properties are\n * flattened, and each row is named after the property itself (`status`).\n * - In `deepObject` mode we recurse into nested objects and emit bracketed names that mirror the\n * serialization on the wire (`filter[user][id]`).\n *\n * `pathPrefix` tracks the path inside the parent parameter value so the handler can later\n * reassemble all sibling rows back into a single object payload.\n */\nconst getExpandedPropertyRows = ({\n parameter,\n schema,\n value,\n pathPrefix,\n namePrefix,\n mode,\n isDisabled,\n isDisabledByDefault,\n hiddenValuePaths,\n renamedValuePaths,\n renameTargetPaths,\n}: {\n parameter: ParameterObject\n schema: Extract<SchemaObject, { type: 'object' }>\n value: unknown\n pathPrefix: string[]\n namePrefix: string\n mode: ExpansionMode\n isDisabled: boolean\n isDisabledByDefault: boolean\n hiddenValuePaths: ReadonlySet<string>\n renamedValuePaths: ReadonlyMap<string, string[]>\n renameTargetPaths: ReadonlySet<string>\n}): TableRow[] => {\n if (!schema.properties) {\n return []\n }\n\n const requiredProperties = new Set(schema.required ?? [])\n\n return Object.entries(schema.properties).flatMap(([propertyName, propertySchema]) => {\n const resolvedPropertySchema = resolveSchema(propertySchema)\n if (!resolvedPropertySchema) {\n return []\n }\n\n const path = [...pathPrefix, propertyName]\n const name = namePrefix ? `${namePrefix}[${propertyName}]` : propertyName\n\n // When this property was renamed, the value lives at the typed key instead of the schema path.\n // Keep the row in its original slot so it does not jump to the end of the table.\n const renamedTo = renamedValuePaths.get(toPathKey(path))\n const effectivePath = renamedTo ?? path\n\n if (hiddenValuePaths.has(toPathKey(effectivePath))) {\n return []\n }\n\n if (renamedTo) {\n // The value move is debounced, so until it lands the value still sits at the old schema path.\n // Fall back to it so the renamed row shows its value immediately instead of flickering empty.\n const renamedValue = getValueAtPath(value, renamedTo)\n const effectiveValue = renamedValue === undefined ? getValueAtPath(value, path) : renamedValue\n\n return [\n toTableRow({\n parameter,\n // The renamed key carries no schema, so the value's array-ness drives the `[]` marker.\n name: withArrayBracket(getExpandedRowName(parameter, renamedTo, mode), mode, Array.isArray(effectiveValue)),\n value: effectiveValue,\n description: parameter.description,\n // The renamed key no longer maps to the schema property, so it carries no schema.\n schema: undefined,\n isRequired: false,\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath: renamedTo,\n }),\n ]\n }\n\n // Another property was renamed onto this path (for example \"a\" renamed to an existing \"b\").\n // The renamed row already occupies this slot, so skip the schema-derived row to avoid rendering\n // two rows for the same value path.\n if (renameTargetPaths.has(toPathKey(path))) {\n return []\n }\n\n // Only deepObject style recurses into nested objects. The OpenAPI 3.1 spec says form-style\n // explode flattens only the top-level properties.\n const shouldRecurse =\n mode === 'deepObject' && isObjectSchema(resolvedPropertySchema) && Boolean(resolvedPropertySchema.properties)\n\n if (shouldRecurse) {\n return getExpandedPropertyRows({\n parameter,\n schema: resolvedPropertySchema,\n value,\n pathPrefix: path,\n namePrefix: name,\n mode,\n isDisabled,\n isDisabledByDefault,\n hiddenValuePaths,\n renamedValuePaths,\n renameTargetPaths,\n })\n }\n\n const leafValue = getValueAtPath(value, path)\n\n // deepObject array values serialize with the trailing-bracket convention\n // (filter[ids][]=1&filter[ids][]=2), so mirror that in the displayed name. We base this on the\n // resolved value too, since the schema can be a composition (anyOf) that hides the array type.\n const displayName = withArrayBracket(name, mode, isArraySchema(resolvedPropertySchema) || Array.isArray(leafValue))\n\n return [\n toTableRow({\n parameter,\n name: displayName,\n value: leafValue,\n description: resolvedPropertySchema.description ?? parameter.description,\n schema: resolvedPropertySchema,\n isRequired: requiredProperties.has(propertyName),\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath: path,\n }),\n ]\n })\n}\n\n/**\n * Collects the leaf paths of an object value. In `form` mode only the top-level keys are leaves\n * (form-style explode does not flatten nested objects); in `deepObject` mode we recurse into nested\n * objects, mirroring how the rows and serialization are built.\n */\nconst collectValueLeafPaths = (value: unknown, mode: ExpansionMode): string[][] => {\n if (!isObject(value)) {\n return []\n }\n\n const paths: string[][] = []\n\n const walk = (node: Record<string, unknown>, prefix: string[]): void => {\n for (const [key, val] of Object.entries(node)) {\n const path = [...prefix, key]\n\n if (mode === 'deepObject' && isObject(val)) {\n walk(val, path)\n } else {\n paths.push(path)\n }\n }\n }\n\n walk(value, [])\n\n return paths\n}\n\n/**\n * Builds rows for value paths that the schema does not describe. This keeps user-edited keys (for\n * example a property that was renamed in the table) visible instead of snapping back to the\n * schema-derived rows on the next render.\n */\nconst getUnmappedValueRows = ({\n parameter,\n value,\n schemaRows,\n mode,\n isDisabled,\n isDisabledByDefault,\n renamedValuePaths,\n}: {\n parameter: ParameterObject\n value: unknown\n schemaRows: TableRow[]\n mode: ExpansionMode\n isDisabled: boolean\n isDisabledByDefault: boolean\n renamedValuePaths: ReadonlyMap<string, string[]>\n}): TableRow[] => {\n // Skip the renamed-away source paths too: the value move is debounced, so the old key lingers in\n // the value for a moment and would otherwise flash as a duplicate row next to the renamed one.\n const seen = new Set([\n ...schemaRows.map((row) => toPathKey(row.sourceParameterValuePath ?? [])),\n ...renamedValuePaths.keys(),\n ])\n\n // These rows always render: a key that carries a value should stay visible. The hidden-path set\n // only suppresses empty schema suggestions, so it is intentionally not applied here.\n return collectValueLeafPaths(value, mode)\n .filter((path) => !seen.has(toPathKey(path)))\n .map((path) => {\n const leafValue = getValueAtPath(value, path)\n\n return toTableRow({\n parameter,\n // No schema describes these rows, so the value's array-ness drives the `[]` marker.\n name: withArrayBracket(getExpandedRowName(parameter, path, mode), mode, Array.isArray(leafValue)),\n value: leafValue,\n description: parameter.description,\n schema: undefined,\n isRequired: false,\n isDisabled,\n isDisabledByDefault,\n sourceParameterValuePath: path,\n })\n })\n}\n\n/**\n * Turns a single OpenAPI parameter into one or more table rows.\n *\n * For object-typed query parameters with `form`/`explode: true` (the default) or `deepObject`\n * serialization, each property becomes its own row so the user can edit them individually,\n * matching how tools like Postman present the same parameter. Every other parameter passes\n * through as a single row.\n */\nexport const createParameterRows = (\n parameter: ParameterObject,\n exampleKey: string,\n options: {\n hiddenValuePaths?: readonly string[][]\n renamedValuePaths?: readonly { from: string[]; to: string[] }[]\n } = {},\n): TableRow[] => {\n const selected = getParameterExample(parameter, exampleKey)\n const { example } = selected\n const displayValue = selected.value === null && example?.dataValue === null ? 'null' : selected.value\n const isDisabled = isParamDisabled(parameter, example)\n // Preserve type-to-enable behavior until the user explicitly chooses a checkbox state.\n const isDisabledByDefault = !parameter.required && parameter.in !== 'path' && example?.['x-disabled'] === undefined\n const schema = getParameterSchema(parameter)\n if (parameter.in === 'querystring') {\n const querystring = getQuerystringParameter(parameter, exampleKey, { includeDisabled: true })\n return [\n toSingleParameterRow(\n parameter,\n // The editor accepts URI-ready text; decoded schema suggestions are not valid wire values.\n undefined,\n querystring ? serializeQuerystringParameter(querystring) : '',\n isDisabled,\n isDisabledByDefault,\n ),\n ]\n }\n const mode = selected.serialized ? null : getExpansionMode(parameter, schema)\n\n // Non-expandable parameters: render as a single row.\n if (mode === null || !schema || !isObjectSchema(schema)) {\n return [\n toSingleParameterRow(\n parameter,\n selected.serialized ? undefined : schema,\n displayValue,\n isDisabled,\n isDisabledByDefault,\n ),\n ]\n }\n\n // Expand into per-property rows. The deserialized value is used so existing per-property values\n // can be displayed in the table even when the example arrives as a serialized string.\n const value = selected.value === undefined ? undefined : deSerializeParameter(selected.value, parameter)\n\n // Fall back to a single row only when the schema has no properties to expand.\n if (!schema.properties) {\n return [\n toSingleParameterRow(\n parameter,\n selected.serialized ? undefined : schema,\n displayValue,\n isDisabled,\n isDisabledByDefault,\n ),\n ]\n }\n\n const hiddenValuePaths = new Set(options.hiddenValuePaths?.map(toPathKey) ?? [])\n const renamedValuePaths = new Map((options.renamedValuePaths ?? []).map(({ from, to }) => [toPathKey(from), to]))\n // Paths a property was renamed onto, used to suppress the colliding schema-derived row.\n const renameTargetPaths = new Set((options.renamedValuePaths ?? []).map(({ to }) => toPathKey(to)))\n\n const rows = getExpandedPropertyRows({\n parameter,\n schema,\n value,\n pathPrefix: [],\n // deepObject names every row with a `parent[child]` prefix; form-style names use bare property names.\n namePrefix: mode === 'deepObject' ? parameter.name : '',\n mode,\n isDisabled,\n isDisabledByDefault,\n hiddenValuePaths,\n renamedValuePaths,\n renameTargetPaths,\n })\n\n // Surface keys present in the value but not in the schema (for example a renamed property) so the\n // edited key keeps showing up instead of snapping back to the schema-derived rows.\n const unmappedRows = getUnmappedValueRows({\n parameter,\n value,\n schemaRows: rows,\n mode,\n isDisabled,\n isDisabledByDefault,\n renamedValuePaths,\n })\n\n return [...rows, ...unmappedRows]\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-form-body-rows.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/request-block/helpers/get-form-body-rows.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAA;AAG/G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0DAA0D,CAAA;AAExF,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,WAQ5C,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,YAAY,GAAG,SAAS,CAAA;IAChC,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,YAAY,EACpB,aAAY,MAAM,EAAO,EACzB,wBAAqB,KACpB,OAAO,EA6BT,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,OAAO,EAChB,QAAQ,YAAY,GAAG,SAAS,EAChC,aAAY,MAAM,EAAO,KACxB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAqCpC,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,GAAI,KAAK,QAAQ,KAAG,MAAM,GAAG,IAAI,GAAG,OAAO,EAAE,GAAG,SAG5E,CAAA;AAED,uGAAuG;AACvG,eAAO,MAAM,eAAe,GAC1B,SAAS,aAAa,GAAG,SAAS,GAAG,IAAI,EACzC,aAAa,MAAM,EACnB,iBAAiB,YAAY,KAC5B,QAAQ,
|
|
1
|
+
{"version":3,"file":"get-form-body-rows.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/request-block/helpers/get-form-body-rows.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAA;AAG/G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0DAA0D,CAAA;AAExF,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,WAQ5C,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,YAAY,GAAG,SAAS,CAAA;IAChC,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,YAAY,EACpB,aAAY,MAAM,EAAO,EACzB,wBAAqB,KACpB,OAAO,EA6BT,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,OAAO,EAChB,QAAQ,YAAY,GAAG,SAAS,EAChC,aAAY,MAAM,EAAO,KACxB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAqCpC,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,GAAI,KAAK,QAAQ,KAAG,MAAM,GAAG,IAAI,GAAG,OAAO,EAAE,GAAG,SAG5E,CAAA;AAED,uGAAuG;AACvG,eAAO,MAAM,eAAe,GAC1B,SAAS,aAAa,GAAG,SAAS,GAAG,IAAI,EACzC,aAAa,MAAM,EACnB,iBAAiB,YAAY,KAC5B,QAAQ,EAyIV,CAAA"}
|
|
@@ -120,12 +120,14 @@ var getFormBodyRows = (example, contentType, formBodySchema) => {
|
|
|
120
120
|
if (Array.isArray(value)) return value.flatMap((exampleValue) => {
|
|
121
121
|
if (isObject(exampleValue)) {
|
|
122
122
|
const name = String(exampleValue.name);
|
|
123
|
-
|
|
123
|
+
return (contentType === "multipart/form-data" ? mapValue(name, exampleValue.value, Boolean(exampleValue.isDisabled), exampleValue.isArray === true) : [mapRow({
|
|
124
124
|
name,
|
|
125
125
|
value: exampleValue.value instanceof File ? exampleValue.value : String(exampleValue.value),
|
|
126
126
|
isDisabled: Boolean(exampleValue.isDisabled)
|
|
127
|
-
})
|
|
128
|
-
|
|
127
|
+
})]).map((row) => row.isDisabled && exampleValue.isDisabledByDefault === true ? {
|
|
128
|
+
...row,
|
|
129
|
+
isDisabledByDefault: true
|
|
130
|
+
} : row);
|
|
129
131
|
}
|
|
130
132
|
return {
|
|
131
133
|
name: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-form-body-rows.js","names":[],"sources":["../../../../../src/v2/blocks/request-block/helpers/get-form-body-rows.ts"],"sourcesContent":["import { isObject } from '@scalar/helpers/object/is-object'\nimport { objectEntries } from '@scalar/helpers/object/object-entries'\nimport { coerceLeafValueToSchemaType } from '@scalar/workspace-store/request-example'\nimport { resolve } from '@scalar/workspace-store/resolve'\nimport type { ExampleObject, SchemaObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { isObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/type-guards'\n\nimport type { TableRow } from '@/v2/blocks/request-block/components/RequestTableRow.vue'\n\nexport const stringifyValue = (value: unknown) => {\n if (value instanceof File) {\n return value.name\n }\n if (typeof value === 'object' && value !== null) {\n return JSON.stringify(value)\n }\n return String(value)\n}\n\nexport type LeafRow = {\n path: string[]\n schema: SchemaObject | undefined\n isRequired: boolean\n}\n\n/**\n * Walk a (possibly nested) object schema and collect one entry per leaf property.\n *\n * A \"leaf\" is any property whose resolved schema is not an `{ type: 'object' }` schema\n * with named `properties` — i.e. primitives, arrays, files, and `additionalProperties`\n * placeholders all stop the recursion. Object properties recurse, so the widget example\n * (`file` + `props.{name,description,created_at}`) yields four leaves.\n */\nexport const collectLeafProperties = (\n schema: SchemaObject,\n parentPath: string[] = [],\n parentRequired = true,\n): LeafRow[] => {\n // Nothing to walk if this is not an object schema or has no declared properties.\n if (!isObjectSchema(schema) || !schema.properties) {\n return []\n }\n\n // `required` is a list of property names on *this* schema, so build it once per level.\n const requiredSet = new Set(schema.required ?? [])\n const leaves: LeafRow[] = []\n\n for (const [key, rawChildSchema] of objectEntries(schema.properties)) {\n const childSchema = resolve.schema(rawChildSchema)\n const path = [...parentPath, String(key)]\n\n // A leaf is only required when *every* ancestor was also required — a non-required\n // parent makes the whole subtree optional regardless of inner `required` lists.\n const isRequired = parentRequired && requiredSet.has(String(key))\n\n // Nested objects with named properties recurse so each leaf gets its own row;\n // everything else (primitives, arrays, files, additionalProperties placeholders)\n // is treated as a leaf and stops the descent.\n if (childSchema && isObjectSchema(childSchema) && childSchema.properties) {\n leaves.push(...collectLeafProperties(childSchema, path, isRequired))\n } else {\n leaves.push({ path, schema: childSchema, isRequired })\n }\n }\n\n return leaves\n}\n\n/**\n * Walk a schema + example together and collect one row per leaf, including example-only\n * keys that the schema does not declare. Schema-declared properties come first in schema\n * order; extras present in the example follow in example order at each level.\n *\n * Unlike `collectLeafProperties` (which is schema-only and feeds the dotted-name index),\n * this walker drives what the user actually sees in the form: a row for every leaf the\n * example contains, even when the schema is partial or out of date.\n */\nexport const collectExampleRows = (\n example: unknown,\n schema: SchemaObject | undefined,\n parentPath: string[] = [],\n): { path: string[]; value: unknown }[] => {\n const rows: { path: string[]; value: unknown }[] = []\n const exampleEntries = isObject(example) ? objectEntries(example) : []\n const exampleByKey = new Map<string, unknown>(exampleEntries.map(([key, value]) => [String(key), value]))\n const declaredKeys = new Set<string>()\n const schemaProperties = schema && isObjectSchema(schema) ? (schema.properties ?? {}) : {}\n\n // Schema-declared properties first, in schema order. Nested object schemas recurse so\n // each leaf gets its own row; otherwise the schema-declared key is a leaf and uses the\n // matching example value (or `undefined` when the example does not provide one).\n for (const [key, rawChildSchema] of objectEntries(schemaProperties)) {\n const keyStr = String(key)\n declaredKeys.add(keyStr)\n const childSchema = resolve.schema(rawChildSchema)\n const path = [...parentPath, keyStr]\n const exampleSubvalue = exampleByKey.get(keyStr)\n\n if (childSchema && isObjectSchema(childSchema) && childSchema.properties) {\n rows.push(...collectExampleRows(exampleSubvalue, childSchema, path))\n } else {\n rows.push({ path, value: exampleSubvalue })\n }\n }\n\n // Example-only properties at this level (preserve example order). Their inner shape is\n // not descended into — without a schema saying \"this is a nested object\" we have no\n // signal to flatten, so an object value becomes a single JSON-stringified row, matching\n // the schema-less fallback below.\n for (const [key, value] of exampleEntries) {\n const keyStr = String(key)\n if (declaredKeys.has(keyStr)) {\n continue\n }\n rows.push({ path: [...parentPath, keyStr], value })\n }\n\n return rows\n}\n\n/** Restore array values after text editing without parsing ordinary string fields. */\nexport const getFormBodyValue = (row: TableRow): string | File | unknown[] | undefined => {\n const value = coerceLeafValueToSchemaType(row.value, row.isArray ? { type: 'array' } : row.schema)\n return Array.isArray(value) ? value : (row.value ?? undefined)\n}\n\n/** Build the table rows for the form data, optionally enriched with schema (e.g. enum) per property */\nexport const getFormBodyRows = (\n example: ExampleObject | undefined | null,\n contentType: string,\n formBodySchema?: SchemaObject,\n): TableRow[] => {\n // Forms use structured data even when the raw example also supplies wire text.\n const value = example?.dataValue !== undefined ? example.dataValue : example?.value\n // We only need the rows for formData\n if (!value || (contentType !== 'multipart/form-data' && contentType !== 'application/x-www-form-urlencoded')) {\n return []\n }\n\n // Get all the schema properties if the schema is an object schema\n const schemaWithProperties = formBodySchema && isObjectSchema(formBodySchema) ? formBodySchema : undefined\n // The request builder preserves composed fields because members can make them required.\n // Keep those fields checked so the initial form matches the body that will be sent.\n const isComposed = Boolean(schemaWithProperties?.allOf || schemaWithProperties?.oneOf || schemaWithProperties?.anyOf)\n const requiredSet = schemaWithProperties ? new Set(schemaWithProperties.required ?? []) : undefined\n\n // Pre-compute the leaf-by-dotted-name index up front so both the array and the\n // schema-driven branches can pull each row's leaf schema and required flag without\n // re-walking the schema. Without this, edited rows lose the per-leaf `Required`\n // badge because a top-level `properties[name]` lookup misses dotted names.\n const leafByDottedName = new Map<string, LeafRow>()\n if (schemaWithProperties) {\n for (const leaf of collectLeafProperties(schemaWithProperties)) {\n leafByDottedName.set(leaf.path.join('.'), leaf)\n }\n }\n\n const mapRow = ({\n name,\n value,\n isDisabled,\n }: {\n name: string\n value: string | File\n isDisabled?: boolean\n }): TableRow => {\n const row: TableRow = { name, value, isDisabled: isDisabled ?? false }\n if (!schemaWithProperties || !name) {\n return row\n }\n // Prefer the pre-resolved leaf (handles dotted names like `props.name`); fall back\n // to a top-level property lookup so user-added rows keep working.\n const leaf = leafByDottedName.get(name)\n const propSchema = leaf?.schema ?? resolve.schema(schemaWithProperties.properties?.[name])\n row.schema = propSchema\n row.description = propSchema?.description\n row.isRequired = leaf?.isRequired ?? requiredSet?.has(name) ?? false\n\n // Top-level optional properties default to disabled (unchecked), matching how optional\n // parameters behave. Scoped to top-level declared properties on purpose: the send side\n // (`build-request-body`) only drops top-level optional keys, so auto-disabling a nested\n // leaf (e.g. `props.name`) would leave it unchecked yet still transmitted. `Object.hasOwn`\n // (not `in`) keeps example-only keys named like `Object.prototype` members enabled. An\n // explicit `isDisabled` (from a stored form-row array) always wins.\n if (\n isDisabled === undefined &&\n !isComposed &&\n schemaWithProperties.properties &&\n Object.hasOwn(schemaWithProperties.properties, name)\n ) {\n row.isDisabled = !row.isRequired\n // Mark the row as disabled by default (no explicit `isDisabled` was stored) so typing a\n // value auto-enables it in `RequestTableRow`, matching how optional parameters behave. An\n // explicit `isDisabled` from a stored form-row array skips this branch and keeps winning.\n row.isDisabledByDefault = row.isDisabled\n }\n return row\n }\n\n const mapValue = (name: string, value: unknown, isDisabled?: boolean, isArray?: boolean): TableRow[] => {\n // Uploaded files must never pass through JSON.stringify, which discards their bytes.\n if (\n contentType === 'multipart/form-data' &&\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((item) => item instanceof File)\n ) {\n return value.map((file) => mapRow({ name, value: file, isDisabled }))\n }\n const row = mapRow({\n name,\n value: value instanceof File ? value : value == null ? '' : stringifyValue(value),\n isDisabled,\n })\n if (contentType === 'multipart/form-data' && (Array.isArray(value) || isArray)) {\n // Example-only arrays also need their type to survive a form-table round trip.\n row.isArray = true\n }\n return [row]\n }\n\n // We have form data stored as an array\n if (Array.isArray(value)) {\n return value.flatMap((exampleValue) => {\n if (isObject(exampleValue)) {\n const name = String(exampleValue.name)\n if (contentType !== 'multipart/form-data') {\n return mapRow({\n name,\n value: exampleValue.value instanceof File ? exampleValue.value : String(exampleValue.value),\n isDisabled: Boolean(exampleValue.isDisabled),\n })\n }\n return mapValue(name, exampleValue.value, Boolean(exampleValue.isDisabled), exampleValue.isArray === true)\n }\n return { name: '', value: exampleValue, isDisabled: false }\n })\n }\n\n // Schema-driven path: when the form body schema describes nested objects, emit one row\n // per leaf so users can edit individual fields. The dotted name (`props.name`) is folded\n // back into a single JSON multipart part by `build-request-body.ts` before the request is\n // sent. Gated to `multipart/form-data` because urlencoded has no regrouping step on send,\n // so dotted leaves would otherwise reach the wire as separate `props.name`-style fields.\n //\n // We walk the example alongside the schema so example-only properties (top-level extras\n // or undeclared keys inside a nested object) are still visible and editable instead of\n // being silently dropped to whatever the schema happens to declare.\n if (contentType === 'multipart/form-data' && schemaWithProperties && typeof value === 'object') {\n return collectExampleRows(value, schemaWithProperties).flatMap(({ path, value }) => mapValue(path.join('.'), value))\n }\n\n // We got an object try to convert it to an array of rows\n if (typeof value === 'object' && value) {\n return objectEntries(value).flatMap(([key, value]) =>\n contentType === 'multipart/form-data'\n ? mapValue(String(key), value)\n : mapRow({ name: String(key), value: stringifyValue(value) }),\n )\n }\n\n return []\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"get-form-body-rows.js","names":[],"sources":["../../../../../src/v2/blocks/request-block/helpers/get-form-body-rows.ts"],"sourcesContent":["import { isObject } from '@scalar/helpers/object/is-object'\nimport { objectEntries } from '@scalar/helpers/object/object-entries'\nimport { coerceLeafValueToSchemaType } from '@scalar/workspace-store/request-example'\nimport { resolve } from '@scalar/workspace-store/resolve'\nimport type { ExampleObject, SchemaObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { isObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/type-guards'\n\nimport type { TableRow } from '@/v2/blocks/request-block/components/RequestTableRow.vue'\n\nexport const stringifyValue = (value: unknown) => {\n if (value instanceof File) {\n return value.name\n }\n if (typeof value === 'object' && value !== null) {\n return JSON.stringify(value)\n }\n return String(value)\n}\n\nexport type LeafRow = {\n path: string[]\n schema: SchemaObject | undefined\n isRequired: boolean\n}\n\n/**\n * Walk a (possibly nested) object schema and collect one entry per leaf property.\n *\n * A \"leaf\" is any property whose resolved schema is not an `{ type: 'object' }` schema\n * with named `properties` — i.e. primitives, arrays, files, and `additionalProperties`\n * placeholders all stop the recursion. Object properties recurse, so the widget example\n * (`file` + `props.{name,description,created_at}`) yields four leaves.\n */\nexport const collectLeafProperties = (\n schema: SchemaObject,\n parentPath: string[] = [],\n parentRequired = true,\n): LeafRow[] => {\n // Nothing to walk if this is not an object schema or has no declared properties.\n if (!isObjectSchema(schema) || !schema.properties) {\n return []\n }\n\n // `required` is a list of property names on *this* schema, so build it once per level.\n const requiredSet = new Set(schema.required ?? [])\n const leaves: LeafRow[] = []\n\n for (const [key, rawChildSchema] of objectEntries(schema.properties)) {\n const childSchema = resolve.schema(rawChildSchema)\n const path = [...parentPath, String(key)]\n\n // A leaf is only required when *every* ancestor was also required — a non-required\n // parent makes the whole subtree optional regardless of inner `required` lists.\n const isRequired = parentRequired && requiredSet.has(String(key))\n\n // Nested objects with named properties recurse so each leaf gets its own row;\n // everything else (primitives, arrays, files, additionalProperties placeholders)\n // is treated as a leaf and stops the descent.\n if (childSchema && isObjectSchema(childSchema) && childSchema.properties) {\n leaves.push(...collectLeafProperties(childSchema, path, isRequired))\n } else {\n leaves.push({ path, schema: childSchema, isRequired })\n }\n }\n\n return leaves\n}\n\n/**\n * Walk a schema + example together and collect one row per leaf, including example-only\n * keys that the schema does not declare. Schema-declared properties come first in schema\n * order; extras present in the example follow in example order at each level.\n *\n * Unlike `collectLeafProperties` (which is schema-only and feeds the dotted-name index),\n * this walker drives what the user actually sees in the form: a row for every leaf the\n * example contains, even when the schema is partial or out of date.\n */\nexport const collectExampleRows = (\n example: unknown,\n schema: SchemaObject | undefined,\n parentPath: string[] = [],\n): { path: string[]; value: unknown }[] => {\n const rows: { path: string[]; value: unknown }[] = []\n const exampleEntries = isObject(example) ? objectEntries(example) : []\n const exampleByKey = new Map<string, unknown>(exampleEntries.map(([key, value]) => [String(key), value]))\n const declaredKeys = new Set<string>()\n const schemaProperties = schema && isObjectSchema(schema) ? (schema.properties ?? {}) : {}\n\n // Schema-declared properties first, in schema order. Nested object schemas recurse so\n // each leaf gets its own row; otherwise the schema-declared key is a leaf and uses the\n // matching example value (or `undefined` when the example does not provide one).\n for (const [key, rawChildSchema] of objectEntries(schemaProperties)) {\n const keyStr = String(key)\n declaredKeys.add(keyStr)\n const childSchema = resolve.schema(rawChildSchema)\n const path = [...parentPath, keyStr]\n const exampleSubvalue = exampleByKey.get(keyStr)\n\n if (childSchema && isObjectSchema(childSchema) && childSchema.properties) {\n rows.push(...collectExampleRows(exampleSubvalue, childSchema, path))\n } else {\n rows.push({ path, value: exampleSubvalue })\n }\n }\n\n // Example-only properties at this level (preserve example order). Their inner shape is\n // not descended into — without a schema saying \"this is a nested object\" we have no\n // signal to flatten, so an object value becomes a single JSON-stringified row, matching\n // the schema-less fallback below.\n for (const [key, value] of exampleEntries) {\n const keyStr = String(key)\n if (declaredKeys.has(keyStr)) {\n continue\n }\n rows.push({ path: [...parentPath, keyStr], value })\n }\n\n return rows\n}\n\n/** Restore array values after text editing without parsing ordinary string fields. */\nexport const getFormBodyValue = (row: TableRow): string | File | unknown[] | undefined => {\n const value = coerceLeafValueToSchemaType(row.value, row.isArray ? { type: 'array' } : row.schema)\n return Array.isArray(value) ? value : (row.value ?? undefined)\n}\n\n/** Build the table rows for the form data, optionally enriched with schema (e.g. enum) per property */\nexport const getFormBodyRows = (\n example: ExampleObject | undefined | null,\n contentType: string,\n formBodySchema?: SchemaObject,\n): TableRow[] => {\n // Forms use structured data even when the raw example also supplies wire text.\n const value = example?.dataValue !== undefined ? example.dataValue : example?.value\n // We only need the rows for formData\n if (!value || (contentType !== 'multipart/form-data' && contentType !== 'application/x-www-form-urlencoded')) {\n return []\n }\n\n // Get all the schema properties if the schema is an object schema\n const schemaWithProperties = formBodySchema && isObjectSchema(formBodySchema) ? formBodySchema : undefined\n // The request builder preserves composed fields because members can make them required.\n // Keep those fields checked so the initial form matches the body that will be sent.\n const isComposed = Boolean(schemaWithProperties?.allOf || schemaWithProperties?.oneOf || schemaWithProperties?.anyOf)\n const requiredSet = schemaWithProperties ? new Set(schemaWithProperties.required ?? []) : undefined\n\n // Pre-compute the leaf-by-dotted-name index up front so both the array and the\n // schema-driven branches can pull each row's leaf schema and required flag without\n // re-walking the schema. Without this, edited rows lose the per-leaf `Required`\n // badge because a top-level `properties[name]` lookup misses dotted names.\n const leafByDottedName = new Map<string, LeafRow>()\n if (schemaWithProperties) {\n for (const leaf of collectLeafProperties(schemaWithProperties)) {\n leafByDottedName.set(leaf.path.join('.'), leaf)\n }\n }\n\n const mapRow = ({\n name,\n value,\n isDisabled,\n }: {\n name: string\n value: string | File\n isDisabled?: boolean\n }): TableRow => {\n const row: TableRow = { name, value, isDisabled: isDisabled ?? false }\n if (!schemaWithProperties || !name) {\n return row\n }\n // Prefer the pre-resolved leaf (handles dotted names like `props.name`); fall back\n // to a top-level property lookup so user-added rows keep working.\n const leaf = leafByDottedName.get(name)\n const propSchema = leaf?.schema ?? resolve.schema(schemaWithProperties.properties?.[name])\n row.schema = propSchema\n row.description = propSchema?.description\n row.isRequired = leaf?.isRequired ?? requiredSet?.has(name) ?? false\n\n // Top-level optional properties default to disabled (unchecked), matching how optional\n // parameters behave. Scoped to top-level declared properties on purpose: the send side\n // (`build-request-body`) only drops top-level optional keys, so auto-disabling a nested\n // leaf (e.g. `props.name`) would leave it unchecked yet still transmitted. `Object.hasOwn`\n // (not `in`) keeps example-only keys named like `Object.prototype` members enabled. An\n // explicit `isDisabled` (from a stored form-row array) always wins.\n if (\n isDisabled === undefined &&\n !isComposed &&\n schemaWithProperties.properties &&\n Object.hasOwn(schemaWithProperties.properties, name)\n ) {\n row.isDisabled = !row.isRequired\n // Mark the row as disabled by default (no explicit `isDisabled` was stored) so typing a\n // value auto-enables it in `RequestTableRow`, matching how optional parameters behave. An\n // explicit `isDisabled` from a stored form-row array skips this branch and keeps winning.\n row.isDisabledByDefault = row.isDisabled\n }\n return row\n }\n\n const mapValue = (name: string, value: unknown, isDisabled?: boolean, isArray?: boolean): TableRow[] => {\n // Uploaded files must never pass through JSON.stringify, which discards their bytes.\n if (\n contentType === 'multipart/form-data' &&\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((item) => item instanceof File)\n ) {\n return value.map((file) => mapRow({ name, value: file, isDisabled }))\n }\n const row = mapRow({\n name,\n value: value instanceof File ? value : value == null ? '' : stringifyValue(value),\n isDisabled,\n })\n if (contentType === 'multipart/form-data' && (Array.isArray(value) || isArray)) {\n // Example-only arrays also need their type to survive a form-table round trip.\n row.isArray = true\n }\n return [row]\n }\n\n // We have form data stored as an array\n if (Array.isArray(value)) {\n return value.flatMap((exampleValue) => {\n if (isObject(exampleValue)) {\n const name = String(exampleValue.name)\n const rows =\n contentType === 'multipart/form-data'\n ? mapValue(name, exampleValue.value, Boolean(exampleValue.isDisabled), exampleValue.isArray === true)\n : [\n mapRow({\n name,\n value: exampleValue.value instanceof File ? exampleValue.value : String(exampleValue.value),\n isDisabled: Boolean(exampleValue.isDisabled),\n }),\n ]\n // An untouched optional row stays eligible for auto-enabling after another row is edited.\n return rows.map((row) =>\n row.isDisabled && exampleValue.isDisabledByDefault === true ? { ...row, isDisabledByDefault: true } : row,\n )\n }\n return { name: '', value: exampleValue, isDisabled: false }\n })\n }\n\n // Schema-driven path: when the form body schema describes nested objects, emit one row\n // per leaf so users can edit individual fields. The dotted name (`props.name`) is folded\n // back into a single JSON multipart part by `build-request-body.ts` before the request is\n // sent. Gated to `multipart/form-data` because urlencoded has no regrouping step on send,\n // so dotted leaves would otherwise reach the wire as separate `props.name`-style fields.\n //\n // We walk the example alongside the schema so example-only properties (top-level extras\n // or undeclared keys inside a nested object) are still visible and editable instead of\n // being silently dropped to whatever the schema happens to declare.\n if (contentType === 'multipart/form-data' && schemaWithProperties && typeof value === 'object') {\n return collectExampleRows(value, schemaWithProperties).flatMap(({ path, value }) => mapValue(path.join('.'), value))\n }\n\n // We got an object try to convert it to an array of rows\n if (typeof value === 'object' && value) {\n return objectEntries(value).flatMap(([key, value]) =>\n contentType === 'multipart/form-data'\n ? mapValue(String(key), value)\n : mapRow({ name: String(key), value: stringifyValue(value) }),\n )\n }\n\n return []\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponseBlock.vue.d.ts","sourceRoot":"","sources":["../../../../src/v2/blocks/response-block/ResponseBlock.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResponseBlock.vue.d.ts","sourceRoot":"","sources":["../../../../src/v2/blocks/response-block/ResponseBlock.vue"],"names":[],"mappings":"AA2TA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAK7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kDAAkD,CAAA;AAYxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,KAAK,WAAW,GAAG;IACf,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,4BAA4B;IAC5B,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACjC,qDAAqD;IACrD,cAAc,EAAE,cAAc,GAAG,IAAI,CAAA;IACrC,oBAAoB;IACpB,MAAM,EAAE,YAAY,CAAA;IACpB,yCAAyC;IACzC,sBAAsB,EAAE,MAAM,CAAA;IAC9B,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,0BAA0B;IAC1B,QAAQ,EAAE,iBAAiB,CAAA;CAC5B,CAAC;AA+BJ,QAAA,MAAM,gBAAgB,yCAA0C,CAAA;AAChE,KAAK,MAAM,GAAG,KAAK,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAubvD,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;oPAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../_virtual/_plugin-vue_exp
|
|
|
2
2
|
import ResponseBlock_vue_vue_type_script_setup_true_lang_default from "./ResponseBlock.vue.script.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/v2/blocks/response-block/ResponseBlock.vue
|
|
5
|
-
var ResponseBlock_default = /*#__PURE__*/ _plugin_vue_export_helper_default(ResponseBlock_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var ResponseBlock_default = /*#__PURE__*/ _plugin_vue_export_helper_default(ResponseBlock_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-2694e1a2"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ResponseBlock_default as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponseBlock.vue.js","names":[],"sources":["../../../../src/v2/blocks/response-block/ResponseBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarErrorBoundary } from '@scalar/components/error-boundary'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { RequestPayload } from '@scalar/workspace-store/request-example'\nimport { computed, ref, useId } from 'vue'\n\nimport SectionFilter from '@/components/SectionFilter.vue'\nimport ViewLayoutSection from '@/components/ViewLayout/ViewLayoutSection.vue'\nimport type { ResponseInstance } from '@/v2/blocks/operation-block/helpers/send-request'\nimport HeadersComponent from '@/v2/blocks/response-block/components/Headers.vue'\nimport ResponseBody from '@/v2/blocks/response-block/components/ResponseBody.vue'\nimport ResponseBodyStreaming from '@/v2/blocks/response-block/components/ResponseBodyStreaming.vue'\nimport ResponseBodyVirtual from '@/v2/blocks/response-block/components/ResponseBodyVirtual.vue'\nimport ResponseCookies from '@/v2/blocks/response-block/components/ResponseCookies.vue'\nimport ResponseEmpty from '@/v2/blocks/response-block/components/ResponseEmpty.vue'\nimport ResponseLoadingOverlay from '@/v2/blocks/response-block/components/ResponseLoadingOverlay.vue'\nimport ResponseMetaInformation from '@/v2/blocks/response-block/components/ResponseMetaInformation.vue'\nimport { textMediaTypes } from '@/v2/blocks/response-block/helpers/media-types'\nimport { parseSetCookie } from '@/v2/blocks/response-block/helpers/parse-set-cookie'\nimport { useLocalization } from '@/v2/features/localization'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nconst { layout, totalPerformedRequests, response, requestPayload } =\n defineProps<{\n /** Wait for the selected external request example before sending. */\n executionDisabled?: boolean\n /** Preprocessed response */\n response: ResponseInstance | null\n /** Original request as a [url, RequestInit] tuple */\n requestPayload: RequestPayload | null\n /** Client layout */\n layout: ClientLayout\n /** Total number of performed requests */\n totalPerformedRequests: number\n /** Application version */\n appVersion: string\n /** Registered app plugins */\n plugins: ClientPlugin[]\n /** Workspace event bus */\n eventBus: WorkspaceEventBus\n }>()\n\nconst { translate } = useLocalization()\n\n// Headers\nconst responseHeaders = computed(() => {\n const headers = response?.headers\n\n return headers\n ? Object.keys(headers).map((key) => ({\n name: key,\n value: headers[key] ?? '',\n }))\n : []\n})\n\nconst responseContentType = computed(\n () => response?.headers['content-type'] ?? response?.headers['Content-Type'],\n)\n\n// Cookies\nconst responseCookies = computed(\n () =>\n response?.cookieHeaderKeys\n .map((setCookieValue) => parseSetCookie(setCookieValue))\n .filter(isDefined) ?? [],\n)\n\nconst responseSections = ['Cookies', 'Headers', 'Body'] as const\ntype Filter = 'All' | (typeof responseSections)[number]\nconst activeFilter = ref<Filter>('All')\n\nconst filters = computed<Filter[]>(() => ['All', ...responseSections])\n\nconst filterIds = computed<Record<Filter, string>>(() => ({\n All: useId(),\n Cookies: useId(),\n Headers: useId(),\n Body: useId(),\n}))\n\n/** Threshold for virtualizing response bodies in bytes */\nconst VIRTUALIZATION_THRESHOLD = 200_000\nconst shouldVirtualize = computed(() => {\n if (!response || !('size' in response)) {\n return false\n }\n\n // Get content type from headers\n const contentType =\n response.headers?.['content-type'] || response.headers?.['Content-Type']\n\n // If no content type or response size is small, don't virtualize\n if (!contentType || (response.size ?? 0) <= VIRTUALIZATION_THRESHOLD) {\n return false\n }\n\n // Do not virtualize html\n if (contentType.includes('text/html')) {\n return false\n }\n\n // Check if content type matches any text-based type\n const isTextBased = textMediaTypes.some((type) => contentType.includes(type))\n\n return isTextBased && (response.size ?? 0) > VIRTUALIZATION_THRESHOLD\n})\n\nconst requestHeaders = computed(() => {\n const headers = requestPayload?.[1]?.headers\n if (!headers) {\n return []\n }\n\n // Normalise via the Headers constructor, and then spread\n return [...new Headers(headers)].map(([name, value]) => ({\n name,\n value,\n required: false,\n }))\n})\n\nconst isSectionVisible = (\n section: (typeof responseSections)[number] | 'All',\n) => {\n if (activeFilter.value === 'All' || activeFilter.value === section) {\n return true\n }\n return false\n}\n\ndefineExpose({\n responseHeaders,\n responseCookies,\n requestHeaders,\n shouldVirtualize,\n activeFilter,\n filters,\n})\n\nconst filterLabels = computed(() => ({\n All: translate('apiClient.sectionFilter.all'),\n Auth: translate('apiClient.sectionFilter.auth'),\n Variables: translate('apiClient.sectionFilter.variables'),\n Cookies: translate('apiClient.sectionFilter.cookies'),\n Headers: translate('apiClient.sectionFilter.headers'),\n Query: translate('apiClient.sectionFilter.query'),\n Body: translate('apiClient.sectionFilter.body'),\n}))\n</script>\n<template>\n <ViewLayoutSection\n :aria-label=\"translate('apiClient.responseBlock.response')\">\n <template #title>\n <div class=\"flex h-8 flex-1 items-center\">\n <div\n aria-live=\"polite\"\n class=\"flex items-center\"\n :class=\"{ 'animate-response-heading': response }\">\n <span class=\"response-heading pointer-events-none absolute\">{{\n translate('apiClient.responseBlock.response')\n }}</span>\n <ResponseMetaInformation\n v-if=\"response\"\n class=\"animate-response-children\"\n :eventBus=\"eventBus\"\n :response=\"response\" />\n </div>\n <SectionFilter\n v-model=\"activeFilter\"\n :filterIds=\"filterIds\"\n :filters=\"filters\"\n :labels=\"filterLabels\" />\n </div>\n </template>\n <div\n :id=\"filterIds.All\"\n class=\"custom-scroll response-section-content relative grid h-full justify-stretch\"\n :class=\"{\n 'content-start': response,\n }\"\n :role=\"activeFilter === 'All' && response ? 'tabpanel' : 'none'\">\n <template v-if=\"!response\">\n <ResponseEmpty\n :appVersion=\"appVersion\"\n :executionDisabled\n :layout=\"layout\"\n :totalPerformedRequests=\"totalPerformedRequests\"\n @addRequest=\"\n eventBus.emit('ui:open:command-palette', {\n action: 'create-request',\n payload: undefined,\n })\n \"\n @openCommandPalette=\"eventBus.emit('ui:open:command-palette')\"\n @sendRequest=\"eventBus.emit('operation:send:request:hotkey')\" />\n </template>\n <template v-else>\n <!-- Cookies section -->\n <ResponseCookies\n v-if=\"isSectionVisible('Cookies')\"\n :id=\"filterIds.Cookies\"\n class=\"response-section-content-cookies\"\n :cookies=\"responseCookies\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n <!-- Request headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"requestHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.requestHeaders') }}\n </template>\n </HeadersComponent>\n <!-- Response headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.responseHeaders') }}\n </template>\n </HeadersComponent>\n\n <!-- Inject response section plugin components -->\n <ScalarErrorBoundary\n v-for=\"(plugin, index) in plugins\"\n :key=\"index\">\n <component\n :is=\"plugin.components.response.component\"\n v-if=\"plugin?.components?.response\"\n v-show=\"activeFilter === 'All'\"\n v-bind=\"plugin.components.response.additionalProps\" />\n </ScalarErrorBoundary>\n\n <template v-if=\"activeFilter === 'All' || activeFilter === 'Body'\">\n <!-- Streaming response body -->\n <ResponseBodyStreaming\n v-if=\"'reader' in response\"\n :id=\"filterIds.Body\"\n class=\"response-section-content-body\"\n :contentType=\"responseContentType\"\n :reader=\"response.reader\" />\n\n <!-- Virtualized Text for massive responses -->\n <ResponseBodyVirtual\n v-else-if=\"shouldVirtualize && typeof response?.data === 'string'\"\n :id=\"filterIds.Body\"\n :content=\"response!.data\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n\n <!-- Regular response body -->\n <ResponseBody\n v-else\n :id=\"filterIds.Body\"\n :active=\"true\"\n class=\"response-section-content-body\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n layout=\"client\"\n :plugins=\"plugins\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\"\n :title=\"translate('apiClient.responseBlock.body')\" />\n </template>\n </template>\n <ResponseLoadingOverlay :eventBus=\"eventBus\" />\n </div>\n </ViewLayoutSection>\n</template>\n<style scoped>\n.animate-response-heading .response-heading {\n animation: push-response 0.2s ease-in-out forwards;\n opacity: 1;\n}\n@keyframes push-response {\n from {\n opacity: 1;\n transform: translateY(0);\n }\n to {\n opacity: 0;\n transform: translateY(-4px);\n }\n}\n.animate-response-heading .animate-response-children {\n animation: response-spans 0.2s ease-in-out forwards 0.05s;\n opacity: 0;\n}\n\n@keyframes response-spans {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"ResponseBlock.vue.js","names":[],"sources":["../../../../src/v2/blocks/response-block/ResponseBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarErrorBoundary } from '@scalar/components/error-boundary'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { RequestPayload } from '@scalar/workspace-store/request-example'\nimport { computed, ref, useId } from 'vue'\n\nimport SectionFilter from '@/components/SectionFilter.vue'\nimport ViewLayoutSection from '@/components/ViewLayout/ViewLayoutSection.vue'\nimport type { ResponseInstance } from '@/v2/blocks/operation-block/helpers/send-request'\nimport HeadersComponent from '@/v2/blocks/response-block/components/Headers.vue'\nimport ResponseBody from '@/v2/blocks/response-block/components/ResponseBody.vue'\nimport ResponseBodyStreaming from '@/v2/blocks/response-block/components/ResponseBodyStreaming.vue'\nimport ResponseBodyVirtual from '@/v2/blocks/response-block/components/ResponseBodyVirtual.vue'\nimport ResponseCookies from '@/v2/blocks/response-block/components/ResponseCookies.vue'\nimport ResponseEmpty from '@/v2/blocks/response-block/components/ResponseEmpty.vue'\nimport ResponseLoadingOverlay from '@/v2/blocks/response-block/components/ResponseLoadingOverlay.vue'\nimport ResponseMetaInformation from '@/v2/blocks/response-block/components/ResponseMetaInformation.vue'\nimport { textMediaTypes } from '@/v2/blocks/response-block/helpers/media-types'\nimport { parseSetCookie } from '@/v2/blocks/response-block/helpers/parse-set-cookie'\nimport { useLocalization } from '@/v2/features/localization'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nconst { layout, totalPerformedRequests, response, requestPayload } =\n defineProps<{\n /** Wait for the selected external request example before sending. */\n executionDisabled?: boolean\n /** Preprocessed response */\n response: ResponseInstance | null\n /** Original request as a [url, RequestInit] tuple */\n requestPayload: RequestPayload | null\n /** Client layout */\n layout: ClientLayout\n /** Total number of performed requests */\n totalPerformedRequests: number\n /** Application version */\n appVersion: string\n /** Registered app plugins */\n plugins: ClientPlugin[]\n /** Workspace event bus */\n eventBus: WorkspaceEventBus\n }>()\n\nconst { translate } = useLocalization()\n\n// Headers\nconst responseHeaders = computed(() => {\n const headers = response?.headers\n\n return headers\n ? Object.keys(headers).map((key) => ({\n name: key,\n value: headers[key] ?? '',\n }))\n : []\n})\n\nconst responseContentType = computed(\n () => response?.headers['content-type'] ?? response?.headers['Content-Type'],\n)\n\n// Cookies\nconst responseCookies = computed(\n () =>\n response?.cookieHeaderKeys\n .map((setCookieValue) => parseSetCookie(setCookieValue))\n .filter(isDefined) ?? [],\n)\n\nconst responseSections = ['Cookies', 'Headers', 'Body'] as const\ntype Filter = 'All' | (typeof responseSections)[number]\nconst activeFilter = ref<Filter>('All')\n\nconst filters = computed<Filter[]>(() => ['All', ...responseSections])\n\nconst filterIds = computed<Record<Filter, string>>(() => ({\n All: useId(),\n Cookies: useId(),\n Headers: useId(),\n Body: useId(),\n}))\n\n/** Threshold for virtualizing response bodies in bytes */\nconst VIRTUALIZATION_THRESHOLD = 200_000\nconst shouldVirtualize = computed(() => {\n if (!response || !('size' in response)) {\n return false\n }\n\n // Get content type from headers\n const contentType =\n response.headers?.['content-type'] || response.headers?.['Content-Type']\n\n // If no content type or response size is small, don't virtualize\n if (!contentType || (response.size ?? 0) <= VIRTUALIZATION_THRESHOLD) {\n return false\n }\n\n // Do not virtualize html\n if (contentType.includes('text/html')) {\n return false\n }\n\n // Check if content type matches any text-based type\n const isTextBased = textMediaTypes.some((type) => contentType.includes(type))\n\n return isTextBased && (response.size ?? 0) > VIRTUALIZATION_THRESHOLD\n})\n\nconst requestHeaders = computed(() => {\n const headers = requestPayload?.[1]?.headers\n if (!headers) {\n return []\n }\n\n // Normalise via the Headers constructor, and then spread\n return [...new Headers(headers)].map(([name, value]) => ({\n name,\n value,\n required: false,\n }))\n})\n\nconst isSectionVisible = (\n section: (typeof responseSections)[number] | 'All',\n) => {\n if (activeFilter.value === 'All' || activeFilter.value === section) {\n return true\n }\n return false\n}\n\ndefineExpose({\n responseHeaders,\n responseCookies,\n requestHeaders,\n shouldVirtualize,\n activeFilter,\n filters,\n})\n\nconst filterLabels = computed(() => ({\n All: translate('apiClient.sectionFilter.all'),\n Auth: translate('apiClient.sectionFilter.auth'),\n Variables: translate('apiClient.sectionFilter.variables'),\n Cookies: translate('apiClient.sectionFilter.cookies'),\n Headers: translate('apiClient.sectionFilter.headers'),\n Query: translate('apiClient.sectionFilter.query'),\n Body: translate('apiClient.sectionFilter.body'),\n}))\n</script>\n<template>\n <!-- Bound the stacked response section so long bodies keep their own scroll area. -->\n <ViewLayoutSection\n :aria-label=\"translate('apiClient.responseBlock.response')\"\n class=\"max-xl:max-h-full\">\n <template #title>\n <div class=\"flex h-8 flex-1 items-center\">\n <div\n aria-live=\"polite\"\n class=\"flex items-center\"\n :class=\"{ 'animate-response-heading': response }\">\n <span class=\"response-heading pointer-events-none absolute\">{{\n translate('apiClient.responseBlock.response')\n }}</span>\n <ResponseMetaInformation\n v-if=\"response\"\n class=\"animate-response-children\"\n :eventBus=\"eventBus\"\n :response=\"response\" />\n </div>\n <SectionFilter\n v-model=\"activeFilter\"\n :filterIds=\"filterIds\"\n :filters=\"filters\"\n :labels=\"filterLabels\" />\n </div>\n </template>\n <div\n :id=\"filterIds.All\"\n class=\"custom-scroll response-section-content relative grid h-full justify-stretch\"\n :class=\"{\n 'content-start': response,\n }\"\n :role=\"activeFilter === 'All' && response ? 'tabpanel' : 'none'\">\n <template v-if=\"!response\">\n <ResponseEmpty\n :appVersion=\"appVersion\"\n :executionDisabled\n :layout=\"layout\"\n :totalPerformedRequests=\"totalPerformedRequests\"\n @addRequest=\"\n eventBus.emit('ui:open:command-palette', {\n action: 'create-request',\n payload: undefined,\n })\n \"\n @openCommandPalette=\"eventBus.emit('ui:open:command-palette')\"\n @sendRequest=\"eventBus.emit('operation:send:request:hotkey')\" />\n </template>\n <template v-else>\n <!-- Cookies section -->\n <ResponseCookies\n v-if=\"isSectionVisible('Cookies')\"\n :id=\"filterIds.Cookies\"\n class=\"response-section-content-cookies\"\n :cookies=\"responseCookies\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n <!-- Request headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"requestHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.requestHeaders') }}\n </template>\n </HeadersComponent>\n <!-- Response headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.responseHeaders') }}\n </template>\n </HeadersComponent>\n\n <!-- Inject response section plugin components -->\n <ScalarErrorBoundary\n v-for=\"(plugin, index) in plugins\"\n :key=\"index\">\n <component\n :is=\"plugin.components.response.component\"\n v-if=\"plugin?.components?.response\"\n v-show=\"activeFilter === 'All'\"\n v-bind=\"plugin.components.response.additionalProps\" />\n </ScalarErrorBoundary>\n\n <template v-if=\"activeFilter === 'All' || activeFilter === 'Body'\">\n <!-- Streaming response body -->\n <ResponseBodyStreaming\n v-if=\"'reader' in response\"\n :id=\"filterIds.Body\"\n class=\"response-section-content-body\"\n :contentType=\"responseContentType\"\n :reader=\"response.reader\" />\n\n <!-- Virtualized Text for massive responses -->\n <ResponseBodyVirtual\n v-else-if=\"shouldVirtualize && typeof response?.data === 'string'\"\n :id=\"filterIds.Body\"\n :content=\"response!.data\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n\n <!-- Regular response body -->\n <ResponseBody\n v-else\n :id=\"filterIds.Body\"\n :active=\"true\"\n class=\"response-section-content-body\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n layout=\"client\"\n :plugins=\"plugins\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\"\n :title=\"translate('apiClient.responseBlock.body')\" />\n </template>\n </template>\n <ResponseLoadingOverlay :eventBus=\"eventBus\" />\n </div>\n </ViewLayoutSection>\n</template>\n<style scoped>\n.animate-response-heading .response-heading {\n animation: push-response 0.2s ease-in-out forwards;\n opacity: 1;\n}\n@keyframes push-response {\n from {\n opacity: 1;\n transform: translateY(0);\n }\n to {\n opacity: 0;\n transform: translateY(-4px);\n }\n}\n.animate-response-heading .animate-response-children {\n animation: response-spans 0.2s ease-in-out forwards 0.05s;\n opacity: 0;\n}\n\n@keyframes response-spans {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -94,7 +94,10 @@ var ResponseBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
94
94
|
Body: translate("apiClient.sectionFilter.body")
|
|
95
95
|
}));
|
|
96
96
|
return (_ctx, _cache) => {
|
|
97
|
-
return openBlock(), createBlock(ViewLayoutSection_default, {
|
|
97
|
+
return openBlock(), createBlock(ViewLayoutSection_default, {
|
|
98
|
+
"aria-label": unref(translate)("apiClient.responseBlock.response"),
|
|
99
|
+
class: "max-xl:max-h-full"
|
|
100
|
+
}, {
|
|
98
101
|
title: withCtx(() => [createElementVNode("div", _hoisted_1, [createElementVNode("div", {
|
|
99
102
|
"aria-live": "polite",
|
|
100
103
|
class: normalizeClass(["flex items-center", { "animate-response-heading": __props.response }])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponseBlock.vue.script.js","names":[],"sources":["../../../../src/v2/blocks/response-block/ResponseBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarErrorBoundary } from '@scalar/components/error-boundary'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { RequestPayload } from '@scalar/workspace-store/request-example'\nimport { computed, ref, useId } from 'vue'\n\nimport SectionFilter from '@/components/SectionFilter.vue'\nimport ViewLayoutSection from '@/components/ViewLayout/ViewLayoutSection.vue'\nimport type { ResponseInstance } from '@/v2/blocks/operation-block/helpers/send-request'\nimport HeadersComponent from '@/v2/blocks/response-block/components/Headers.vue'\nimport ResponseBody from '@/v2/blocks/response-block/components/ResponseBody.vue'\nimport ResponseBodyStreaming from '@/v2/blocks/response-block/components/ResponseBodyStreaming.vue'\nimport ResponseBodyVirtual from '@/v2/blocks/response-block/components/ResponseBodyVirtual.vue'\nimport ResponseCookies from '@/v2/blocks/response-block/components/ResponseCookies.vue'\nimport ResponseEmpty from '@/v2/blocks/response-block/components/ResponseEmpty.vue'\nimport ResponseLoadingOverlay from '@/v2/blocks/response-block/components/ResponseLoadingOverlay.vue'\nimport ResponseMetaInformation from '@/v2/blocks/response-block/components/ResponseMetaInformation.vue'\nimport { textMediaTypes } from '@/v2/blocks/response-block/helpers/media-types'\nimport { parseSetCookie } from '@/v2/blocks/response-block/helpers/parse-set-cookie'\nimport { useLocalization } from '@/v2/features/localization'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nconst { layout, totalPerformedRequests, response, requestPayload } =\n defineProps<{\n /** Wait for the selected external request example before sending. */\n executionDisabled?: boolean\n /** Preprocessed response */\n response: ResponseInstance | null\n /** Original request as a [url, RequestInit] tuple */\n requestPayload: RequestPayload | null\n /** Client layout */\n layout: ClientLayout\n /** Total number of performed requests */\n totalPerformedRequests: number\n /** Application version */\n appVersion: string\n /** Registered app plugins */\n plugins: ClientPlugin[]\n /** Workspace event bus */\n eventBus: WorkspaceEventBus\n }>()\n\nconst { translate } = useLocalization()\n\n// Headers\nconst responseHeaders = computed(() => {\n const headers = response?.headers\n\n return headers\n ? Object.keys(headers).map((key) => ({\n name: key,\n value: headers[key] ?? '',\n }))\n : []\n})\n\nconst responseContentType = computed(\n () => response?.headers['content-type'] ?? response?.headers['Content-Type'],\n)\n\n// Cookies\nconst responseCookies = computed(\n () =>\n response?.cookieHeaderKeys\n .map((setCookieValue) => parseSetCookie(setCookieValue))\n .filter(isDefined) ?? [],\n)\n\nconst responseSections = ['Cookies', 'Headers', 'Body'] as const\ntype Filter = 'All' | (typeof responseSections)[number]\nconst activeFilter = ref<Filter>('All')\n\nconst filters = computed<Filter[]>(() => ['All', ...responseSections])\n\nconst filterIds = computed<Record<Filter, string>>(() => ({\n All: useId(),\n Cookies: useId(),\n Headers: useId(),\n Body: useId(),\n}))\n\n/** Threshold for virtualizing response bodies in bytes */\nconst VIRTUALIZATION_THRESHOLD = 200_000\nconst shouldVirtualize = computed(() => {\n if (!response || !('size' in response)) {\n return false\n }\n\n // Get content type from headers\n const contentType =\n response.headers?.['content-type'] || response.headers?.['Content-Type']\n\n // If no content type or response size is small, don't virtualize\n if (!contentType || (response.size ?? 0) <= VIRTUALIZATION_THRESHOLD) {\n return false\n }\n\n // Do not virtualize html\n if (contentType.includes('text/html')) {\n return false\n }\n\n // Check if content type matches any text-based type\n const isTextBased = textMediaTypes.some((type) => contentType.includes(type))\n\n return isTextBased && (response.size ?? 0) > VIRTUALIZATION_THRESHOLD\n})\n\nconst requestHeaders = computed(() => {\n const headers = requestPayload?.[1]?.headers\n if (!headers) {\n return []\n }\n\n // Normalise via the Headers constructor, and then spread\n return [...new Headers(headers)].map(([name, value]) => ({\n name,\n value,\n required: false,\n }))\n})\n\nconst isSectionVisible = (\n section: (typeof responseSections)[number] | 'All',\n) => {\n if (activeFilter.value === 'All' || activeFilter.value === section) {\n return true\n }\n return false\n}\n\ndefineExpose({\n responseHeaders,\n responseCookies,\n requestHeaders,\n shouldVirtualize,\n activeFilter,\n filters,\n})\n\nconst filterLabels = computed(() => ({\n All: translate('apiClient.sectionFilter.all'),\n Auth: translate('apiClient.sectionFilter.auth'),\n Variables: translate('apiClient.sectionFilter.variables'),\n Cookies: translate('apiClient.sectionFilter.cookies'),\n Headers: translate('apiClient.sectionFilter.headers'),\n Query: translate('apiClient.sectionFilter.query'),\n Body: translate('apiClient.sectionFilter.body'),\n}))\n</script>\n<template>\n <ViewLayoutSection\n :aria-label=\"translate('apiClient.responseBlock.response')\">\n <template #title>\n <div class=\"flex h-8 flex-1 items-center\">\n <div\n aria-live=\"polite\"\n class=\"flex items-center\"\n :class=\"{ 'animate-response-heading': response }\">\n <span class=\"response-heading pointer-events-none absolute\">{{\n translate('apiClient.responseBlock.response')\n }}</span>\n <ResponseMetaInformation\n v-if=\"response\"\n class=\"animate-response-children\"\n :eventBus=\"eventBus\"\n :response=\"response\" />\n </div>\n <SectionFilter\n v-model=\"activeFilter\"\n :filterIds=\"filterIds\"\n :filters=\"filters\"\n :labels=\"filterLabels\" />\n </div>\n </template>\n <div\n :id=\"filterIds.All\"\n class=\"custom-scroll response-section-content relative grid h-full justify-stretch\"\n :class=\"{\n 'content-start': response,\n }\"\n :role=\"activeFilter === 'All' && response ? 'tabpanel' : 'none'\">\n <template v-if=\"!response\">\n <ResponseEmpty\n :appVersion=\"appVersion\"\n :executionDisabled\n :layout=\"layout\"\n :totalPerformedRequests=\"totalPerformedRequests\"\n @addRequest=\"\n eventBus.emit('ui:open:command-palette', {\n action: 'create-request',\n payload: undefined,\n })\n \"\n @openCommandPalette=\"eventBus.emit('ui:open:command-palette')\"\n @sendRequest=\"eventBus.emit('operation:send:request:hotkey')\" />\n </template>\n <template v-else>\n <!-- Cookies section -->\n <ResponseCookies\n v-if=\"isSectionVisible('Cookies')\"\n :id=\"filterIds.Cookies\"\n class=\"response-section-content-cookies\"\n :cookies=\"responseCookies\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n <!-- Request headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"requestHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.requestHeaders') }}\n </template>\n </HeadersComponent>\n <!-- Response headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.responseHeaders') }}\n </template>\n </HeadersComponent>\n\n <!-- Inject response section plugin components -->\n <ScalarErrorBoundary\n v-for=\"(plugin, index) in plugins\"\n :key=\"index\">\n <component\n :is=\"plugin.components.response.component\"\n v-if=\"plugin?.components?.response\"\n v-show=\"activeFilter === 'All'\"\n v-bind=\"plugin.components.response.additionalProps\" />\n </ScalarErrorBoundary>\n\n <template v-if=\"activeFilter === 'All' || activeFilter === 'Body'\">\n <!-- Streaming response body -->\n <ResponseBodyStreaming\n v-if=\"'reader' in response\"\n :id=\"filterIds.Body\"\n class=\"response-section-content-body\"\n :contentType=\"responseContentType\"\n :reader=\"response.reader\" />\n\n <!-- Virtualized Text for massive responses -->\n <ResponseBodyVirtual\n v-else-if=\"shouldVirtualize && typeof response?.data === 'string'\"\n :id=\"filterIds.Body\"\n :content=\"response!.data\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n\n <!-- Regular response body -->\n <ResponseBody\n v-else\n :id=\"filterIds.Body\"\n :active=\"true\"\n class=\"response-section-content-body\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n layout=\"client\"\n :plugins=\"plugins\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\"\n :title=\"translate('apiClient.responseBlock.body')\" />\n </template>\n </template>\n <ResponseLoadingOverlay :eventBus=\"eventBus\" />\n </div>\n </ViewLayoutSection>\n</template>\n<style scoped>\n.animate-response-heading .response-heading {\n animation: push-response 0.2s ease-in-out forwards;\n opacity: 1;\n}\n@keyframes push-response {\n from {\n opacity: 1;\n transform: translateY(0);\n }\n to {\n opacity: 0;\n transform: translateY(-4px);\n }\n}\n.animate-response-heading .animate-response-children {\n animation: response-spans 0.2s ease-in-out forwards 0.05s;\n opacity: 0;\n}\n\n@keyframes response-spans {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoFA,IAAM,2BAA2B;;;;;;;;;;;;;;EAxCjC,MAAM,EAAE,cAAc,gBAAgB;EAGtC,MAAM,kBAAkB,eAAe;GACrC,MAAM,UAAU,QAAA,UAAU;GAE1B,OAAO,UACH,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;IACjC,MAAM;IACN,OAAO,QAAQ,QAAQ;GACzB,EAAE,IACF,CAAC;EACP,CAAC;EAED,MAAM,sBAAsB,eACpB,QAAA,UAAU,QAAQ,mBAAmB,QAAA,UAAU,QAAQ,eAC/D;EAGA,MAAM,kBAAkB,eAEpB,QAAA,UAAU,iBACP,KAAK,mBAAmB,eAAe,cAAc,CAAC,CAAA,CACtD,OAAO,SAAS,KAAK,CAAC,CAC7B;EAEA,MAAM,mBAAmB;GAAC;GAAW;GAAW;EAAM;EAEtD,MAAM,eAAe,IAAY,KAAK;EAEtC,MAAM,UAAU,eAAyB,CAAC,OAAO,GAAG,gBAAgB,CAAC;EAErE,MAAM,YAAY,gBAAwC;GACxD,KAAK,MAAM;GACX,SAAS,MAAM;GACf,SAAS,MAAM;GACf,MAAM,MAAM;EACd,EAAE;;EAIF,MAAM,mBAAmB,eAAe;GACtC,IAAI,CAAC,QAAA,YAAY,EAAE,UAAU,QAAA,WAC3B,OAAO;GAIT,MAAM,cACJ,QAAA,SAAS,UAAU,mBAAmB,QAAA,SAAS,UAAU;GAG3D,IAAI,CAAC,gBAAgB,QAAA,SAAS,QAAQ,MAAM,0BAC1C,OAAO;GAIT,IAAI,YAAY,SAAS,WAAW,GAClC,OAAO;GAMT,OAFoB,eAAe,MAAM,SAAS,YAAY,SAAS,IAAI,CAEpE,MAAgB,QAAA,SAAS,QAAQ,KAAK;EAC/C,CAAC;EAED,MAAM,iBAAiB,eAAe;GACpC,MAAM,UAAU,QAAA,iBAAiB,EAAE,EAAE;GACrC,IAAI,CAAC,SACH,OAAO,CAAC;GAIV,OAAO,CAAC,GAAG,IAAI,QAAQ,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY;IACvD;IACA;IACA,UAAU;GACZ,EAAE;EACJ,CAAC;EAED,MAAM,oBACJ,YACG;GACH,IAAI,aAAa,UAAU,SAAS,aAAa,UAAU,SACzD,OAAO;GAET,OAAO;EACT;EAEA,SAAa;GACX;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EAED,MAAM,eAAe,gBAAgB;GACnC,KAAK,UAAU,6BAA6B;GAC5C,MAAM,UAAU,8BAA8B;GAC9C,WAAW,UAAU,mCAAmC;GACxD,SAAS,UAAU,iCAAiC;GACpD,SAAS,UAAU,iCAAiC;GACpD,OAAO,UAAU,+BAA+B;GAChD,MAAM,UAAU,8BAA8B;EAChD,EAAE;;uBAGA,YA0HoB,2BAAA,EAzHjB,cAAY,MAAA,SAAA,CAAS,CAAA,kCAAA,EAAA,GAAA;IACX,OAAK,cAoBR,CAnBN,mBAmBM,OAnBN,YAmBM,CAlBJ,mBAYM,OAAA;KAXJ,aAAU;KACV,OAAK,eAAA,CAAC,qBAAmB,EAAA,4BACa,QAAA,SAAQ,CAAA,CAAA;QAC9C,mBAES,QAFT,YAES,gBADP,MAAA,SAAA,CAAS,CAAA,kCAAA,CAAA,GAAA,CAAA,GAGH,QAAA,YAAA,UAAA,GADR,YAIyB,iCAAA;;KAFvB,OAAM;KACL,UAAU,QAAA;KACV,UAAU,QAAA;gFAEf,YAI2B,uBAAA;iBAHhB,aAAA;+EAAY,QAAA;KACpB,WAAW,UAAA;KACX,SAAS,QAAA;KACT,QAAQ,aAAA;;;;;;;2BAoGT,CAjGN,mBAiGM,OAAA;KAhGH,IAAI,UAAA,MAAU;KACf,OAAK,eAAA,CAAC,+EAA6E,EAAA,iBAChD,QAAA,SAAA,CAAA,CAAA;KAGlC,MAAM,aAAA,UAAY,SAAc,QAAA,WAAQ,aAAA;SACxB,QAAA,YAAA,UAAA,GACf,YAYkE,uBAAA;;KAX/D,YAAY,QAAA;KACZ,mBAAA,QAAA;KACA,QAAQ,QAAA;KACR,wBAAwB,QAAA;KACxB,cAAU,OAAA,OAAA,OAAA,MAAA,WAAe,QAAA,SAAS,KAAI,2BAAA;;eAA6F,KAAA;;KAMnI,sBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,QAAA,SAAS,KAAI,yBAAA;KACjC,eAAW,OAAA,OAAA,OAAA,MAAA,WAAE,QAAA,SAAS,KAAI,+BAAA;;;;;;wBAE/B,mBAyEW,UAAA,EAAA,KAAA,EAAA,GAAA;KAtED,iBAAgB,SAAA,KAAA,UAAA,GADxB,YAKyD,yBAAA;;MAHtD,IAAI,UAAA,MAAU;MACf,OAAM;MACL,SAAS,gBAAA;MACT,MAAM,aAAA,UAAY,QAAA,SAAA;;;;;;KAGb,iBAAgB,SAAA,KAAA,UAAA,GADxB,YASmB,iBAAA;;MAPhB,IAAI,UAAA,MAAU;MACf,OAAM;MACL,SAAS,eAAA;MACT,MAAM,aAAA,UAAY,QAAA,SAAA;;MACR,OAAK,cAC2C,CAAA,gBAAA,gBAAtD,MAAA,SAAA,CAAS,CAAA,wCAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;;;;;KAKR,iBAAgB,SAAA,KAAA,UAAA,GADxB,YASmB,iBAAA;;MAPhB,IAAI,UAAA,MAAU;MACf,OAAM;MACL,SAAS,gBAAA;MACT,MAAM,aAAA,UAAY,QAAA,SAAA;;MACR,OAAK,cAC4C,CAAA,gBAAA,gBAAvD,MAAA,SAAA,CAAS,CAAA,yCAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;;;;;uBAKhB,mBAQsB,UAAA,MAAA,WAPM,QAAA,UAAlB,QAAQ,UAAK;0BADvB,YAQsB,MAAA,mBAAA,GAAA,EANnB,KAAK,MAAK,GAAA;8BAK6C,CAFhD,QAAQ,YAAY,WAAA,gBAAA,UAAA,GAF5B,YAIwD,wBAHjD,OAAO,WAAW,SAAS,SAAS,GAD3C,WAIwD;;;UAA9C,OAAO,WAAW,SAAS,eAAe,GAAA,MAAA,EAAA,IAAA,CAAA,CAAA,OAD1C,aAAA,UAAY,KAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,IAAA,CAAA,CAAA;;;;KAIR,aAAA,UAAY,SAAc,aAAA,UAAY,UAAA,UAAA,GAAtD,mBA8BW,UAAA,EAAA,KAAA,EAAA,GAAA,CAAA,YA3BW,QAAA,YAAA,UAAA,GADpB,YAK8B,+BAAA;;MAH3B,IAAI,UAAA,MAAU;MACf,OAAM;MACL,aAAa,oBAAA;MACb,QAAQ,QAAA,SAAS;;;;;WAIP,iBAAA,SAAgB,OAAW,QAAA,UAAU,SAAI,YAAA,UAAA,GADtD,YAMyD,6BAAA;;MAJtD,IAAI,UAAA,MAAU;MACd,SAAS,QAAA,SAAU;MACnB,MAAM,QAAA,UAAU;MAChB,SAAS,gBAAA;MACT,MAAM,aAAA,UAAY,QAAA,SAAA;;;;;;;yBAGrB,YAUuD,sBAAA;;MARpD,IAAI,UAAA,MAAU;MACd,QAAQ;MACT,OAAM;MACL,MAAM,QAAA,UAAU;MAChB,SAAS,gBAAA;MACV,QAAO;MACN,SAAS,QAAA;MACT,MAAM,aAAA,UAAY,QAAA,SAAA;MAClB,OAAO,MAAA,SAAA,CAAS,CAAA,8BAAA;;;;;;;;;aAGvB,YAA+C,gCAAA,EAAtB,UAAU,QAAA,SAAQ,GAAA,MAAA,GAAA,CAAA,UAAA,CAAA,CAAA,GAAA,IAAA,UAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"ResponseBlock.vue.script.js","names":[],"sources":["../../../../src/v2/blocks/response-block/ResponseBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarErrorBoundary } from '@scalar/components/error-boundary'\nimport { isDefined } from '@scalar/helpers/array/is-defined'\nimport type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport type { WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { RequestPayload } from '@scalar/workspace-store/request-example'\nimport { computed, ref, useId } from 'vue'\n\nimport SectionFilter from '@/components/SectionFilter.vue'\nimport ViewLayoutSection from '@/components/ViewLayout/ViewLayoutSection.vue'\nimport type { ResponseInstance } from '@/v2/blocks/operation-block/helpers/send-request'\nimport HeadersComponent from '@/v2/blocks/response-block/components/Headers.vue'\nimport ResponseBody from '@/v2/blocks/response-block/components/ResponseBody.vue'\nimport ResponseBodyStreaming from '@/v2/blocks/response-block/components/ResponseBodyStreaming.vue'\nimport ResponseBodyVirtual from '@/v2/blocks/response-block/components/ResponseBodyVirtual.vue'\nimport ResponseCookies from '@/v2/blocks/response-block/components/ResponseCookies.vue'\nimport ResponseEmpty from '@/v2/blocks/response-block/components/ResponseEmpty.vue'\nimport ResponseLoadingOverlay from '@/v2/blocks/response-block/components/ResponseLoadingOverlay.vue'\nimport ResponseMetaInformation from '@/v2/blocks/response-block/components/ResponseMetaInformation.vue'\nimport { textMediaTypes } from '@/v2/blocks/response-block/helpers/media-types'\nimport { parseSetCookie } from '@/v2/blocks/response-block/helpers/parse-set-cookie'\nimport { useLocalization } from '@/v2/features/localization'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nconst { layout, totalPerformedRequests, response, requestPayload } =\n defineProps<{\n /** Wait for the selected external request example before sending. */\n executionDisabled?: boolean\n /** Preprocessed response */\n response: ResponseInstance | null\n /** Original request as a [url, RequestInit] tuple */\n requestPayload: RequestPayload | null\n /** Client layout */\n layout: ClientLayout\n /** Total number of performed requests */\n totalPerformedRequests: number\n /** Application version */\n appVersion: string\n /** Registered app plugins */\n plugins: ClientPlugin[]\n /** Workspace event bus */\n eventBus: WorkspaceEventBus\n }>()\n\nconst { translate } = useLocalization()\n\n// Headers\nconst responseHeaders = computed(() => {\n const headers = response?.headers\n\n return headers\n ? Object.keys(headers).map((key) => ({\n name: key,\n value: headers[key] ?? '',\n }))\n : []\n})\n\nconst responseContentType = computed(\n () => response?.headers['content-type'] ?? response?.headers['Content-Type'],\n)\n\n// Cookies\nconst responseCookies = computed(\n () =>\n response?.cookieHeaderKeys\n .map((setCookieValue) => parseSetCookie(setCookieValue))\n .filter(isDefined) ?? [],\n)\n\nconst responseSections = ['Cookies', 'Headers', 'Body'] as const\ntype Filter = 'All' | (typeof responseSections)[number]\nconst activeFilter = ref<Filter>('All')\n\nconst filters = computed<Filter[]>(() => ['All', ...responseSections])\n\nconst filterIds = computed<Record<Filter, string>>(() => ({\n All: useId(),\n Cookies: useId(),\n Headers: useId(),\n Body: useId(),\n}))\n\n/** Threshold for virtualizing response bodies in bytes */\nconst VIRTUALIZATION_THRESHOLD = 200_000\nconst shouldVirtualize = computed(() => {\n if (!response || !('size' in response)) {\n return false\n }\n\n // Get content type from headers\n const contentType =\n response.headers?.['content-type'] || response.headers?.['Content-Type']\n\n // If no content type or response size is small, don't virtualize\n if (!contentType || (response.size ?? 0) <= VIRTUALIZATION_THRESHOLD) {\n return false\n }\n\n // Do not virtualize html\n if (contentType.includes('text/html')) {\n return false\n }\n\n // Check if content type matches any text-based type\n const isTextBased = textMediaTypes.some((type) => contentType.includes(type))\n\n return isTextBased && (response.size ?? 0) > VIRTUALIZATION_THRESHOLD\n})\n\nconst requestHeaders = computed(() => {\n const headers = requestPayload?.[1]?.headers\n if (!headers) {\n return []\n }\n\n // Normalise via the Headers constructor, and then spread\n return [...new Headers(headers)].map(([name, value]) => ({\n name,\n value,\n required: false,\n }))\n})\n\nconst isSectionVisible = (\n section: (typeof responseSections)[number] | 'All',\n) => {\n if (activeFilter.value === 'All' || activeFilter.value === section) {\n return true\n }\n return false\n}\n\ndefineExpose({\n responseHeaders,\n responseCookies,\n requestHeaders,\n shouldVirtualize,\n activeFilter,\n filters,\n})\n\nconst filterLabels = computed(() => ({\n All: translate('apiClient.sectionFilter.all'),\n Auth: translate('apiClient.sectionFilter.auth'),\n Variables: translate('apiClient.sectionFilter.variables'),\n Cookies: translate('apiClient.sectionFilter.cookies'),\n Headers: translate('apiClient.sectionFilter.headers'),\n Query: translate('apiClient.sectionFilter.query'),\n Body: translate('apiClient.sectionFilter.body'),\n}))\n</script>\n<template>\n <!-- Bound the stacked response section so long bodies keep their own scroll area. -->\n <ViewLayoutSection\n :aria-label=\"translate('apiClient.responseBlock.response')\"\n class=\"max-xl:max-h-full\">\n <template #title>\n <div class=\"flex h-8 flex-1 items-center\">\n <div\n aria-live=\"polite\"\n class=\"flex items-center\"\n :class=\"{ 'animate-response-heading': response }\">\n <span class=\"response-heading pointer-events-none absolute\">{{\n translate('apiClient.responseBlock.response')\n }}</span>\n <ResponseMetaInformation\n v-if=\"response\"\n class=\"animate-response-children\"\n :eventBus=\"eventBus\"\n :response=\"response\" />\n </div>\n <SectionFilter\n v-model=\"activeFilter\"\n :filterIds=\"filterIds\"\n :filters=\"filters\"\n :labels=\"filterLabels\" />\n </div>\n </template>\n <div\n :id=\"filterIds.All\"\n class=\"custom-scroll response-section-content relative grid h-full justify-stretch\"\n :class=\"{\n 'content-start': response,\n }\"\n :role=\"activeFilter === 'All' && response ? 'tabpanel' : 'none'\">\n <template v-if=\"!response\">\n <ResponseEmpty\n :appVersion=\"appVersion\"\n :executionDisabled\n :layout=\"layout\"\n :totalPerformedRequests=\"totalPerformedRequests\"\n @addRequest=\"\n eventBus.emit('ui:open:command-palette', {\n action: 'create-request',\n payload: undefined,\n })\n \"\n @openCommandPalette=\"eventBus.emit('ui:open:command-palette')\"\n @sendRequest=\"eventBus.emit('operation:send:request:hotkey')\" />\n </template>\n <template v-else>\n <!-- Cookies section -->\n <ResponseCookies\n v-if=\"isSectionVisible('Cookies')\"\n :id=\"filterIds.Cookies\"\n class=\"response-section-content-cookies\"\n :cookies=\"responseCookies\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n <!-- Request headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"requestHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.requestHeaders') }}\n </template>\n </HeadersComponent>\n <!-- Response headers section -->\n <HeadersComponent\n v-if=\"isSectionVisible('Headers')\"\n :id=\"filterIds.Headers\"\n class=\"response-section-content-headers\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\">\n <template #title>\n {{ translate('apiClient.responseBlock.responseHeaders') }}\n </template>\n </HeadersComponent>\n\n <!-- Inject response section plugin components -->\n <ScalarErrorBoundary\n v-for=\"(plugin, index) in plugins\"\n :key=\"index\">\n <component\n :is=\"plugin.components.response.component\"\n v-if=\"plugin?.components?.response\"\n v-show=\"activeFilter === 'All'\"\n v-bind=\"plugin.components.response.additionalProps\" />\n </ScalarErrorBoundary>\n\n <template v-if=\"activeFilter === 'All' || activeFilter === 'Body'\">\n <!-- Streaming response body -->\n <ResponseBodyStreaming\n v-if=\"'reader' in response\"\n :id=\"filterIds.Body\"\n class=\"response-section-content-body\"\n :contentType=\"responseContentType\"\n :reader=\"response.reader\" />\n\n <!-- Virtualized Text for massive responses -->\n <ResponseBodyVirtual\n v-else-if=\"shouldVirtualize && typeof response?.data === 'string'\"\n :id=\"filterIds.Body\"\n :content=\"response!.data\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\" />\n\n <!-- Regular response body -->\n <ResponseBody\n v-else\n :id=\"filterIds.Body\"\n :active=\"true\"\n class=\"response-section-content-body\"\n :data=\"response?.data\"\n :headers=\"responseHeaders\"\n layout=\"client\"\n :plugins=\"plugins\"\n :role=\"activeFilter === 'All' ? 'none' : 'tabpanel'\"\n :title=\"translate('apiClient.responseBlock.body')\" />\n </template>\n </template>\n <ResponseLoadingOverlay :eventBus=\"eventBus\" />\n </div>\n </ViewLayoutSection>\n</template>\n<style scoped>\n.animate-response-heading .response-heading {\n animation: push-response 0.2s ease-in-out forwards;\n opacity: 1;\n}\n@keyframes push-response {\n from {\n opacity: 1;\n transform: translateY(0);\n }\n to {\n opacity: 0;\n transform: translateY(-4px);\n }\n}\n.animate-response-heading .animate-response-children {\n animation: response-spans 0.2s ease-in-out forwards 0.05s;\n opacity: 0;\n}\n\n@keyframes response-spans {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { HttpMethod as HttpMethodType } from '@scalar/helpers/http/http-methods';
|
|
2
1
|
import type { ServerMeta, WorkspaceEventBus } from '@scalar/workspace-store/events';
|
|
3
2
|
import type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments';
|
|
4
3
|
import type { ServerObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
@@ -17,7 +16,7 @@ export type AddressBarProps = {
|
|
|
17
16
|
/** Current request path */
|
|
18
17
|
path: string;
|
|
19
18
|
/** Current request method */
|
|
20
|
-
method:
|
|
19
|
+
method: string;
|
|
21
20
|
/** Whether the request target belongs to an OpenAPI webhook. */
|
|
22
21
|
isWebhook?: boolean;
|
|
23
22
|
/** Openapi document slug */
|
|
@@ -40,7 +39,7 @@ export type AddressBarProps = {
|
|
|
40
39
|
serverMeta: ServerMeta;
|
|
41
40
|
};
|
|
42
41
|
declare const __VLS_export: import("vue").DefineComponent<AddressBarProps, {
|
|
43
|
-
methodConflict: import("vue").Ref<
|
|
42
|
+
methodConflict: import("vue").Ref<string | null, string | null>;
|
|
44
43
|
pathConflict: import("vue").Ref<string | null, string | null>;
|
|
45
44
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
46
45
|
"select:history:item": (payload: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBar.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBar.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AddressBar.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBar.vue"],"names":[],"mappings":"AAuvBA,OAAO,KAAK,EAEV,UAAU,EACV,iBAAiB,EAClB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2EAA2E,CAAA;AACnH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAA;AAqBhG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAA0B,EAAE,KAAK,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAGzE;;;;GAIG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAKzC,MAAM,MAAM,eAAe,GAAG;IAC5B,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAA;IAClB,gCAAgC;IAChC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B,mDAAmD;IACnD,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,8BAA8B;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAA;IAClB,oBAAoB;IACpB,MAAM,EAAE,YAAY,CAAA;IACpB,gBAAgB;IAChB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,kBAAkB;IAClB,WAAW,EAAE,kBAAkB,CAAA;IAC/B,sCAAsC;IACtC,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AACD,QAAA,MAAM,YAAY;;;;;eAqB6B,MAAM;;;;;;eAAN,MAAM;;;;kFAuhCjD,CAAC"}
|
|
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_
|
|
|
2
2
|
import AddressBar_vue_vue_type_script_setup_true_lang_default from "./AddressBar.vue.script.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/v2/blocks/scalar-address-bar-block/components/AddressBar.vue
|
|
5
|
-
var AddressBar_default = /*#__PURE__*/ _plugin_vue_export_helper_default(AddressBar_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var AddressBar_default = /*#__PURE__*/ _plugin_vue_export_helper_default(AddressBar_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-0e96b5b8"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { AddressBar_default as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBar.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBar.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * AddressBar component\n * This component is used to display the address bar for the operation block\n * It is used to display the path, method, server, and history for the operation\n */\nexport default {\n name: 'AddressBar',\n}\nexport type AddressBarProps = {\n /** An external example must finish loading before sending. */\n executionDisabled?: boolean\n /** Current request path */\n path: string\n /** Current request method */\n method: HttpMethodType\n /** Whether the request target belongs to an OpenAPI webhook. */\n isWebhook?: boolean\n /** Openapi document slug */\n documentSlug: string\n /** Currently selected example key for the current operation */\n exampleKey: string\n /** Currently selected server */\n server: ServerObject | null\n /** Server list available for operation/document */\n servers: ServerObject[]\n /** List of request history */\n history: History[]\n /** Client layout */\n layout: ClientLayout\n /** Event bus */\n eventBus: WorkspaceEventBus\n /** Environment */\n environment: XScalarEnvironment\n /** Meta information for the server */\n serverMeta: ServerMeta\n}\n</script>\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { getSelector } from '@scalar/helpers/dom/get-selector'\nimport { REQUEST_METHODS } from '@scalar/helpers/http/http-info'\nimport type { HttpMethod as HttpMethodType } from '@scalar/helpers/http/http-methods'\nimport { extractServerFromPath } from '@scalar/helpers/url/extract-server-from-path'\nimport { ScalarIconCopy, ScalarIconWarningCircle } from '@scalar/icons'\nimport { EditorView } from '@scalar/use-codemirror'\nimport type {\n ApiReferenceEvents,\n ServerMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { ServerObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport { getOperationExampleKey } from '@/v2/blocks/operation-block/helpers/response-cache'\nimport { isPlaceholderPath } from '@/v2/blocks/scalar-address-bar-block/helpers/is-placeholder-path'\nimport { refocusBlurTarget } from '@/v2/blocks/scalar-address-bar-block/helpers/refocus-blur-target'\nimport { useLoadingAnimation } from '@/v2/blocks/scalar-address-bar-block/hooks/use-loading-animation'\nimport { usePathMasking } from '@/v2/blocks/scalar-address-bar-block/hooks/use-path-masking'\nimport { CodeInput } from '@/v2/components/code-input'\nimport { ServerDropdown } from '@/v2/components/server'\nimport { useLocalization } from '@/v2/features/localization'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nimport AddressBarHistory, { type History } from './AddressBarHistory.vue'\n\nconst {\n path,\n method,\n layout,\n eventBus,\n exampleKey,\n documentSlug,\n server,\n servers,\n environment,\n serverMeta,\n isWebhook = false,\n} = defineProps<AddressBarProps>()\n\nconst emit = defineEmits<{\n /** Execute the current operation example */\n (e: 'execute'): void\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the full destination URL used to deliver a webhook. */\n (e: 'update:webhook-url', url: string): void\n}>()\n\nconst { translate } = useLocalization()\n\n// ───────────────────────────────────────────────────────────────────\n// Template refs & reactive state\n// ───────────────────────────────────────────────────────────────────\n\nconst id = useId()\nconst sendButtonRef = useTemplateRef('sendButtonRef')\nconst mobileSendButtonRef = useTemplateRef('mobileSendButtonRef')\nconst addressBarRef = useTemplateRef('addressBarRef')\n\nconst { percentage, startLoading, stopLoading, isLoading } =\n useLoadingAnimation()\n\nconst pathConflict = ref<string | null>(null)\nconst methodConflict = ref<HttpMethodType | null>(null)\nconst tabbedOut = ref(false)\nconst isServerDropdownOpen = ref(false)\nconst isHistoryDropdownOpen = ref(false)\n\n// ───────────────────────────────────────────────────────────────────\n// Derived state\n// ───────────────────────────────────────────────────────────────────\n\n/** Keeps the cursor visible past the fade-right overlay while typing */\nconst addressBarScrollMargins = EditorView.scrollMargins.of(() => ({\n right: 24,\n}))\n\n/** Animated background transform for the loading indicator */\nconst style = computed(() => ({\n backgroundColor: `color-mix(in srgb, transparent 90%, ${REQUEST_METHODS[method].colorVar})`,\n transform: `translate3d(-${percentage.value}%,0,0)`,\n}))\n\n/** Whether there is a path or method conflict */\nconst hasConflict = computed(() => methodConflict.value || pathConflict.value)\n\n/**\n * Placeholder for the path field.\n *\n * Webhooks have no server to prefix the path, so the field doubles as the full\n * destination URL. Hint that a complete URL (including the host) is expected,\n * rather than a bare path.\n */\nconst pathPlaceholder = computed(() => {\n if (server) {\n return ''\n }\n return isWebhook\n ? translate('apiClient.addressBar.webhookUrlPlaceholder')\n : translate('apiClient.addressBar.urlPlaceholder')\n})\n\n/** Whether either dropdown (server or history) is open */\nconst isDropdownOpen = computed(\n () => isServerDropdownOpen.value || isHistoryDropdownOpen.value,\n)\n\n/** Uniquely identifies the currently selected operation + example */\nconst uniqueKey = computed(() =>\n getOperationExampleKey(method, path, exampleKey, documentSlug),\n)\n\n/** Clear conflict state when switching to a different operation */\nwatch(uniqueKey, () => {\n pathConflict.value = null\n methodConflict.value = null\n})\n\n// ───────────────────────────────────────────────────────────────────\n// Focus helpers\n// ───────────────────────────────────────────────────────────────────\n\nconst handleFocusSendButton = (): void => {\n const desktop = sendButtonRef.value?.$el\n const mobile = mobileSendButtonRef.value?.$el\n\n // Focus whichever send button is currently visible\n if (desktop && desktop.offsetParent !== null) {\n desktop.focus()\n } else {\n mobile?.focus()\n }\n}\n\nconst handleFocusAddressBar = (\n payload: ApiReferenceEvents['ui:focus:address-bar'],\n): void => {\n // On non-desktop layouts, if already focused we let the browser handle the\n // native behavior (e.g. selecting the browser's own address bar).\n if (addressBarRef.value?.isFocused && layout !== 'desktop') {\n return\n }\n\n addressBarRef.value?.focus('end')\n\n if (payload && 'clear' in payload && payload.clear) {\n addressBarRef.value?.setCodeMirrorContent('')\n }\n\n if (payload && 'event' in payload) {\n payload.event.preventDefault()\n }\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Path masking\n//\n// Drafts on `/` and auto-generated `/_scalar_temp...` paths are internal\n// placeholders — the address bar focuses and clears on mount and on\n// navigation so the user sees a blank prompt instead of the internal\n// path. The deferred mask only clears when CodeMirror still contains that\n// placeholder path, so a user edit that lands before the frame is preserved.\n// ───────────────────────────────────────────────────────────────────\n\nusePathMasking({\n isReady: () => addressBarRef.value?.codeMirror,\n operationKey: () => uniqueKey.value,\n shouldMask: () => isPlaceholderPath(path, documentSlug),\n // Defer to the next frame so focus() runs after click-handler side\n // effects that move focus (e.g. a dropdown refocusing its trigger),\n // which would otherwise blur our input and emit a spurious path\n // update against the now-empty value.\n onMask: () =>\n requestAnimationFrame(() => {\n const editorContent =\n addressBarRef.value?.codeMirror?.state.doc.toString()\n\n if (editorContent && editorContent !== path) {\n return\n }\n\n handleFocusAddressBar({ clear: true })\n }),\n})\n\n// ───────────────────────────────────────────────────────────────────\n// Server extraction\n// ───────────────────────────────────────────────────────────────────\n\n/** If the path contains a server URL, extract it and select or add that server */\nconst extractAndSelectServer = (targetPath: string): string => {\n const extracted = extractServerFromPath(targetPath)\n if (!extracted) {\n return targetPath\n }\n\n const [url, remainingPath] = extracted\n\n // Server is already selected — nothing to do\n if (url === server?.url) {\n return remainingPath\n }\n\n const matchingServer = servers.find((s) => s.url === url)\n if (matchingServer) {\n eventBus.emit('server:update:selected', { url, meta: serverMeta })\n } else {\n eventBus.emit('server:add:server', {\n url,\n select: true,\n meta: { type: 'operation', path, method },\n })\n }\n\n return remainingPath\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Path / method updates\n// ───────────────────────────────────────────────────────────────────\n\nconst normalizePath = (value: string): string =>\n value.startsWith('/') ? value : `/${value}`\n\n/** Emit a path/method update and reconcile conflicts + cursor state on the result */\nconst emitPathMethodUpdate = (\n targetMethod: HttpMethodType,\n targetPath: string,\n blurTargetSelector: string | null = null,\n): void => {\n if (isWebhook) {\n // A webhook has no OpenAPI server, so the field holds the full destination\n // URL. Keep exactly what the user entered — do not split it into an invisible\n // server plus a leftover path, and do not force a leading slash. That split\n // is what made a typed URL collapse to a stray \"/\" on blur.\n const webhookUrl = targetPath.trim()\n addressBarRef.value?.setCodeMirrorContent(webhookUrl)\n emit('update:webhook-url', webhookUrl)\n methodConflict.value = null\n pathConflict.value = null\n nextTick(() => refocusBlurTarget(blurTargetSelector))\n return\n }\n\n const extractedPath = extractAndSelectServer(targetPath)\n const normalizedPath = normalizePath(extractedPath)\n\n // Keep CodeMirror in sync so a conflict does not leave a stale value on screen\n addressBarRef.value?.setCodeMirrorContent(normalizedPath)\n\n eventBus.emit('operation:update:pathMethod', {\n meta: { method, path },\n blurTargetSelector,\n payload: { method: targetMethod, path: normalizedPath },\n callback: (status, returnedSelector) => {\n if (status === 'success' || status === 'no-change') {\n methodConflict.value = null\n pathConflict.value = null\n } else if (status === 'conflict') {\n if (targetMethod !== method) {\n methodConflict.value = targetMethod\n }\n if (normalizedPath !== path) {\n pathConflict.value = normalizedPath\n }\n\n return\n }\n\n // Edge case: pasting a full URL extracts the server but leaves the path\n // unchanged. CodeMirror still shows the full URL, so force it back to\n // just the path.\n const mirrorContent = addressBarRef.value?.codeMirrorRef?.textContent\n if (\n status === 'no-change' &&\n mirrorContent &&\n mirrorContent !== extractedPath\n ) {\n addressBarRef.value?.setCodeMirrorContent(extractedPath)\n }\n\n nextTick(() => refocusBlurTarget(returnedSelector))\n },\n })\n}\n\n/** Change the operation's method, preferring the conflicting path if present */\nconst handleMethodChange = (newMethod: HttpMethodType): void =>\n emitPathMethodUpdate(newMethod, pathConflict.value ?? path)\n\n/**\n * Save the path on blur and replay the click that caused the blur (so e.g. a\n * click on the Send button still fires after the async update resolves).\n *\n * Tab-outs do not replay — tabbing to a button should not trigger its click\n * (`tabbedOut` is set on `@keydown.tab`).\n *\n * Programmatic focus moves also do not replay. On first navigation into a\n * draft operation, `usePathMasking` focuses and clears the address bar while\n * `ScalarSidebarNestedItems` focuses the drilled-in Back button in\n * `nextTick`. That steals focus from CodeMirror, fires blur with a\n * `relatedTarget` (the Back button), and without a guard we would treat it\n * like a user click: capture the selector, then `refocusBlurTarget` would\n * synthesize `.click()` on Back and navigate away.\n *\n * `FocusEvent.sourceCapabilities` is null when no input device caused the\n * focus change (e.g. `element.focus()`). A real pointer click sets it on the\n * blur that precedes the click, so we still replay Send and other buttons.\n */\nconst handlePathBlur = (newPath: string, event: FocusEvent): void => {\n const relatedTarget =\n event.relatedTarget instanceof Element ? event.relatedTarget : null\n const blurTargetSelector =\n tabbedOut.value ||\n ('sourceCapabilities' in event && event.sourceCapabilities === null)\n ? null\n : getSelector(relatedTarget)\n tabbedOut.value = false\n\n emitPathMethodUpdate(\n methodConflict.value ?? method,\n newPath,\n blurTargetSelector,\n )\n}\n\n/** Save the path on Enter and trigger the Send button after the update resolves */\nconst handlePathSubmit = (\n newPath: string,\n event: KeyboardEvent | FocusEvent,\n): void => {\n // Prevent the global hotkey listener from also handling this Enter press\n event.stopPropagation()\n\n emitPathMethodUpdate(\n methodConflict.value ?? method,\n newPath,\n '[data-addressbar-action=\"send\"]',\n )\n}\n\n/** Unset the server when backspace is pressed on an empty path */\nconst handlePathBackspace = (event: KeyboardEvent): void => {\n if (event.target instanceof HTMLElement && event.target.innerText === '\\n') {\n // A webhook has no server to unset; its full URL lives in the field itself.\n if (isWebhook) {\n return\n }\n eventBus.emit('server:update:selected', { url: '', meta: serverMeta })\n }\n}\n\nconst handleServerSelect = (\n payload: ApiReferenceEvents['server:update:selected'],\n): void => {\n eventBus.emit('server:update:selected', payload)\n}\n\nconst handleServerVariable = (\n payload: ApiReferenceEvents['server:update:variables'],\n): void => {\n eventBus.emit('server:update:variables', payload)\n}\n\n/** Address bar copy is handled in OperationBlock (same URL as Send). */\nconst requestCopyUrl = (): void => {\n eventBus.emit('copy-url:address-bar')\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Navigation\n// ───────────────────────────────────────────────────────────────────\n\nconst navigateToServersPage = (): void => {\n if (serverMeta.type === 'operation') {\n eventBus.emit('ui:navigate', {\n page: 'operation',\n path: 'servers',\n operationPath: serverMeta.path,\n method: serverMeta.method,\n })\n return\n }\n\n eventBus.emit('ui:navigate', { page: 'document', path: 'servers' })\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Lifecycle\n// ───────────────────────────────────────────────────────────────────\n\nconst unsubscribes: Array<() => void> = []\n\nonMounted(() => {\n unsubscribes.push(\n eventBus.on('ui:focus:address-bar', handleFocusAddressBar),\n eventBus.on('ui:focus:send-button', handleFocusSendButton),\n eventBus.on('hooks:on:request:sent', startLoading),\n eventBus.on('hooks:on:request:complete', stopLoading),\n )\n})\n\nonBeforeUnmount(() => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n\n // Prevents the loading animation from continuing after unmount\n stopLoading()\n})\n\ndefineExpose({\n methodConflict,\n pathConflict,\n})\n</script>\n<template>\n <!--\n Address bar.\n\n The wide-container layout matches the original single-row bar:\n `[Method | URL | Copy | History | Send]`. When the surrounding\n `@container` drops below `@3xl` the bar collapses to\n `[URL | History]` and a second row appears beneath it with a\n duplicate `Method`, `Copy`, and `Send` so the URL gets the full\n container width while every action stays on the same line as the\n send button.\n -->\n <div\n class=\"order-last flex h-auto w-full max-w-3xl grow-3 flex-wrap items-stretch [--scalar-address-bar-height:32px] @3xl:order-0 @3xl:flex-nowrap\">\n <!-- Address Bar -->\n <div\n :id=\"id\"\n class=\"address-bar-bg-states text-xxs group relative flex h-(--scalar-address-bar-height) w-full flex-1 flex-row items-stretch rounded-lg p-0.75\"\n :class=\"{\n 'outline-c-danger outline': hasConflict,\n 'rounded-b-none': isDropdownOpen,\n }\">\n <div\n class=\"pointer-events-none absolute top-0 left-0 block h-full w-full overflow-hidden rounded-lg border\"\n :class=\"{\n 'rounded-b-none': isDropdownOpen,\n }\">\n <div\n class=\"absolute top-0 left-0 h-full w-full\"\n :style />\n </div>\n\n <!--\n Method, Copy, and Send are hidden in mobile mode (container\n narrower than `@3xl`) and the duplicate buttons in the trailing\n mobile actions row take over at that point.\n -->\n <div class=\"hidden @3xl:flex\">\n <HttpMethod\n :isEditable=\"layout !== 'modal' && !isWebhook\"\n isSquare\n :method=\"methodConflict ?? method\"\n teleport\n @change=\"handleMethodChange\" />\n </div>\n\n <div\n class=\"scroll-timeline-x scroll-timeline-x-hidden relative flex w-full bg-blend-normal\"\n dir=\"ltr\">\n <!-- Servers -->\n <ServerDropdown\n v-if=\"servers.length\"\n :layout=\"isWebhook ? 'modal' : layout\"\n :meta=\"serverMeta\"\n :server=\"server\"\n :servers=\"servers\"\n :target=\"id\"\n @update:open=\"(value) => (isServerDropdownOpen = value)\"\n @update:selectedServer=\"handleServerSelect\"\n @update:servers=\"navigateToServersPage\"\n @update:variable=\"handleServerVariable\" />\n\n <!-- Path + URL + env vars -->\n <CodeInput\n ref=\"addressBarRef\"\n alwaysEmitChange\n :aria-label=\"translate('apiClient.addressBar.path')\"\n class=\"ml-1 min-w-fit pl-px outline-none\"\n disableCloseBrackets\n :disabled=\"layout === 'modal' && !isWebhook\"\n disableEnter\n disableTabIndent\n :emitOnBlur=\"false\"\n :environment=\"environment\"\n :extensions=\"[addressBarScrollMargins]\"\n importCurl\n :layout=\"layout\"\n :modelValue=\"path\"\n :placeholder=\"pathPlaceholder\"\n server\n @blur=\"handlePathBlur\"\n @keydown.delete=\"handlePathBackspace\"\n @keydown.tab=\"tabbedOut = true\"\n @submit=\"handlePathSubmit\" />\n </div>\n\n <!-- Copy url button -->\n <ScalarButton\n class=\"hover:bg-b-3 mx-1 hidden @3xl:flex\"\n size=\"xs\"\n variant=\"ghost\"\n @click=\"requestCopyUrl\">\n <ScalarIconCopy />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBar.copyUrl')\n }}</span>\n </ScalarButton>\n\n <AddressBarHistory\n :history=\"history\"\n :target=\"id\"\n @select:history:item=\"(payload) => emit('select:history:item', payload)\"\n @update:open=\"(value) => (isHistoryDropdownOpen = value)\" />\n <!-- Error message -->\n <div\n v-if=\"hasConflict\"\n class=\"z-context absolute inset-x-0 top-[calc(100%+4px)] flex flex-col items-center rounded px-6\">\n <div\n class=\"text-c-danger bg-b-danger border-c-danger flex items-center gap-1 rounded border p-1\">\n <ScalarIconWarningCircle size=\"sm\" />\n <div class=\"min-w-0 flex-1\">\n {{\n translate('apiClient.addressBar.duplicateRequest', {\n method: (methodConflict ?? method).toUpperCase(),\n path: pathConflict ?? path,\n })\n }}\n </div>\n </div>\n </div>\n\n <ScalarButton\n ref=\"sendButtonRef\"\n class=\"relative hidden h-auto shrink-0 overflow-hidden py-1 pr-2.5 pl-2 font-bold @3xl:flex\"\n data-addressbar-action=\"send\"\n :disabled=\"isLoading || executionDisabled\"\n @click=\"emit('execute')\">\n <span\n aria-hidden=\"true\"\n class=\"inline-flex items-center gap-1\">\n <ScalarIcon\n class=\"relative shrink-0 fill-current\"\n icon=\"Play\"\n size=\"xs\" />\n <span class=\"text-xxs flex\">{{\n translate('apiClient.addressBar.send')\n }}</span>\n </span>\n <span class=\"sr-only\"\n >{{\n translate('apiClient.addressBar.sendRequest', {\n method,\n url: `${server?.url ?? ''}${path}`,\n })\n }}\n </span>\n </ScalarButton>\n </div>\n\n <!--\n Mobile actions row. Visible by default and hidden once the\n container reaches `@3xl`, where the duplicate Method / Copy /\n Send buttons move back into the bar itself.\n -->\n <div\n class=\"mt-2 flex h-(--scalar-address-bar-height) w-full items-stretch gap-1 @3xl:hidden\">\n <HttpMethod\n :isEditable=\"layout !== 'modal' && !isWebhook\"\n isSquare\n :method=\"methodConflict ?? method\"\n teleport\n @change=\"handleMethodChange\" />\n <ScalarButton\n class=\"hover:bg-b-3 ml-auto\"\n size=\"xs\"\n variant=\"ghost\"\n @click=\"requestCopyUrl\">\n <ScalarIconCopy />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBar.copyUrl')\n }}</span>\n </ScalarButton>\n <ScalarButton\n ref=\"mobileSendButtonRef\"\n class=\"relative h-auto shrink-0 overflow-hidden py-1 pr-2.5 pl-2 font-bold\"\n data-addressbar-action=\"send\"\n :disabled=\"isLoading || executionDisabled\"\n @click=\"emit('execute')\">\n <span\n aria-hidden=\"true\"\n class=\"inline-flex items-center gap-1\">\n <ScalarIcon\n class=\"relative shrink-0 fill-current\"\n icon=\"Play\"\n size=\"xs\" />\n <span class=\"text-xxs\">{{\n translate('apiClient.addressBar.send')\n }}</span>\n </span>\n <span class=\"sr-only\"\n >{{\n translate('apiClient.addressBar.sendRequest', {\n method,\n url: `${server?.url ?? ''}${path}`,\n })\n }}\n </span>\n </ScalarButton>\n </div>\n </div>\n</template>\n<style scoped>\n:deep(.cm-editor) {\n height: 100%;\n outline: none;\n width: 100%;\n}\n:deep(.cm-line) {\n padding: 0;\n}\n:deep(.cm-content) {\n padding: 0;\n display: flex;\n align-items: center;\n font-size: var(--scalar-small);\n}\n.scroll-timeline-x {\n -ms-overflow-style: none;\n /* Fade the URL at the viewport edges. Mask lives on the scrollport (this\n element) rather than on `.cm-scroller`, whose width follows the URL's\n full content and pushes the right edge off-screen when overflowing. */\n mask-image: linear-gradient(\n to right,\n transparent 0,\n black 6px,\n black calc(100% - 24px),\n transparent 100%\n );\n}\n.scroll-timeline-x-hidden {\n overflow-x: auto;\n}\n.scroll-timeline-x-hidden :deep(.cm-scroller) {\n scrollbar-width: none;\n -ms-overflow-style: none;\n padding-right: 20px;\n overflow: auto;\n}\n.scroll-timeline-x-hidden::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n}\n.scroll-timeline-x-hidden :deep(.cm-scroller::-webkit-scrollbar) {\n width: 0;\n height: 0;\n display: none;\n}\n.scroll-timeline-x-address {\n line-height: 27px;\n scrollbar-width: none; /* Firefox */\n}\n/* make clickable are to left of send button */\n.scroll-timeline-x-address:after {\n content: '';\n position: absolute;\n height: 100%;\n width: 24px;\n right: 0;\n cursor: text;\n}\n\n.address-bar-bg-states {\n --scalar-address-bar-bg: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n background: var(--scalar-address-bar-bg);\n}\n.address-bar-bg-states:has(.cm-focused) {\n --scalar-address-bar-bg: var(--scalar-background-1);\n border-color: var(--scalar-border-color);\n outline-width: 1px;\n outline-style: solid;\n}\n.address-bar-bg-states:has(.cm-focused) .fade-left,\n.address-bar-bg-states:has(.cm-focused) .fade-right {\n --scalar-address-bar-bg: var(--scalar-background-1);\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"AddressBar.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBar.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * AddressBar component\n * This component is used to display the address bar for the operation block\n * It is used to display the path, method, server, and history for the operation\n */\nexport default {\n name: 'AddressBar',\n}\nexport type AddressBarProps = {\n /** An external example must finish loading before sending. */\n executionDisabled?: boolean\n /** Current request path */\n path: string\n /** Current request method */\n method: string\n /** Whether the request target belongs to an OpenAPI webhook. */\n isWebhook?: boolean\n /** Openapi document slug */\n documentSlug: string\n /** Currently selected example key for the current operation */\n exampleKey: string\n /** Currently selected server */\n server: ServerObject | null\n /** Server list available for operation/document */\n servers: ServerObject[]\n /** List of request history */\n history: History[]\n /** Client layout */\n layout: ClientLayout\n /** Event bus */\n eventBus: WorkspaceEventBus\n /** Environment */\n environment: XScalarEnvironment\n /** Meta information for the server */\n serverMeta: ServerMeta\n}\n</script>\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { getSelector } from '@scalar/helpers/dom/get-selector'\nimport { getHttpMethodInfo } from '@scalar/helpers/http/http-info'\nimport { extractServerFromPath } from '@scalar/helpers/url/extract-server-from-path'\nimport { ScalarIconCopy, ScalarIconWarningCircle } from '@scalar/icons'\nimport { EditorView } from '@scalar/use-codemirror'\nimport type {\n ApiReferenceEvents,\n ServerMeta,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { ServerObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport { getOperationExampleKey } from '@/v2/blocks/operation-block/helpers/response-cache'\nimport { isPlaceholderPath } from '@/v2/blocks/scalar-address-bar-block/helpers/is-placeholder-path'\nimport { refocusBlurTarget } from '@/v2/blocks/scalar-address-bar-block/helpers/refocus-blur-target'\nimport { useLoadingAnimation } from '@/v2/blocks/scalar-address-bar-block/hooks/use-loading-animation'\nimport { usePathMasking } from '@/v2/blocks/scalar-address-bar-block/hooks/use-path-masking'\nimport { CodeInput } from '@/v2/components/code-input'\nimport { ServerDropdown } from '@/v2/components/server'\nimport { useLocalization } from '@/v2/features/localization'\nimport type { ClientLayout } from '@/v2/types/layout'\n\nimport AddressBarHistory, { type History } from './AddressBarHistory.vue'\n\nconst {\n path,\n method,\n layout,\n eventBus,\n exampleKey,\n documentSlug,\n server,\n servers,\n environment,\n serverMeta,\n isWebhook = false,\n} = defineProps<AddressBarProps>()\n\nconst emit = defineEmits<{\n /** Execute the current operation example */\n (e: 'execute'): void\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the full destination URL used to deliver a webhook. */\n (e: 'update:webhook-url', url: string): void\n}>()\n\nconst { translate } = useLocalization()\n\n// ───────────────────────────────────────────────────────────────────\n// Template refs & reactive state\n// ───────────────────────────────────────────────────────────────────\n\nconst id = useId()\nconst sendButtonRef = useTemplateRef('sendButtonRef')\nconst mobileSendButtonRef = useTemplateRef('mobileSendButtonRef')\nconst addressBarRef = useTemplateRef('addressBarRef')\n\nconst { percentage, startLoading, stopLoading, isLoading } =\n useLoadingAnimation()\n\nconst pathConflict = ref<string | null>(null)\nconst methodConflict = ref<string | null>(null)\nconst tabbedOut = ref(false)\nconst isServerDropdownOpen = ref(false)\nconst isHistoryDropdownOpen = ref(false)\n\n// ───────────────────────────────────────────────────────────────────\n// Derived state\n// ───────────────────────────────────────────────────────────────────\n\n/** Keeps the cursor visible past the fade-right overlay while typing */\nconst addressBarScrollMargins = EditorView.scrollMargins.of(() => ({\n right: 24,\n}))\n\n/** Animated background transform for the loading indicator */\nconst style = computed(() => ({\n backgroundColor: `color-mix(in srgb, transparent 90%, ${getHttpMethodInfo(method).colorVar})`,\n transform: `translate3d(-${percentage.value}%,0,0)`,\n}))\n\n/** Whether there is a path or method conflict */\nconst hasConflict = computed(() => methodConflict.value || pathConflict.value)\n\n/**\n * Placeholder for the path field.\n *\n * Webhooks have no server to prefix the path, so the field doubles as the full\n * destination URL. Hint that a complete URL (including the host) is expected,\n * rather than a bare path.\n */\nconst pathPlaceholder = computed(() => {\n if (server) {\n return ''\n }\n return isWebhook\n ? translate('apiClient.addressBar.webhookUrlPlaceholder')\n : translate('apiClient.addressBar.urlPlaceholder')\n})\n\n/** Whether either dropdown (server or history) is open */\nconst isDropdownOpen = computed(\n () => isServerDropdownOpen.value || isHistoryDropdownOpen.value,\n)\n\n/** Uniquely identifies the currently selected operation + example */\nconst uniqueKey = computed(() =>\n getOperationExampleKey(method, path, exampleKey, documentSlug),\n)\n\n/** Clear conflict state when switching to a different operation */\nwatch(uniqueKey, () => {\n pathConflict.value = null\n methodConflict.value = null\n})\n\n// ───────────────────────────────────────────────────────────────────\n// Focus helpers\n// ───────────────────────────────────────────────────────────────────\n\nconst handleFocusSendButton = (): void => {\n const desktop = sendButtonRef.value?.$el\n const mobile = mobileSendButtonRef.value?.$el\n\n // Focus whichever send button is currently visible\n if (desktop && desktop.offsetParent !== null) {\n desktop.focus()\n } else {\n mobile?.focus()\n }\n}\n\nconst handleFocusAddressBar = (\n payload: ApiReferenceEvents['ui:focus:address-bar'],\n): void => {\n // On non-desktop layouts, if already focused we let the browser handle the\n // native behavior (e.g. selecting the browser's own address bar).\n if (addressBarRef.value?.isFocused && layout !== 'desktop') {\n return\n }\n\n addressBarRef.value?.focus('end')\n\n if (payload && 'clear' in payload && payload.clear) {\n addressBarRef.value?.setCodeMirrorContent('')\n }\n\n if (payload && 'event' in payload) {\n payload.event.preventDefault()\n }\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Path masking\n//\n// Drafts on `/` and auto-generated `/_scalar_temp...` paths are internal\n// placeholders — the address bar focuses and clears on mount and on\n// navigation so the user sees a blank prompt instead of the internal\n// path. The deferred mask only clears when CodeMirror still contains that\n// placeholder path, so a user edit that lands before the frame is preserved.\n// ───────────────────────────────────────────────────────────────────\n\nusePathMasking({\n isReady: () => addressBarRef.value?.codeMirror,\n operationKey: () => uniqueKey.value,\n shouldMask: () => isPlaceholderPath(path, documentSlug),\n // Defer to the next frame so focus() runs after click-handler side\n // effects that move focus (e.g. a dropdown refocusing its trigger),\n // which would otherwise blur our input and emit a spurious path\n // update against the now-empty value.\n onMask: () =>\n requestAnimationFrame(() => {\n const editorContent =\n addressBarRef.value?.codeMirror?.state.doc.toString()\n\n if (editorContent && editorContent !== path) {\n return\n }\n\n handleFocusAddressBar({ clear: true })\n }),\n})\n\n// ───────────────────────────────────────────────────────────────────\n// Server extraction\n// ───────────────────────────────────────────────────────────────────\n\n/** If the path contains a server URL, extract it and select or add that server */\nconst extractAndSelectServer = (targetPath: string): string => {\n const extracted = extractServerFromPath(targetPath)\n if (!extracted) {\n return targetPath\n }\n\n const [url, remainingPath] = extracted\n\n // Server is already selected — nothing to do\n if (url === server?.url) {\n return remainingPath\n }\n\n const matchingServer = servers.find((s) => s.url === url)\n if (matchingServer) {\n eventBus.emit('server:update:selected', { url, meta: serverMeta })\n } else {\n eventBus.emit('server:add:server', {\n url,\n select: true,\n meta: { type: 'operation', path, method },\n })\n }\n\n return remainingPath\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Path / method updates\n// ───────────────────────────────────────────────────────────────────\n\nconst normalizePath = (value: string): string =>\n value.startsWith('/') ? value : `/${value}`\n\n/** Emit a path/method update and reconcile conflicts + cursor state on the result */\nconst emitPathMethodUpdate = (\n targetMethod: string,\n targetPath: string,\n blurTargetSelector: string | null = null,\n): void => {\n if (isWebhook) {\n // A webhook has no OpenAPI server, so the field holds the full destination\n // URL. Keep exactly what the user entered — do not split it into an invisible\n // server plus a leftover path, and do not force a leading slash. That split\n // is what made a typed URL collapse to a stray \"/\" on blur.\n const webhookUrl = targetPath.trim()\n addressBarRef.value?.setCodeMirrorContent(webhookUrl)\n emit('update:webhook-url', webhookUrl)\n methodConflict.value = null\n pathConflict.value = null\n nextTick(() => refocusBlurTarget(blurTargetSelector))\n return\n }\n\n const extractedPath = extractAndSelectServer(targetPath)\n const normalizedPath = normalizePath(extractedPath)\n\n // Keep CodeMirror in sync so a conflict does not leave a stale value on screen\n addressBarRef.value?.setCodeMirrorContent(normalizedPath)\n\n eventBus.emit('operation:update:pathMethod', {\n meta: { method, path },\n blurTargetSelector,\n payload: { method: targetMethod, path: normalizedPath },\n callback: (status, returnedSelector) => {\n if (status === 'success' || status === 'no-change') {\n methodConflict.value = null\n pathConflict.value = null\n } else if (status === 'conflict') {\n if (targetMethod !== method) {\n methodConflict.value = targetMethod\n }\n if (normalizedPath !== path) {\n pathConflict.value = normalizedPath\n }\n\n return\n }\n\n // Edge case: pasting a full URL extracts the server but leaves the path\n // unchanged. CodeMirror still shows the full URL, so force it back to\n // just the path.\n const mirrorContent = addressBarRef.value?.codeMirrorRef?.textContent\n if (\n status === 'no-change' &&\n mirrorContent &&\n mirrorContent !== extractedPath\n ) {\n addressBarRef.value?.setCodeMirrorContent(extractedPath)\n }\n\n nextTick(() => refocusBlurTarget(returnedSelector))\n },\n })\n}\n\n/** Change the operation's method, preferring the conflicting path if present */\nconst handleMethodChange = (newMethod: string): void =>\n emitPathMethodUpdate(newMethod, pathConflict.value ?? path)\n\n/**\n * Save the path on blur and replay the click that caused the blur (so e.g. a\n * click on the Send button still fires after the async update resolves).\n *\n * Tab-outs do not replay — tabbing to a button should not trigger its click\n * (`tabbedOut` is set on `@keydown.tab`).\n *\n * Programmatic focus moves also do not replay. On first navigation into a\n * draft operation, `usePathMasking` focuses and clears the address bar while\n * `ScalarSidebarNestedItems` focuses the drilled-in Back button in\n * `nextTick`. That steals focus from CodeMirror, fires blur with a\n * `relatedTarget` (the Back button), and without a guard we would treat it\n * like a user click: capture the selector, then `refocusBlurTarget` would\n * synthesize `.click()` on Back and navigate away.\n *\n * `FocusEvent.sourceCapabilities` is null when no input device caused the\n * focus change (e.g. `element.focus()`). A real pointer click sets it on the\n * blur that precedes the click, so we still replay Send and other buttons.\n */\nconst handlePathBlur = (newPath: string, event: FocusEvent): void => {\n const relatedTarget =\n event.relatedTarget instanceof Element ? event.relatedTarget : null\n const blurTargetSelector =\n tabbedOut.value ||\n ('sourceCapabilities' in event && event.sourceCapabilities === null)\n ? null\n : getSelector(relatedTarget)\n tabbedOut.value = false\n\n emitPathMethodUpdate(\n methodConflict.value ?? method,\n newPath,\n blurTargetSelector,\n )\n}\n\n/** Save the path on Enter and trigger the Send button after the update resolves */\nconst handlePathSubmit = (\n newPath: string,\n event: KeyboardEvent | FocusEvent,\n): void => {\n // Prevent the global hotkey listener from also handling this Enter press\n event.stopPropagation()\n\n emitPathMethodUpdate(\n methodConflict.value ?? method,\n newPath,\n '[data-addressbar-action=\"send\"]',\n )\n}\n\n/** Unset the server when backspace is pressed on an empty path */\nconst handlePathBackspace = (event: KeyboardEvent): void => {\n if (event.target instanceof HTMLElement && event.target.innerText === '\\n') {\n // A webhook has no server to unset; its full URL lives in the field itself.\n if (isWebhook) {\n return\n }\n eventBus.emit('server:update:selected', { url: '', meta: serverMeta })\n }\n}\n\nconst handleServerSelect = (\n payload: ApiReferenceEvents['server:update:selected'],\n): void => {\n eventBus.emit('server:update:selected', payload)\n}\n\nconst handleServerVariable = (\n payload: ApiReferenceEvents['server:update:variables'],\n): void => {\n eventBus.emit('server:update:variables', payload)\n}\n\n/** Address bar copy is handled in OperationBlock (same URL as Send). */\nconst requestCopyUrl = (): void => {\n eventBus.emit('copy-url:address-bar')\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Navigation\n// ───────────────────────────────────────────────────────────────────\n\nconst navigateToServersPage = (): void => {\n if (serverMeta.type === 'operation') {\n eventBus.emit('ui:navigate', {\n page: 'operation',\n path: 'servers',\n operationPath: serverMeta.path,\n method: serverMeta.method,\n })\n return\n }\n\n eventBus.emit('ui:navigate', { page: 'document', path: 'servers' })\n}\n\n// ───────────────────────────────────────────────────────────────────\n// Lifecycle\n// ───────────────────────────────────────────────────────────────────\n\nconst unsubscribes: Array<() => void> = []\n\nonMounted(() => {\n unsubscribes.push(\n eventBus.on('ui:focus:address-bar', handleFocusAddressBar),\n eventBus.on('ui:focus:send-button', handleFocusSendButton),\n eventBus.on('hooks:on:request:sent', startLoading),\n eventBus.on('hooks:on:request:complete', stopLoading),\n )\n})\n\nonBeforeUnmount(() => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n\n // Prevents the loading animation from continuing after unmount\n stopLoading()\n})\n\ndefineExpose({\n methodConflict,\n pathConflict,\n})\n</script>\n<template>\n <!--\n Address bar.\n\n The wide-container layout matches the original single-row bar:\n `[Method | URL | Copy | History | Send]`. When the surrounding\n `@container` drops below `@3xl` the bar collapses to\n `[URL | History]` and a second row appears beneath it with a\n duplicate `Method`, `Copy`, and `Send` so the URL gets the full\n container width while every action stays on the same line as the\n send button.\n -->\n <div\n class=\"order-last flex h-auto w-full max-w-3xl grow-3 flex-wrap items-stretch [--scalar-address-bar-height:32px] @3xl:order-0 @3xl:flex-nowrap\">\n <!-- Address Bar -->\n <div\n :id=\"id\"\n class=\"address-bar-bg-states text-xxs group relative flex h-(--scalar-address-bar-height) w-full flex-1 flex-row items-stretch rounded-lg p-0.75\"\n :class=\"{\n 'outline-c-danger outline': hasConflict,\n 'rounded-b-none': isDropdownOpen,\n }\">\n <div\n class=\"pointer-events-none absolute top-0 left-0 block h-full w-full overflow-hidden rounded-lg border\"\n :class=\"{\n 'rounded-b-none': isDropdownOpen,\n }\">\n <div\n class=\"absolute top-0 left-0 h-full w-full\"\n :style />\n </div>\n\n <!--\n Method, Copy, and Send are hidden in mobile mode (container\n narrower than `@3xl`) and the duplicate buttons in the trailing\n mobile actions row take over at that point.\n -->\n <div class=\"hidden @3xl:flex\">\n <HttpMethod\n :isEditable=\"layout !== 'modal' && !isWebhook\"\n isSquare\n :method=\"methodConflict ?? method\"\n teleport\n @change=\"handleMethodChange\" />\n </div>\n\n <div\n class=\"scroll-timeline-x scroll-timeline-x-hidden relative flex w-full bg-blend-normal\"\n dir=\"ltr\">\n <!-- Servers -->\n <ServerDropdown\n v-if=\"servers.length\"\n :layout=\"isWebhook ? 'modal' : layout\"\n :meta=\"serverMeta\"\n :server=\"server\"\n :servers=\"servers\"\n :target=\"id\"\n @update:open=\"(value) => (isServerDropdownOpen = value)\"\n @update:selectedServer=\"handleServerSelect\"\n @update:servers=\"navigateToServersPage\"\n @update:variable=\"handleServerVariable\" />\n\n <!-- Path + URL + env vars -->\n <CodeInput\n ref=\"addressBarRef\"\n alwaysEmitChange\n :aria-label=\"translate('apiClient.addressBar.path')\"\n class=\"ml-1 min-w-fit pl-px outline-none\"\n disableCloseBrackets\n :disabled=\"layout === 'modal' && !isWebhook\"\n disableEnter\n disableTabIndent\n :emitOnBlur=\"false\"\n :environment=\"environment\"\n :extensions=\"[addressBarScrollMargins]\"\n importCurl\n :layout=\"layout\"\n :modelValue=\"path\"\n :placeholder=\"pathPlaceholder\"\n server\n @blur=\"handlePathBlur\"\n @keydown.delete=\"handlePathBackspace\"\n @keydown.tab=\"tabbedOut = true\"\n @submit=\"handlePathSubmit\" />\n </div>\n\n <!-- Copy url button -->\n <ScalarButton\n class=\"hover:bg-b-3 mx-1 hidden @3xl:flex\"\n size=\"xs\"\n variant=\"ghost\"\n @click=\"requestCopyUrl\">\n <ScalarIconCopy />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBar.copyUrl')\n }}</span>\n </ScalarButton>\n\n <AddressBarHistory\n :history=\"history\"\n :target=\"id\"\n @select:history:item=\"(payload) => emit('select:history:item', payload)\"\n @update:open=\"(value) => (isHistoryDropdownOpen = value)\" />\n <!-- Error message -->\n <div\n v-if=\"hasConflict\"\n class=\"z-context absolute inset-x-0 top-[calc(100%+4px)] flex flex-col items-center rounded px-6\">\n <div\n class=\"text-c-danger bg-b-danger border-c-danger flex items-center gap-1 rounded border p-1\">\n <ScalarIconWarningCircle size=\"sm\" />\n <div class=\"min-w-0 flex-1\">\n {{\n translate('apiClient.addressBar.duplicateRequest', {\n method: (methodConflict ?? method).toUpperCase(),\n path: pathConflict ?? path,\n })\n }}\n </div>\n </div>\n </div>\n\n <ScalarButton\n ref=\"sendButtonRef\"\n class=\"relative hidden h-auto shrink-0 overflow-hidden py-1 pr-2.5 pl-2 font-bold @3xl:flex\"\n data-addressbar-action=\"send\"\n :disabled=\"isLoading || executionDisabled\"\n @click=\"emit('execute')\">\n <span\n aria-hidden=\"true\"\n class=\"inline-flex items-center gap-1\">\n <ScalarIcon\n class=\"relative shrink-0 fill-current\"\n icon=\"Play\"\n size=\"xs\" />\n <span class=\"text-xxs flex\">{{\n translate('apiClient.addressBar.send')\n }}</span>\n </span>\n <span class=\"sr-only\"\n >{{\n translate('apiClient.addressBar.sendRequest', {\n method,\n url: `${server?.url ?? ''}${path}`,\n })\n }}\n </span>\n </ScalarButton>\n </div>\n\n <!--\n Mobile actions row. Visible by default and hidden once the\n container reaches `@3xl`, where the duplicate Method / Copy /\n Send buttons move back into the bar itself.\n -->\n <div\n class=\"mt-2 flex h-(--scalar-address-bar-height) w-full items-stretch gap-1 @3xl:hidden\">\n <HttpMethod\n :isEditable=\"layout !== 'modal' && !isWebhook\"\n isSquare\n :method=\"methodConflict ?? method\"\n teleport\n @change=\"handleMethodChange\" />\n <ScalarButton\n class=\"hover:bg-b-3 ml-auto\"\n size=\"xs\"\n variant=\"ghost\"\n @click=\"requestCopyUrl\">\n <ScalarIconCopy />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBar.copyUrl')\n }}</span>\n </ScalarButton>\n <ScalarButton\n ref=\"mobileSendButtonRef\"\n class=\"relative h-auto shrink-0 overflow-hidden py-1 pr-2.5 pl-2 font-bold\"\n data-addressbar-action=\"send\"\n :disabled=\"isLoading || executionDisabled\"\n @click=\"emit('execute')\">\n <span\n aria-hidden=\"true\"\n class=\"inline-flex items-center gap-1\">\n <ScalarIcon\n class=\"relative shrink-0 fill-current\"\n icon=\"Play\"\n size=\"xs\" />\n <span class=\"text-xxs\">{{\n translate('apiClient.addressBar.send')\n }}</span>\n </span>\n <span class=\"sr-only\"\n >{{\n translate('apiClient.addressBar.sendRequest', {\n method,\n url: `${server?.url ?? ''}${path}`,\n })\n }}\n </span>\n </ScalarButton>\n </div>\n </div>\n</template>\n<style scoped>\n:deep(.cm-editor) {\n height: 100%;\n outline: none;\n width: 100%;\n}\n:deep(.cm-line) {\n padding: 0;\n}\n:deep(.cm-content) {\n padding: 0;\n display: flex;\n align-items: center;\n font-size: var(--scalar-small);\n}\n.scroll-timeline-x {\n -ms-overflow-style: none;\n /* Fade the URL at the viewport edges. Mask lives on the scrollport (this\n element) rather than on `.cm-scroller`, whose width follows the URL's\n full content and pushes the right edge off-screen when overflowing. */\n mask-image: linear-gradient(\n to right,\n transparent 0,\n black 6px,\n black calc(100% - 24px),\n transparent 100%\n );\n}\n.scroll-timeline-x-hidden {\n overflow-x: auto;\n}\n.scroll-timeline-x-hidden :deep(.cm-scroller) {\n scrollbar-width: none;\n -ms-overflow-style: none;\n padding-right: 20px;\n overflow: auto;\n}\n.scroll-timeline-x-hidden::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n}\n.scroll-timeline-x-hidden :deep(.cm-scroller::-webkit-scrollbar) {\n width: 0;\n height: 0;\n display: none;\n}\n.scroll-timeline-x-address {\n line-height: 27px;\n scrollbar-width: none; /* Firefox */\n}\n/* make clickable are to left of send button */\n.scroll-timeline-x-address:after {\n content: '';\n position: absolute;\n height: 100%;\n width: 24px;\n right: 0;\n cursor: text;\n}\n\n.address-bar-bg-states {\n --scalar-address-bar-bg: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n background: var(--scalar-address-bar-bg);\n}\n.address-bar-bg-states:has(.cm-focused) {\n --scalar-address-bar-bg: var(--scalar-background-1);\n border-color: var(--scalar-border-color);\n outline-width: 1px;\n outline-style: solid;\n}\n.address-bar-bg-states:has(.cm-focused) .fade-left,\n.address-bar-bg-states:has(.cm-focused) .fade-right {\n --scalar-address-bar-bg: var(--scalar-background-1);\n}\n</style>\n"],"mappings":""}
|