@scalar/api-client 3.21.4 → 3.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/plugins/posthog/sanitize-event-payload.d.ts.map +1 -1
- package/dist/plugins/posthog/sanitize-event-payload.js +1 -0
- package/dist/plugins/posthog/sanitize-event-payload.js.map +1 -1
- package/dist/style.css +1204 -51
- package/dist/styles/tailwind.config.css +1 -0
- 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 +1 -1
- package/dist/v2/blocks/operation-block/OperationBlock.vue.script.js.map +1 -1
- 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 +62 -13
- 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 +5 -3
- package/dist/v2/blocks/request-block/components/RequestBodyForm.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.d.ts +9 -3
- package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.d.ts.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.js.map +1 -1
- package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.script.js +44 -16
- package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.script.js.map +1 -1
- package/dist/v2/blocks/request-block/helpers/get-form-body-rows.d.ts +3 -2
- 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 +13 -10
- package/dist/v2/blocks/request-block/helpers/get-form-body-rows.js.map +1 -1
- 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 +3 -3
- 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/OAuth2.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.script.js +53 -22
- package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.d.ts +4 -0
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.script.js +23 -12
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthDataTableInput.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.script.js +30 -6
- package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.script.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-device-authorization.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-device-authorization.js +3 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth-device-authorization.js.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.d.ts.map +1 -1
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js +9 -3
- package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.d.ts.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.js +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.js.map +1 -1
- package/dist/v2/components/code-input/CodeInput.vue.script.js.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.d.ts.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.js +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.js.map +1 -1
- package/dist/v2/components/data-table/DataTableInput.vue.script.js +15 -11
- package/dist/v2/components/data-table/DataTableInput.vue.script.js.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.d.ts.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.js.map +1 -1
- package/dist/v2/components/server/ServerDropdown.vue.script.js +7 -9
- package/dist/v2/components/server/ServerDropdown.vue.script.js.map +1 -1
- package/dist/v2/constants.js +1 -1
- package/dist/v2/features/environments/components/EnvironmentVariablesDropdown.vue.d.ts.map +1 -1
- package/dist/v2/features/environments/components/EnvironmentVariablesDropdown.vue.js.map +1 -1
- package/dist/v2/features/environments/components/EnvironmentVariablesDropdown.vue.script.js +27 -31
- package/dist/v2/features/environments/components/EnvironmentVariablesDropdown.vue.script.js.map +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 +5 -1
- 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 +5 -1
- 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 +5 -1
- 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 +5 -1
- 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 +5 -1
- 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 +5 -1
- 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 +5 -1
- 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 +5 -1
- package/dist/v2/features/localization/translations.js.map +1 -1
- package/dist/v2/workspace-events.d.ts.map +1 -1
- package/dist/v2/workspace-events.js +1 -0
- package/dist/v2/workspace-events.js.map +1 -1
- package/dist/vue-styles.css +30 -33
- package/package.json +12 -12
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { objectEntries } from "@scalar/helpers/object/object-entries";
|
|
2
2
|
import { coerceLeafValueToSchemaType } from "@scalar/workspace-store/request-example";
|
|
3
3
|
import { isObject } from "@scalar/helpers/object/is-object";
|
|
4
|
-
import { resolve } from "@scalar/workspace-store/resolve";
|
|
5
4
|
import { isObjectSchema } from "@scalar/workspace-store/schemas/v3.2/strict/type-guards";
|
|
5
|
+
import { resolveFormSchema } from "@scalar/workspace-store/helpers/resolve-form-schema";
|
|
6
|
+
import { resolveSchemaWithAnnotations } from "@scalar/workspace-store/helpers/resolve-schema-with-annotations";
|
|
6
7
|
//#region src/v2/blocks/request-block/helpers/get-form-body-rows.ts
|
|
7
8
|
var stringifyValue = (value) => {
|
|
8
9
|
if (value instanceof File) return value.name;
|
|
@@ -17,19 +18,20 @@ var stringifyValue = (value) => {
|
|
|
17
18
|
* placeholders all stop the recursion. Object properties recurse, so the widget example
|
|
18
19
|
* (`file` + `props.{name,description,created_at}`) yields four leaves.
|
|
19
20
|
*/
|
|
20
|
-
var collectLeafProperties = (schema, parentPath = [], parentRequired = true) => {
|
|
21
|
+
var collectLeafProperties = (schema, parentPath = [], parentRequired = true, parentDescription) => {
|
|
21
22
|
if (!isObjectSchema(schema) || !schema.properties) return [];
|
|
22
23
|
const requiredSet = new Set(schema.required ?? []);
|
|
23
24
|
const leaves = [];
|
|
24
25
|
for (const [key, rawChildSchema] of objectEntries(schema.properties)) {
|
|
25
|
-
const childSchema =
|
|
26
|
+
const childSchema = resolveSchemaWithAnnotations(rawChildSchema);
|
|
26
27
|
const path = [...parentPath, String(key)];
|
|
27
28
|
const isRequired = parentRequired && requiredSet.has(String(key));
|
|
28
|
-
if (childSchema && isObjectSchema(childSchema) && childSchema.properties) leaves.push(...collectLeafProperties(childSchema, path, isRequired));
|
|
29
|
+
if (childSchema && isObjectSchema(childSchema) && childSchema.properties) leaves.push(...collectLeafProperties(childSchema, path, isRequired, childSchema.description ?? parentDescription));
|
|
29
30
|
else leaves.push({
|
|
30
31
|
path,
|
|
31
32
|
schema: childSchema,
|
|
32
|
-
isRequired
|
|
33
|
+
isRequired,
|
|
34
|
+
description: childSchema?.description ?? parentDescription
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
return leaves;
|
|
@@ -52,7 +54,7 @@ var collectExampleRows = (example, schema, parentPath = []) => {
|
|
|
52
54
|
for (const [key, rawChildSchema] of objectEntries(schemaProperties)) {
|
|
53
55
|
const keyStr = String(key);
|
|
54
56
|
declaredKeys.add(keyStr);
|
|
55
|
-
const childSchema =
|
|
57
|
+
const childSchema = resolveSchemaWithAnnotations(rawChildSchema);
|
|
56
58
|
const path = [...parentPath, keyStr];
|
|
57
59
|
const exampleSubvalue = exampleByKey.get(keyStr);
|
|
58
60
|
if (childSchema && isObjectSchema(childSchema) && childSchema.properties) rows.push(...collectExampleRows(exampleSubvalue, childSchema, path));
|
|
@@ -77,10 +79,11 @@ var getFormBodyValue = (row) => {
|
|
|
77
79
|
return Array.isArray(value) ? value : row.value ?? void 0;
|
|
78
80
|
};
|
|
79
81
|
/** Build the table rows for the form data, optionally enriched with schema (e.g. enum) per property */
|
|
80
|
-
var getFormBodyRows = (example, contentType, formBodySchema) => {
|
|
82
|
+
var getFormBodyRows = (example, contentType, formBodySchema, compositionSelection) => {
|
|
81
83
|
const value = example?.dataValue !== void 0 ? example.dataValue : example?.value;
|
|
82
84
|
if (!value || contentType !== "multipart/form-data" && contentType !== "application/x-www-form-urlencoded") return [];
|
|
83
|
-
const
|
|
85
|
+
const resolvedBodySchema = resolveFormSchema(formBodySchema, compositionSelection);
|
|
86
|
+
const schemaWithProperties = resolvedBodySchema && isObjectSchema(resolvedBodySchema) ? resolvedBodySchema : void 0;
|
|
84
87
|
const isComposed = Boolean(schemaWithProperties?.allOf || schemaWithProperties?.oneOf || schemaWithProperties?.anyOf);
|
|
85
88
|
const requiredSet = schemaWithProperties ? new Set(schemaWithProperties.required ?? []) : void 0;
|
|
86
89
|
const leafByDottedName = /* @__PURE__ */ new Map();
|
|
@@ -93,9 +96,9 @@ var getFormBodyRows = (example, contentType, formBodySchema) => {
|
|
|
93
96
|
};
|
|
94
97
|
if (!schemaWithProperties || !name) return row;
|
|
95
98
|
const leaf = leafByDottedName.get(name);
|
|
96
|
-
const propSchema = leaf?.schema ??
|
|
99
|
+
const propSchema = leaf?.schema ?? resolveSchemaWithAnnotations(schemaWithProperties.properties?.[name]);
|
|
97
100
|
row.schema = propSchema;
|
|
98
|
-
row.description = propSchema?.description;
|
|
101
|
+
row.description = leaf?.description ?? propSchema?.description;
|
|
99
102
|
row.isRequired = leaf?.isRequired ?? requiredSet?.has(name) ?? false;
|
|
100
103
|
if (isDisabled === void 0 && !isComposed && schemaWithProperties.properties && Object.hasOwn(schemaWithProperties.properties, name)) {
|
|
101
104
|
row.isDisabled = !row.isRequired;
|
|
@@ -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 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":";;;;;;AASA,IAAa,kBAAkB,UAAmB;CAChD,IAAI,iBAAiB,MACnB,OAAO,MAAM;CAEf,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO,KAAK,UAAU,KAAK;CAE7B,OAAO,OAAO,KAAK;AACrB;;;;;;;;;AAgBA,IAAa,yBACX,QACA,aAAuB,CAAC,GACxB,iBAAiB,SACH;CAEd,IAAI,CAAC,eAAe,MAAM,KAAK,CAAC,OAAO,YACrC,OAAO,CAAC;CAIV,MAAM,cAAc,IAAI,IAAI,OAAO,YAAY,CAAC,CAAC;CACjD,MAAM,SAAoB,CAAC;CAE3B,KAAK,MAAM,CAAC,KAAK,mBAAmB,cAAc,OAAO,UAAU,GAAG;EACpE,MAAM,cAAc,QAAQ,OAAO,cAAc;EACjD,MAAM,OAAO,CAAC,GAAG,YAAY,OAAO,GAAG,CAAC;EAIxC,MAAM,aAAa,kBAAkB,YAAY,IAAI,OAAO,GAAG,CAAC;EAKhE,IAAI,eAAe,eAAe,WAAW,KAAK,YAAY,YAC5D,OAAO,KAAK,GAAG,sBAAsB,aAAa,MAAM,UAAU,CAAC;OAEnE,OAAO,KAAK;GAAE;GAAM,QAAQ;GAAa;EAAW,CAAC;CAEzD;CAEA,OAAO;AACT;;;;;;;;;;AAWA,IAAa,sBACX,SACA,QACA,aAAuB,CAAC,MACiB;CACzC,MAAM,OAA6C,CAAC;CACpD,MAAM,iBAAiB,SAAS,OAAO,IAAI,cAAc,OAAO,IAAI,CAAC;CACrE,MAAM,eAAe,IAAI,IAAqB,eAAe,KAAK,CAAC,KAAK,WAAW,CAAC,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC;CACxG,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,mBAAmB,UAAU,eAAe,MAAM,IAAK,OAAO,cAAc,CAAC,IAAK,CAAC;CAKzF,KAAK,MAAM,CAAC,KAAK,mBAAmB,cAAc,gBAAgB,GAAG;EACnE,MAAM,SAAS,OAAO,GAAG;EACzB,aAAa,IAAI,MAAM;EACvB,MAAM,cAAc,QAAQ,OAAO,cAAc;EACjD,MAAM,OAAO,CAAC,GAAG,YAAY,MAAM;EACnC,MAAM,kBAAkB,aAAa,IAAI,MAAM;EAE/C,IAAI,eAAe,eAAe,WAAW,KAAK,YAAY,YAC5D,KAAK,KAAK,GAAG,mBAAmB,iBAAiB,aAAa,IAAI,CAAC;OAEnE,KAAK,KAAK;GAAE;GAAM,OAAO;EAAgB,CAAC;CAE9C;CAMA,KAAK,MAAM,CAAC,KAAK,UAAU,gBAAgB;EACzC,MAAM,SAAS,OAAO,GAAG;EACzB,IAAI,aAAa,IAAI,MAAM,GACzB;EAEF,KAAK,KAAK;GAAE,MAAM,CAAC,GAAG,YAAY,MAAM;GAAG;EAAM,CAAC;CACpD;CAEA,OAAO;AACT;;AAGA,IAAa,oBAAoB,QAAyD;CACxF,MAAM,QAAQ,4BAA4B,IAAI,OAAO,IAAI,UAAU,EAAE,MAAM,QAAQ,IAAI,IAAI,MAAM;CACjG,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAS,IAAI,SAAS,KAAA;AACtD;;AAGA,IAAa,mBACX,SACA,aACA,mBACe;CAEf,MAAM,QAAQ,SAAS,cAAc,KAAA,IAAY,QAAQ,YAAY,SAAS;CAE9E,IAAI,CAAC,SAAU,gBAAgB,yBAAyB,gBAAgB,qCACtE,OAAO,CAAC;CAIV,MAAM,uBAAuB,kBAAkB,eAAe,cAAc,IAAI,iBAAiB,KAAA;CAGjG,MAAM,aAAa,QAAQ,sBAAsB,SAAS,sBAAsB,SAAS,sBAAsB,KAAK;CACpH,MAAM,cAAc,uBAAuB,IAAI,IAAI,qBAAqB,YAAY,CAAC,CAAC,IAAI,KAAA;CAM1F,MAAM,mCAAmB,IAAI,IAAqB;CAClD,IAAI,sBACF,KAAK,MAAM,QAAQ,sBAAsB,oBAAoB,GAC3D,iBAAiB,IAAI,KAAK,KAAK,KAAK,GAAG,GAAG,IAAI;CAIlD,MAAM,UAAU,EACd,MACA,OACA,iBAKc;EACd,MAAM,MAAgB;GAAE;GAAM;GAAO,YAAY,cAAc;EAAM;EACrE,IAAI,CAAC,wBAAwB,CAAC,MAC5B,OAAO;EAIT,MAAM,OAAO,iBAAiB,IAAI,IAAI;EACtC,MAAM,aAAa,MAAM,UAAU,QAAQ,OAAO,qBAAqB,aAAa,KAAK;EACzF,IAAI,SAAS;EACb,IAAI,cAAc,YAAY;EAC9B,IAAI,aAAa,MAAM,cAAc,aAAa,IAAI,IAAI,KAAK;EAQ/D,IACE,eAAe,KAAA,KACf,CAAC,cACD,qBAAqB,cACrB,OAAO,OAAO,qBAAqB,YAAY,IAAI,GACnD;GACA,IAAI,aAAa,CAAC,IAAI;GAItB,IAAI,sBAAsB,IAAI;EAChC;EACA,OAAO;CACT;CAEA,MAAM,YAAY,MAAc,OAAgB,YAAsB,YAAkC;EAEtG,IACE,gBAAgB,yBAChB,MAAM,QAAQ,KAAK,KACnB,MAAM,SAAS,KACf,MAAM,OAAO,SAAS,gBAAgB,IAAI,GAE1C,OAAO,MAAM,KAAK,SAAS,OAAO;GAAE;GAAM,OAAO;GAAM;EAAW,CAAC,CAAC;EAEtE,MAAM,MAAM,OAAO;GACjB;GACA,OAAO,iBAAiB,OAAO,QAAQ,SAAS,OAAO,KAAK,eAAe,KAAK;GAChF;EACF,CAAC;EACD,IAAI,gBAAgB,0BAA0B,MAAM,QAAQ,KAAK,KAAK,UAEpE,IAAI,UAAU;EAEhB,OAAO,CAAC,GAAG;CACb;CAGA,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,SAAS,iBAAiB;EACrC,IAAI,SAAS,YAAY,GAAG;GAC1B,MAAM,OAAO,OAAO,aAAa,IAAI;GAYrC,QAVE,gBAAgB,wBACZ,SAAS,MAAM,aAAa,OAAO,QAAQ,aAAa,UAAU,GAAG,aAAa,YAAY,IAAI,IAClG,CACE,OAAO;IACL;IACA,OAAO,aAAa,iBAAiB,OAAO,aAAa,QAAQ,OAAO,aAAa,KAAK;IAC1F,YAAY,QAAQ,aAAa,UAAU;GAC7C,CAAC,CACH,EAAA,CAEM,KAAK,QACf,IAAI,cAAc,aAAa,wBAAwB,OAAO;IAAE,GAAG;IAAK,qBAAqB;GAAK,IAAI,GACxG;EACF;EACA,OAAO;GAAE,MAAM;GAAI,OAAO;GAAc,YAAY;EAAM;CAC5D,CAAC;CAYH,IAAI,gBAAgB,yBAAyB,wBAAwB,OAAO,UAAU,UACpF,OAAO,mBAAmB,OAAO,oBAAoB,CAAC,CAAC,SAAS,EAAE,MAAM,YAAY,SAAS,KAAK,KAAK,GAAG,GAAG,KAAK,CAAC;CAIrH,IAAI,OAAO,UAAU,YAAY,OAC/B,OAAO,cAAc,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,WACzC,gBAAgB,wBACZ,SAAS,OAAO,GAAG,GAAG,KAAK,IAC3B,OAAO;EAAE,MAAM,OAAO,GAAG;EAAG,OAAO,eAAe,KAAK;CAAE,CAAC,CAChE;CAGF,OAAO,CAAC;AACV"}
|
|
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 { resolveFormSchema } from '@scalar/workspace-store/helpers/resolve-form-schema'\nimport { resolveSchemaWithAnnotations } from '@scalar/workspace-store/helpers/resolve-schema-with-annotations'\nimport { coerceLeafValueToSchemaType } from '@scalar/workspace-store/request-example'\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 description?: string\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 parentDescription?: string,\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 = resolveSchemaWithAnnotations(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, childSchema.description ?? parentDescription))\n } else {\n leaves.push({ path, schema: childSchema, isRequired, description: childSchema?.description ?? parentDescription })\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 = resolveSchemaWithAnnotations(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 compositionSelection?: Record<string, number>,\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 resolvedBodySchema = resolveFormSchema(formBodySchema, compositionSelection)\n const schemaWithProperties = resolvedBodySchema && isObjectSchema(resolvedBodySchema) ? resolvedBodySchema : 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 ?? resolveSchemaWithAnnotations(schemaWithProperties.properties?.[name])\n row.schema = propSchema\n row.description = leaf?.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":";;;;;;;AAUA,IAAa,kBAAkB,UAAmB;CAChD,IAAI,iBAAiB,MACnB,OAAO,MAAM;CAEf,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO,KAAK,UAAU,KAAK;CAE7B,OAAO,OAAO,KAAK;AACrB;;;;;;;;;AAiBA,IAAa,yBACX,QACA,aAAuB,CAAC,GACxB,iBAAiB,MACjB,sBACc;CAEd,IAAI,CAAC,eAAe,MAAM,KAAK,CAAC,OAAO,YACrC,OAAO,CAAC;CAIV,MAAM,cAAc,IAAI,IAAI,OAAO,YAAY,CAAC,CAAC;CACjD,MAAM,SAAoB,CAAC;CAE3B,KAAK,MAAM,CAAC,KAAK,mBAAmB,cAAc,OAAO,UAAU,GAAG;EACpE,MAAM,cAAc,6BAA6B,cAAc;EAC/D,MAAM,OAAO,CAAC,GAAG,YAAY,OAAO,GAAG,CAAC;EAIxC,MAAM,aAAa,kBAAkB,YAAY,IAAI,OAAO,GAAG,CAAC;EAKhE,IAAI,eAAe,eAAe,WAAW,KAAK,YAAY,YAC5D,OAAO,KAAK,GAAG,sBAAsB,aAAa,MAAM,YAAY,YAAY,eAAe,iBAAiB,CAAC;OAEjH,OAAO,KAAK;GAAE;GAAM,QAAQ;GAAa;GAAY,aAAa,aAAa,eAAe;EAAkB,CAAC;CAErH;CAEA,OAAO;AACT;;;;;;;;;;AAWA,IAAa,sBACX,SACA,QACA,aAAuB,CAAC,MACiB;CACzC,MAAM,OAA6C,CAAC;CACpD,MAAM,iBAAiB,SAAS,OAAO,IAAI,cAAc,OAAO,IAAI,CAAC;CACrE,MAAM,eAAe,IAAI,IAAqB,eAAe,KAAK,CAAC,KAAK,WAAW,CAAC,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC;CACxG,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,mBAAmB,UAAU,eAAe,MAAM,IAAK,OAAO,cAAc,CAAC,IAAK,CAAC;CAKzF,KAAK,MAAM,CAAC,KAAK,mBAAmB,cAAc,gBAAgB,GAAG;EACnE,MAAM,SAAS,OAAO,GAAG;EACzB,aAAa,IAAI,MAAM;EACvB,MAAM,cAAc,6BAA6B,cAAc;EAC/D,MAAM,OAAO,CAAC,GAAG,YAAY,MAAM;EACnC,MAAM,kBAAkB,aAAa,IAAI,MAAM;EAE/C,IAAI,eAAe,eAAe,WAAW,KAAK,YAAY,YAC5D,KAAK,KAAK,GAAG,mBAAmB,iBAAiB,aAAa,IAAI,CAAC;OAEnE,KAAK,KAAK;GAAE;GAAM,OAAO;EAAgB,CAAC;CAE9C;CAMA,KAAK,MAAM,CAAC,KAAK,UAAU,gBAAgB;EACzC,MAAM,SAAS,OAAO,GAAG;EACzB,IAAI,aAAa,IAAI,MAAM,GACzB;EAEF,KAAK,KAAK;GAAE,MAAM,CAAC,GAAG,YAAY,MAAM;GAAG;EAAM,CAAC;CACpD;CAEA,OAAO;AACT;;AAGA,IAAa,oBAAoB,QAAyD;CACxF,MAAM,QAAQ,4BAA4B,IAAI,OAAO,IAAI,UAAU,EAAE,MAAM,QAAQ,IAAI,IAAI,MAAM;CACjG,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAS,IAAI,SAAS,KAAA;AACtD;;AAGA,IAAa,mBACX,SACA,aACA,gBACA,yBACe;CAEf,MAAM,QAAQ,SAAS,cAAc,KAAA,IAAY,QAAQ,YAAY,SAAS;CAE9E,IAAI,CAAC,SAAU,gBAAgB,yBAAyB,gBAAgB,qCACtE,OAAO,CAAC;CAIV,MAAM,qBAAqB,kBAAkB,gBAAgB,oBAAoB;CACjF,MAAM,uBAAuB,sBAAsB,eAAe,kBAAkB,IAAI,qBAAqB,KAAA;CAG7G,MAAM,aAAa,QAAQ,sBAAsB,SAAS,sBAAsB,SAAS,sBAAsB,KAAK;CACpH,MAAM,cAAc,uBAAuB,IAAI,IAAI,qBAAqB,YAAY,CAAC,CAAC,IAAI,KAAA;CAM1F,MAAM,mCAAmB,IAAI,IAAqB;CAClD,IAAI,sBACF,KAAK,MAAM,QAAQ,sBAAsB,oBAAoB,GAC3D,iBAAiB,IAAI,KAAK,KAAK,KAAK,GAAG,GAAG,IAAI;CAIlD,MAAM,UAAU,EACd,MACA,OACA,iBAKc;EACd,MAAM,MAAgB;GAAE;GAAM;GAAO,YAAY,cAAc;EAAM;EACrE,IAAI,CAAC,wBAAwB,CAAC,MAC5B,OAAO;EAIT,MAAM,OAAO,iBAAiB,IAAI,IAAI;EACtC,MAAM,aAAa,MAAM,UAAU,6BAA6B,qBAAqB,aAAa,KAAK;EACvG,IAAI,SAAS;EACb,IAAI,cAAc,MAAM,eAAe,YAAY;EACnD,IAAI,aAAa,MAAM,cAAc,aAAa,IAAI,IAAI,KAAK;EAQ/D,IACE,eAAe,KAAA,KACf,CAAC,cACD,qBAAqB,cACrB,OAAO,OAAO,qBAAqB,YAAY,IAAI,GACnD;GACA,IAAI,aAAa,CAAC,IAAI;GAItB,IAAI,sBAAsB,IAAI;EAChC;EACA,OAAO;CACT;CAEA,MAAM,YAAY,MAAc,OAAgB,YAAsB,YAAkC;EAEtG,IACE,gBAAgB,yBAChB,MAAM,QAAQ,KAAK,KACnB,MAAM,SAAS,KACf,MAAM,OAAO,SAAS,gBAAgB,IAAI,GAE1C,OAAO,MAAM,KAAK,SAAS,OAAO;GAAE;GAAM,OAAO;GAAM;EAAW,CAAC,CAAC;EAEtE,MAAM,MAAM,OAAO;GACjB;GACA,OAAO,iBAAiB,OAAO,QAAQ,SAAS,OAAO,KAAK,eAAe,KAAK;GAChF;EACF,CAAC;EACD,IAAI,gBAAgB,0BAA0B,MAAM,QAAQ,KAAK,KAAK,UAEpE,IAAI,UAAU;EAEhB,OAAO,CAAC,GAAG;CACb;CAGA,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,SAAS,iBAAiB;EACrC,IAAI,SAAS,YAAY,GAAG;GAC1B,MAAM,OAAO,OAAO,aAAa,IAAI;GAYrC,QAVE,gBAAgB,wBACZ,SAAS,MAAM,aAAa,OAAO,QAAQ,aAAa,UAAU,GAAG,aAAa,YAAY,IAAI,IAClG,CACE,OAAO;IACL;IACA,OAAO,aAAa,iBAAiB,OAAO,aAAa,QAAQ,OAAO,aAAa,KAAK;IAC1F,YAAY,QAAQ,aAAa,UAAU;GAC7C,CAAC,CACH,EAAA,CAEM,KAAK,QACf,IAAI,cAAc,aAAa,wBAAwB,OAAO;IAAE,GAAG;IAAK,qBAAqB;GAAK,IAAI,GACxG;EACF;EACA,OAAO;GAAE,MAAM;GAAI,OAAO;GAAc,YAAY;EAAM;CAC5D,CAAC;CAYH,IAAI,gBAAgB,yBAAyB,wBAAwB,OAAO,UAAU,UACpF,OAAO,mBAAmB,OAAO,oBAAoB,CAAC,CAAC,SAAS,EAAE,MAAM,YAAY,SAAS,KAAK,KAAK,GAAG,GAAG,KAAK,CAAC;CAIrH,IAAI,OAAO,UAAU,YAAY,OAC/B,OAAO,cAAc,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,WACzC,gBAAgB,wBACZ,SAAS,OAAO,GAAG,GAAG,KAAK,IAC3B,OAAO;EAAE,MAAM,OAAO,GAAG;EAAG,OAAO,eAAe,KAAK;CAAE,CAAC,CAChE;CAGF,OAAO,CAAC;AACV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBarHistory.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AddressBarHistory.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"names":[],"mappings":"AA0HA,KAAK,WAAW,GAAG;IACjB,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB,CAAC;AAcF,MAAM,MAAM,OAAO,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AA8QD,QAAA,MAAM,YAAY;;;eA3R6B,MAAM;;;;;eAAN,MAAM;;kFA8RnD,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../../_virtual/_plugin-vue_
|
|
|
2
2
|
import AddressBarHistory_vue_vue_type_script_setup_true_lang_default from "./AddressBarHistory.vue.script.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue
|
|
5
|
-
var AddressBarHistory_default = /*#__PURE__*/ _plugin_vue_export_helper_default(AddressBarHistory_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
5
|
+
var AddressBarHistory_default = /*#__PURE__*/ _plugin_vue_export_helper_default(AddressBarHistory_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-04d27531"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { AddressBarHistory_default as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBarHistory.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport {
|
|
1
|
+
{"version":3,"file":"AddressBarHistory.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport { ScalarFloating } from '@scalar/components/floating'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { formatMilliseconds } from '@scalar/helpers/formatters/format-milliseconds'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport ValueEmitter from '@/v2/components/layout/ValueEmitter.vue'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport { getStatusCodeColor } from './httpStatusCodeColors'\n\nconst { target } = defineProps<{\n /** The id of the target to use for the popover (e.g. address bar) */\n target: string\n /** List of all request history items */\n history: History[]\n}>()\n\nconst emits = defineEmits<{\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the open state of the history popover */\n (e: 'update:open', value: boolean): void\n}>()\n\nconst { translate } = useLocalization()\n\nexport type History = {\n method: string\n path: string\n duration: number\n status: number\n}\n</script>\n<template>\n <Menu\n v-slot=\"{ open }\"\n as=\"div\">\n <!-- Emit the slot value back out the parent -->\n <ValueEmitter\n :value=\"open\"\n @change=\"(value) => emits('update:open', value)\"\n @unmount=\"emits('update:open', false)\" />\n\n <ScalarFloating\n :offset=\"0\"\n resize\n :target=\"target\">\n <!-- History -->\n <MenuButton\n v-if=\"history.length\"\n class=\"address-bar-history-button text-c-3 focus:text-c-1 relative mr-1 rounded-lg p-1.5\">\n <ScalarIcon\n icon=\"History\"\n size=\"sm\"\n thickness=\"2.25\" />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBarHistory.requestHistory')\n }}</span>\n </MenuButton>\n <!-- History shadow and placement-->\n <template\n v-if=\"open\"\n #floating=\"{ width }\">\n <!-- History Item -->\n <MenuItems\n class=\"custom-scroll bg-b-1 dark:bg-b-1.5 grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center rounded-b-lg border border-t-0 p-0.75 shadow-md\"\n static\n :style=\"{ width }\">\n <MenuItem\n v-for=\"(entry, index) in history\"\n :key=\"index\"\n as=\"button\"\n class=\"font-code ui-active:*:bg-b-2 text-c-2 contents text-sm font-medium *:flex *:h-8 *:cursor-pointer *:items-center *:rounded-none *:px-1.5 *:first:rounded-l *:last:rounded-r\"\n :value=\"index\"\n @click=\"emits('select:history:item', { index })\">\n <HttpMethod\n class=\"text-[11px]\"\n :method=\"entry.method\" />\n <div class=\"min-w-0\">\n <div class=\"text-c-1 min-w-0 truncate\">\n {{ entry.path }}\n </div>\n </div>\n <div>{{ formatMilliseconds(entry.duration) }}</div>\n <div :class=\"[getStatusCodeColor(entry.status).color]\">\n {{ entry.status }}\n </div>\n <div>\n {{ httpStatusCodes[entry.status]?.name }}\n </div>\n </MenuItem>\n </MenuItems>\n </template>\n </ScalarFloating>\n </Menu>\n</template>\n<style scoped>\n.address-bar-history-button:hover {\n background: var(--scalar-background-3);\n}\n.address-bar-history-button:focus-within {\n background: var(--scalar-background-2);\n}\n</style>\n"],"mappings":""}
|
|
@@ -6,7 +6,7 @@ import { Fragment, createBlock, createCommentVNode, createElementBlock, createEl
|
|
|
6
6
|
import { ScalarIcon } from "@scalar/components/icon";
|
|
7
7
|
import { httpStatusCodes } from "@scalar/helpers/http/http-status-codes";
|
|
8
8
|
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
|
|
9
|
-
import { ScalarFloating
|
|
9
|
+
import { ScalarFloating } from "@scalar/components/floating";
|
|
10
10
|
import { formatMilliseconds } from "@scalar/helpers/formatters/format-milliseconds";
|
|
11
11
|
//#region src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue?vue&type=script&setup=true&lang.ts
|
|
12
12
|
var _hoisted_1 = { class: "sr-only" };
|
|
@@ -48,7 +48,7 @@ var AddressBarHistory_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
|
|
|
48
48
|
}, [open ? {
|
|
49
49
|
name: "floating",
|
|
50
50
|
fn: withCtx(({ width }) => [createVNode(unref(MenuItems), {
|
|
51
|
-
class: "custom-scroll grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center p-0.75",
|
|
51
|
+
class: "custom-scroll bg-b-1 dark:bg-b-1.5 grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center rounded-b-lg border border-t-0 p-0.75 shadow-md",
|
|
52
52
|
static: "",
|
|
53
53
|
style: normalizeStyle({ width })
|
|
54
54
|
}, {
|
|
@@ -74,7 +74,7 @@ var AddressBarHistory_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
|
|
|
74
74
|
}, 1032, ["value", "onClick"]);
|
|
75
75
|
}), 128))]),
|
|
76
76
|
_: 1
|
|
77
|
-
}, 8, ["style"])
|
|
77
|
+
}, 8, ["style"])]),
|
|
78
78
|
key: "0"
|
|
79
79
|
} : void 0]), 1032, ["target"])]),
|
|
80
80
|
_: 1
|
package/dist/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue.script.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBarHistory.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport {
|
|
1
|
+
{"version":3,"file":"AddressBarHistory.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-address-bar-block/components/AddressBarHistory.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'\nimport { ScalarFloating } from '@scalar/components/floating'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { formatMilliseconds } from '@scalar/helpers/formatters/format-milliseconds'\nimport { httpStatusCodes } from '@scalar/helpers/http/http-status-codes'\n\nimport { HttpMethod } from '@/components/HttpMethod'\nimport ValueEmitter from '@/v2/components/layout/ValueEmitter.vue'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport { getStatusCodeColor } from './httpStatusCodeColors'\n\nconst { target } = defineProps<{\n /** The id of the target to use for the popover (e.g. address bar) */\n target: string\n /** List of all request history items */\n history: History[]\n}>()\n\nconst emits = defineEmits<{\n /** Select a request history item by index */\n (e: 'select:history:item', payload: { index: number }): void\n /** Update the open state of the history popover */\n (e: 'update:open', value: boolean): void\n}>()\n\nconst { translate } = useLocalization()\n\nexport type History = {\n method: string\n path: string\n duration: number\n status: number\n}\n</script>\n<template>\n <Menu\n v-slot=\"{ open }\"\n as=\"div\">\n <!-- Emit the slot value back out the parent -->\n <ValueEmitter\n :value=\"open\"\n @change=\"(value) => emits('update:open', value)\"\n @unmount=\"emits('update:open', false)\" />\n\n <ScalarFloating\n :offset=\"0\"\n resize\n :target=\"target\">\n <!-- History -->\n <MenuButton\n v-if=\"history.length\"\n class=\"address-bar-history-button text-c-3 focus:text-c-1 relative mr-1 rounded-lg p-1.5\">\n <ScalarIcon\n icon=\"History\"\n size=\"sm\"\n thickness=\"2.25\" />\n <span class=\"sr-only\">{{\n translate('apiClient.addressBarHistory.requestHistory')\n }}</span>\n </MenuButton>\n <!-- History shadow and placement-->\n <template\n v-if=\"open\"\n #floating=\"{ width }\">\n <!-- History Item -->\n <MenuItems\n class=\"custom-scroll bg-b-1 dark:bg-b-1.5 grid max-h-[inherit] grid-cols-[44px_1fr_repeat(3,auto)] items-center rounded-b-lg border border-t-0 p-0.75 shadow-md\"\n static\n :style=\"{ width }\">\n <MenuItem\n v-for=\"(entry, index) in history\"\n :key=\"index\"\n as=\"button\"\n class=\"font-code ui-active:*:bg-b-2 text-c-2 contents text-sm font-medium *:flex *:h-8 *:cursor-pointer *:items-center *:rounded-none *:px-1.5 *:first:rounded-l *:last:rounded-r\"\n :value=\"index\"\n @click=\"emits('select:history:item', { index })\">\n <HttpMethod\n class=\"text-[11px]\"\n :method=\"entry.method\" />\n <div class=\"min-w-0\">\n <div class=\"text-c-1 min-w-0 truncate\">\n {{ entry.path }}\n </div>\n </div>\n <div>{{ formatMilliseconds(entry.duration) }}</div>\n <div :class=\"[getStatusCodeColor(entry.status).color]\">\n {{ entry.status }}\n </div>\n <div>\n {{ httpStatusCodes[entry.status]?.name }}\n </div>\n </MenuItem>\n </MenuItems>\n </template>\n </ScalarFloating>\n </Menu>\n</template>\n<style scoped>\n.address-bar-history-button:hover {\n background: var(--scalar-background-3);\n}\n.address-bar-history-button:focus-within {\n background: var(--scalar-background-2);\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;EAoBA,MAAM,QAAQ;EAOd,MAAM,EAAE,cAAc,gBAAgB;;uBAUpC,YA4DO,MAAA,IAAA,GAAA,EA1DL,IAAG,MAAK,GAAA;sBAKmC,EANjC,WAAI,CAGd,YAG2C,sBAAA;KAFxC,OAAO;KACP,UAAM,OAAA,OAAA,OAAA,MAAG,UAAU,MAAK,eAAgB,KAAK;KAC7C,WAAO,OAAA,OAAA,OAAA,MAAA,WAAE,MAAK,eAAA,KAAA;4BAEjB,YAkDiB,MAAA,cAAA,GAAA;KAjDd,QAAQ;KACT,QAAA;KACC,QAAQ,QAAA;;4BAYI,CATL,QAAA,QAAQ,UAAA,UAAA,GADhB,YAUa,MAAA,UAAA,GAAA;;MARX,OAAM;;6BAIe,CAHrB,YAGqB,MAAA,UAAA,GAAA;OAFnB,MAAK;OACL,MAAK;OACL,WAAU;UACZ,mBAES,QAFT,YAES,gBADP,MAAA,SAAA,CAAS,CAAA,4CAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;;;QAKL,OAAA;WACL;kBA6BW,EA7BC,YAAK,CAElB,YA2BY,MAAA,SAAA,GAAA;MA1BV,OAAM;MACN,QAAA;MACC,OAAK,eAAA,EAAI,MAAK,CAAA;;6BAEoB,EAAA,UAAA,IAAA,GADnC,mBAsBW,UAAA,MAAA,WArBgB,QAAA,UAAjB,OAAO,UAAK;2BADtB,YAsBW,MAAA,QAAA,GAAA;QApBR,KAAK;QACN,IAAG;QACH,OAAM;QACL,OAAO;QACP,UAAK,WAAE,MAAK,uBAAA,EAA0B,MAAK,CAAA;;+BAGjB;SAF3B,YAE2B,MAAA,kBAAA,GAAA;UADzB,OAAM;UACL,QAAQ,MAAM;;SACjB,mBAIM,OAJN,YAIM,CAHJ,mBAEM,OAFN,YAEM,gBADD,MAAM,IAAI,GAAA,CAAA,CAAA,CAAA;SAGjB,mBAAmD,OAAA,MAAA,gBAA3C,MAAA,kBAAA,CAAkB,CAAC,MAAM,QAAQ,CAAA,GAAA,CAAA;SACzC,mBAEM,OAAA,EAFA,OAAK,eAAA,CAAG,MAAA,kBAAA,CAAkB,CAAC,MAAM,MAAM,CAAA,CAAE,KAAK,CAAA,EAAA,GAAA,gBAC/C,MAAM,MAAM,GAAA,CAAA;SAEjB,mBAEM,OAAA,MAAA,gBADD,MAAA,eAAA,CAAe,CAAC,MAAM,OAAM,EAAG,IAAI,GAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuth2.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OAuth2.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"names":[],"mappings":"AA0tBA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAIzE,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,gCAAgC,CAAA;AAKvC,OAAO,EAKL,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAChC,MAAM,yCAAyC,CAAA;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2EAA2E,CAAA;AAEnH,OAAO,KAAK,EAEV,YAAY,EACb,MAAM,8DAA8D,CAAA;AAIrE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,sDAAsD,CAAA;AAS7D,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,sBAAsB,EACtB,mBAAmB,CACpB,GAAG;IACF,2GAA2G;IAC3G,WAAW,CAAC,EAAE,OAAO,KAAK,CAAA;IAC1B,0FAA0F;IAC1F,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;CAC9C,CAAA;wBACoB,OAAO,YAAY;AAAxC,wBAAyC;AACzC,QAAA,MAAM,YAAY;IAEhB,wCAAwC;iBAC3B,kBAAkB;IAC/B,kBAAkB;WACX,sBAAsB;IAC7B,6BAA6B;UACvB,MAAM,sBAAsB;IAClC,sBAAsB;oBACN,MAAM,EAAE;IACxB,sBAAsB;YACd,0BAA0B;IAClC,mCAAmC;YAC3B,YAAY,GAAG,IAAI;IAC3B,gBAAgB;cACN,MAAM;IAChB,kCAAkC;UAC5B,MAAM;IACZ,2CAA2C;cACjC,iBAAiB;IAC3B,uDAAuD;cAC7C,aAAa;IACvB;;;;OAIG;kBACW,OAAO;;;iBAmBC,MAAM,EAAE;gBAAU,MAAM;mBAAa,OAAO;;;;;;;;;;;;;;;;IA5ClE,wCAAwC;iBAC3B,kBAAkB;IAC/B,kBAAkB;WACX,sBAAsB;IAC7B,6BAA6B;UACvB,MAAM,sBAAsB;IAClC,sBAAsB;oBACN,MAAM,EAAE;IACxB,sBAAsB;YACd,0BAA0B;IAClC,mCAAmC;YAC3B,YAAY,GAAG,IAAI;IAC3B,gBAAgB;cACN,MAAM;IAChB,kCAAkC;UAC5B,MAAM;IACZ,2CAA2C;cACjC,iBAAiB;IAC3B,uDAAuD;cAC7C,aAAa;IACvB;;;;OAIG;kBACW,OAAO;;;iBAmBC,MAAM,EAAE;gBAAU,MAAM;mBAAa,OAAO;;;;;;;;;;;;;;;kFAu2ChE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuth2.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport { type XusePkce } from '@scalar/workspace-store/schemas/extensions/security/x-use-pkce'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport type { DeviceAuthorizationPrompt } from '../helpers/oauth-device-authorization'\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /**\n * Hides the Authorize / Refresh / Clear actions. Used when the form is embedded as a\n * configuration-only view (e.g. behind a gear icon), so the host component owns the\n * authorize action and the tokens are routed to the scheme it chooses.\n */\n hideActions?: boolean\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst devicePrompt = ref<DeviceAuthorizationPrompt | null>(null)\nlet deviceController: AbortController | undefined\nconst cancelDeviceAuthorization = (): void => {\n deviceController?.abort()\n devicePrompt.value = null\n}\nonBeforeUnmount(cancelDeviceAuthorization)\nwatch(() => [type, name, flows], cancelDeviceAuthorization)\n\nconst { translate } = useLocalization()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * Show the client secret whenever the flow supports one. PKCE and a client\n * secret are independent, so confidential clients can use both together\n * (see RFC 9700 Section 2.1.1).\n */\nconst showClientSecret = computed(\n () => 'x-scalar-secret-client-secret' in flow.value,\n)\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation> & {\n deviceAuthorizationUrl?: string\n },\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n // Only OAuth2 schemes carry flows on the document; this component is never\n // rendered for the other scheme types.\n if (scheme.type !== 'oauth2') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void => {\n // This component only renders for oauth2/openIdConnect. Other scheme types (e.g. mutualTLS)\n // carry no flow secrets, so there is nothing to persist and no matching secrets payload type.\n if (scheme.type !== 'oauth2' && scheme.type !== 'openIdConnect') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n}\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n const controller = new AbortController()\n deviceController = controller\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n {\n signal: controller.signal,\n onPrompt: (prompt) => {\n devicePrompt.value = prompt\n },\n },\n )\n devicePrompt.value = null\n\n await loader.clear()\n if (controller.signal.aborted) {\n return\n }\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToauthorize'),\n 'error',\n )\n }\n}\n\n/** Refreshing requires a token issued by the provider, not just a compatible flow. */\nconst canRefreshToken = computed<boolean>(\n (): boolean =>\n type !== 'implicit' && Boolean(flow.value['x-scalar-secret-refresh-token']),\n)\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n // The explicit flow check narrows the type for refreshOauth2Token; the computed boolean cannot.\n if (loader.isLoading || type === 'implicit' || !canRefreshToken.value) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToRefreshToken'),\n 'error',\n )\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n const credentialsLocation = value === 'body' ? 'body' : 'header'\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template v-if=\"Boolean(flow['x-scalar-secret-token'])\">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n {{ translate('apiClient.oauth2.accessToken') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"canRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n {{ translate('apiClient.oauth2.refreshUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"canRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n {{ translate('apiClient.oauth2.refresh') }}\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n {{ translate('apiClient.oauth2.clear') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow v-if=\"'deviceAuthorizationUrl' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow.deviceAuthorizationUrl\"\n @update:modelValue=\"\n (v) => handleOauth2Update({ deviceAuthorizationUrl: v })\n \">\n {{ translate('apiClient.oauth2.deviceAuthorizationUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n <DataTableRow v-if=\"devicePrompt\">\n <div class=\"w-full p-2\">\n <div\n class=\"bg-b-2 flex flex-col gap-3 rounded-lg border p-3 text-sm\"\n role=\"status\">\n <p class=\"text-c-2 text-xs leading-normal\">\n {{ translate('apiClient.oauth2.deviceVerificationPrompt') }}\n </p>\n <strong\n class=\"bg-b-1 font-code w-fit max-w-full rounded border px-3 py-2 text-base font-medium tracking-widest break-all select-all\">\n {{ devicePrompt.userCode }}\n </strong>\n <a\n class=\"text-c-accent w-fit max-w-full text-xs break-all underline underline-offset-2\"\n :href=\"\n devicePrompt.verificationUriComplete ||\n devicePrompt.verificationUri\n \"\n rel=\"noopener noreferrer\"\n target=\"_blank\">\n {{ devicePrompt.verificationUri }}\n </a>\n <div class=\"flex items-center justify-between gap-3\">\n <p class=\"text-c-2 text-xs\">\n {{ translate('apiClient.oauth2.waitingForAuthorization') }}\n </p>\n <ScalarButton\n size=\"xs\"\n variant=\"outlined\"\n @click=\"cancelDeviceAuthorization\">\n {{ translate('apiClient.oauth2.cancelDeviceAuthorization') }}\n </ScalarButton>\n </div>\n </div>\n </div>\n </DataTableRow>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n handleOauth2Update({ authorizationUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.authUrl') }}\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n handleOauth2Update({ tokenUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.tokenUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : translate('apiClient.oauth2.optionalRedirectUrl')\n \"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n {{ translate('apiClient.oauth2.redirectUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n {{ translate('apiClient.oauth2.username') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n {{ translate('apiClient.oauth2.password') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n {{ translate('apiClient.oauth2.clientID') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n {{ translate('apiClient.oauth2.clientSecret') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"\n (v) =>\n handleOauth2Update({\n 'x-usePkce': v as XusePkce['x-usePkce'],\n })\n \">\n {{ translate('apiClient.oauth2.usePKCE') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n {{ translate('apiClient.oauth2.credentialsLocation') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @delete:scope=\"(v) => emits('delete:scope', v)\"\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n {{ translate('apiClient.oauth2.clear') }}\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n {{ translate('apiClient.oauth2.authorize') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"OAuth2.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n isSecretFieldCleared,\n type AuthSecretField,\n} from '@scalar/workspace-store/helpers/auth-secret-fields'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\nimport { useLocalization } from '@/v2/features/localization'\n\nimport type { DeviceAuthorizationPrompt } from '../helpers/oauth-device-authorization'\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /**\n * Hides the Authorize / Refresh / Clear actions. Used when the form is embedded as a\n * configuration-only view (e.g. behind a gear icon), so the host component owns the\n * authorize action and the tokens are routed to the scheme it chooses.\n */\n hideActions?: boolean\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst devicePrompt = ref<DeviceAuthorizationPrompt | null>(null)\nlet deviceController: AbortController | undefined\nconst cancelDeviceAuthorization = (): void => {\n deviceController?.abort()\n devicePrompt.value = null\n}\nonBeforeUnmount(cancelDeviceAuthorization)\nwatch(() => [type, name, flows], cancelDeviceAuthorization)\n\nconst { translate } = useLocalization()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * Show the client secret whenever the flow supports one. PKCE and a client\n * secret are independent, so confidential clients can use both together\n * (see RFC 9700 Section 2.1.1).\n */\nconst showClientSecret = computed(\n () => 'x-scalar-secret-client-secret' in flow.value,\n)\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation> & {\n deviceAuthorizationUrl?: string\n },\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n // Only OAuth2 schemes carry flows on the document; this component is never\n // rendered for the other scheme types.\n if (scheme.type !== 'oauth2') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void => {\n // This component only renders for oauth2/openIdConnect. Other scheme types (e.g. mutualTLS)\n // carry no flow secrets, so there is nothing to persist and no matching secrets payload type.\n if (scheme.type !== 'oauth2' && scheme.type !== 'openIdConnect') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n}\n\n/** Reset only this field; redirect URI may need its browser default prefilled again. */\nconst handleResetSecret = (field: AuthSecretField): void => {\n if (field === 'x-scalar-secret-redirect-uri') {\n hasHandledRedirectPrefill.value = false\n }\n eventBus.emit('auth:reset:security-scheme-secret', {\n name,\n flow: type,\n field,\n })\n}\n\n/** Fixed-choice fields must never write an empty string into the document. */\nconst handlePkceUpdate = (value: string): void => {\n if (value === 'SHA-256' || value === 'plain' || value === 'no') {\n handleOauth2Update({ 'x-usePkce': value })\n }\n}\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n isSecretFieldCleared(currentFlow, 'x-scalar-secret-redirect-uri') ||\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n const controller = new AbortController()\n deviceController = controller\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n {\n signal: controller.signal,\n onPrompt: (prompt) => {\n devicePrompt.value = prompt\n },\n },\n )\n devicePrompt.value = null\n\n await loader.clear()\n if (controller.signal.aborted) {\n return\n }\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToauthorize'),\n 'error',\n )\n }\n}\n\n/** Refreshing requires a token issued by the provider, not just a compatible flow. */\nconst canRefreshToken = computed<boolean>(\n (): boolean =>\n type !== 'implicit' && Boolean(flow.value['x-scalar-secret-refresh-token']),\n)\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n // The explicit flow check narrows the type for refreshOauth2Token; the computed boolean cannot.\n if (loader.isLoading || type === 'implicit' || !canRefreshToken.value) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(\n error?.message ?? translate('apiClient.oauth2.failedToRefreshToken'),\n 'error',\n )\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n if (value !== 'body' && value !== 'header') return\n const credentialsLocation = value\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template\n v-if=\"\n Boolean(flow['x-scalar-secret-token']) ||\n isSecretFieldCleared(flow, 'x-scalar-secret-token')\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @reset=\"handleResetSecret('x-scalar-secret-token')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n {{ translate('apiClient.oauth2.accessToken') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"canRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n {{ translate('apiClient.oauth2.refreshUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"canRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n {{ translate('apiClient.oauth2.refresh') }}\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n {{ translate('apiClient.oauth2.clearTokens') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow v-if=\"'deviceAuthorizationUrl' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow.deviceAuthorizationUrl\"\n @update:modelValue=\"\n (v) => handleOauth2Update({ deviceAuthorizationUrl: v })\n \">\n {{ translate('apiClient.oauth2.deviceAuthorizationUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n <DataTableRow v-if=\"devicePrompt\">\n <div class=\"w-full p-2\">\n <div\n class=\"bg-b-2 flex flex-col gap-3 rounded-lg border p-3 text-sm\"\n role=\"status\">\n <p class=\"text-c-2 text-xs leading-normal\">\n {{ translate('apiClient.oauth2.deviceVerificationPrompt') }}\n </p>\n <strong\n class=\"bg-b-1 font-code w-fit max-w-full rounded border px-3 py-2 text-base font-medium tracking-widest break-all select-all\">\n {{ devicePrompt.userCode }}\n </strong>\n <a\n class=\"text-c-accent w-fit max-w-full text-xs break-all underline underline-offset-2\"\n :href=\"\n devicePrompt.verificationUriComplete ||\n devicePrompt.verificationUri\n \"\n rel=\"noopener noreferrer\"\n target=\"_blank\">\n {{ devicePrompt.verificationUri }}\n </a>\n <div class=\"flex items-center justify-between gap-3\">\n <p class=\"text-c-2 text-xs\">\n {{ translate('apiClient.oauth2.waitingForAuthorization') }}\n </p>\n <ScalarButton\n size=\"xs\"\n variant=\"outlined\"\n @click=\"cancelDeviceAuthorization\">\n {{ translate('apiClient.oauth2.cancelDeviceAuthorization') }}\n </ScalarButton>\n </div>\n </div>\n </div>\n </DataTableRow>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n canReset\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @reset=\"handleResetSecret('x-scalar-secret-auth-url')\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n if (v) handleOauth2Update({ authorizationUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.authUrl') }}\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @reset=\"handleResetSecret('x-scalar-secret-token-url')\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n if (v) handleOauth2Update({ tokenUrl: v })\n }\n \">\n {{ translate('apiClient.oauth2.tokenUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : translate('apiClient.oauth2.optionalRedirectUrl')\n \"\n @reset=\"handleResetSecret('x-scalar-secret-redirect-uri')\"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n {{ translate('apiClient.oauth2.redirectUrl') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @reset=\"handleResetSecret('x-scalar-secret-username')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n {{ translate('apiClient.oauth2.username') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @reset=\"handleResetSecret('x-scalar-secret-password')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n {{ translate('apiClient.oauth2.password') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @reset=\"handleResetSecret('x-scalar-secret-client-id')\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n {{ translate('apiClient.oauth2.clientID') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n canReset\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @reset=\"handleResetSecret('x-scalar-secret-client-secret')\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n {{ translate('apiClient.oauth2.clientSecret') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"handlePkceUpdate\">\n {{ translate('apiClient.oauth2.usePKCE') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n {{ translate('apiClient.oauth2.credentialsLocation') }}\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @delete:scope=\"(v) => emits('delete:scope', v)\"\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n {{ translate('apiClient.oauth2.resetDiscovery') }}\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n {{ translate('apiClient.oauth2.authorize') }}\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":""}
|