mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-07 13:44:54 +03:00
fix: No maxLength label is displayed for arrays of items #1701
This commit is contained in:
parent
b5fb407470
commit
74bcf11514
27
src/components/Fields/ArrayItemDetails.tsx
Normal file
27
src/components/Fields/ArrayItemDetails.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { TypeFormat, TypeName, TypePrefix } from '../../common-elements/fields';
|
||||||
|
import { ConstraintsView } from './FieldContstraints';
|
||||||
|
import { Pattern } from './Pattern';
|
||||||
|
import { SchemaModel } from '../../services';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
|
||||||
|
if (!schema || schema.type === 'string' && !schema.constraints.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ArrayItemDetailsStyled>
|
||||||
|
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
||||||
|
<TypeName>{schema.displayType}</TypeName>
|
||||||
|
{schema.displayFormat && (
|
||||||
|
<TypeFormat>{` <${schema.displayFormat}> `}</TypeFormat>
|
||||||
|
)}
|
||||||
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
|
<Pattern schema={schema}/>
|
||||||
|
{schema.items && <ArrayItemDetails schema={schema.items}/> }
|
||||||
|
</ArrayItemDetailsStyled>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArrayItemDetailsStyled = styled.div`
|
||||||
|
padding-left: 7px;
|
||||||
|
`;
|
|
@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
|
||||||
export interface EnumValuesProps {
|
export interface EnumValuesProps {
|
||||||
values: string[];
|
values: string[];
|
||||||
type: string | string[];
|
isArrayType: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnumValuesState {
|
export interface EnumValuesState {
|
||||||
|
@ -27,7 +27,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { values, type } = this.props;
|
const { values, isArrayType } = this.props;
|
||||||
const { collapsed } = this.state;
|
const { collapsed } = this.state;
|
||||||
|
|
||||||
// TODO: provide context interface in more elegant way
|
// TODO: provide context interface in more elegant way
|
||||||
|
@ -55,7 +55,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>
|
<FieldLabel>
|
||||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
{isArrayType ? l('enumArray') : ''}{' '}
|
||||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||||
</FieldLabel>{' '}
|
</FieldLabel>{' '}
|
||||||
{displayedItems.map((value, idx) => {
|
{displayedItems.map((value, idx) => {
|
||||||
|
|
35
src/components/Fields/Examples.tsx
Normal file
35
src/components/Fields/Examples.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { FieldLabel, ExampleValue } from '../../common-elements/fields';
|
||||||
|
import { getSerializedValue } from '../../utils';
|
||||||
|
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
import { FieldModel } from '../../services';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
export function Examples({ field }: { field: FieldModel }) {
|
||||||
|
if (!field.examples) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FieldLabel> {l('examples')}: </FieldLabel>
|
||||||
|
<ExamplesList>
|
||||||
|
{Object.values(field.examples).map((example, idx) => {
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ExamplesList>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExamplesList = styled.ul`
|
||||||
|
margin-top: 1em;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style-position: inside;
|
||||||
|
`;
|
|
@ -1,22 +1,19 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PatternLabel,
|
|
||||||
RecursiveLabel,
|
RecursiveLabel,
|
||||||
TypeFormat,
|
TypeFormat,
|
||||||
TypeName,
|
TypeName,
|
||||||
TypePrefix,
|
TypePrefix,
|
||||||
TypeTitle,
|
TypeTitle,
|
||||||
ToggleButton,
|
|
||||||
FieldLabel,
|
|
||||||
ExampleValue,
|
|
||||||
} from '../../common-elements/fields';
|
} from '../../common-elements/fields';
|
||||||
import { serializeParameterValue } from '../../utils/openapi';
|
import { getSerializedValue } from '../../utils';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { EnumValues } from './EnumValues';
|
import { EnumValues } from './EnumValues';
|
||||||
import { Extensions } from './Extensions';
|
import { Extensions } from './Extensions';
|
||||||
import { FieldProps } from './Field';
|
import { FieldProps } from './Field';
|
||||||
|
import { Examples } from './Examples';
|
||||||
import { ConstraintsView } from './FieldContstraints';
|
import { ConstraintsView } from './FieldContstraints';
|
||||||
import { FieldDetail } from './FieldDetail';
|
import { FieldDetail } from './FieldDetail';
|
||||||
|
|
||||||
|
@ -24,30 +21,19 @@ import { Badge } from '../../common-elements/';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { FieldModel } from '../../services/models/Field';
|
import { Pattern } from './Pattern';
|
||||||
import styled from '../../styled-components';
|
import { ArrayItemDetails } from './ArrayItemDetails';
|
||||||
|
|
||||||
const MAX_PATTERN_LENGTH = 45;
|
|
||||||
|
|
||||||
export class FieldDetails extends React.PureComponent<FieldProps, { patternShown: boolean }> {
|
export class FieldDetails extends React.PureComponent<FieldProps, { patternShown: boolean }> {
|
||||||
state = {
|
|
||||||
patternShown: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
static contextType = OptionsContext;
|
static contextType = OptionsContext;
|
||||||
|
|
||||||
togglePattern = () => {
|
|
||||||
this.setState({
|
|
||||||
patternShown: !this.state.patternShown,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
||||||
const { patternShown } = this.state;
|
const { enumSkipQuotes, hideSchemaTitles } = this.context;
|
||||||
const { enumSkipQuotes, hideSchemaTitles, hideSchemaPattern } = this.context;
|
|
||||||
|
|
||||||
const { schema, description, example, deprecated, examples } = field;
|
const { schema, description, example, deprecated, examples } = field;
|
||||||
|
const { type } = schema;
|
||||||
|
const isArrayType = type === 'array';
|
||||||
|
|
||||||
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
||||||
|
|
||||||
|
@ -92,20 +78,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
|
||||||
)}
|
)}
|
||||||
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||||
<ConstraintsView constraints={schema.constraints} />
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
{schema.pattern && !hideSchemaPattern && (
|
<Pattern schema={schema}/>
|
||||||
<>
|
|
||||||
<PatternLabel>
|
|
||||||
{patternShown || schema.pattern.length < MAX_PATTERN_LENGTH
|
|
||||||
? schema.pattern
|
|
||||||
: `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
|
|
||||||
</PatternLabel>
|
|
||||||
{schema.pattern.length > MAX_PATTERN_LENGTH && (
|
|
||||||
<ToggleButton onClick={this.togglePattern}>
|
|
||||||
{patternShown ? 'Hide pattern' : 'Show pattern'}
|
|
||||||
</ToggleButton>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||||
</div>
|
</div>
|
||||||
{deprecated && (
|
{deprecated && (
|
||||||
|
@ -114,7 +87,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
{!renderDiscriminatorSwitch && <EnumValues isArrayType={isArrayType} values={schema.enum} />}{' '}
|
||||||
{renderedExamples}
|
{renderedExamples}
|
||||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||||
<div>
|
<div>
|
||||||
|
@ -125,44 +98,8 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
|
||||||
)}
|
)}
|
||||||
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
|
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
|
||||||
{field.const && (<FieldDetail label={l('const') + ':'} value={field.const} />) || null}
|
{field.const && (<FieldDetail label={l('const') + ':'} value={field.const} />) || null}
|
||||||
|
{isArrayType && schema.items && <ArrayItemDetails schema={schema.items}/>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Examples({ field }: { field: FieldModel }) {
|
|
||||||
if (!field.examples) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FieldLabel> {l('examples')}: </FieldLabel>
|
|
||||||
<ExamplesList>
|
|
||||||
{Object.values(field.examples).map((example, idx) => {
|
|
||||||
return (
|
|
||||||
<li key={idx}>
|
|
||||||
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ExamplesList>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSerializedValue(field: FieldModel, example: any) {
|
|
||||||
if (field.in) {
|
|
||||||
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
|
||||||
return decodeURIComponent(serializeParameterValue(field, example));
|
|
||||||
} else {
|
|
||||||
return example;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const ExamplesList = styled.ul`
|
|
||||||
margin-top: 1em;
|
|
||||||
padding-left: 0;
|
|
||||||
list-style-position: inside;
|
|
||||||
`;
|
|
||||||
|
|
30
src/components/Fields/Pattern.tsx
Normal file
30
src/components/Fields/Pattern.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { PatternLabel, ToggleButton } from '../../common-elements/fields';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
import { useCallback, useContext, useState } from 'react';
|
||||||
|
import { SchemaModel } from '../../services';
|
||||||
|
|
||||||
|
const MAX_PATTERN_LENGTH = 45;
|
||||||
|
|
||||||
|
export function Pattern(props: {schema: SchemaModel}) {
|
||||||
|
const pattern = props.schema.pattern;
|
||||||
|
const { hideSchemaPattern } = useContext(OptionsContext);
|
||||||
|
const [patternShown, usePatternShown] = useState(false);
|
||||||
|
const togglePattern = useCallback(() => usePatternShown(!patternShown), [patternShown]);
|
||||||
|
|
||||||
|
if (!pattern || hideSchemaPattern) return null;
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<PatternLabel>
|
||||||
|
{patternShown || pattern.length < MAX_PATTERN_LENGTH
|
||||||
|
? pattern
|
||||||
|
: `${pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
|
||||||
|
</PatternLabel>
|
||||||
|
{pattern.length > MAX_PATTERN_LENGTH && (
|
||||||
|
<ToggleButton onClick={togglePattern}>
|
||||||
|
{patternShown ? 'Hide pattern' : 'Show pattern'}
|
||||||
|
</ToggleButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
|
@ -362,6 +362,15 @@ export function serializeParameterValue(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSerializedValue(field: FieldModel, example: any) {
|
||||||
|
if (field.in) {
|
||||||
|
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
||||||
|
return decodeURIComponent(serializeParameterValue(field, example));
|
||||||
|
} else {
|
||||||
|
return example;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function langFromMime(contentType: string): string {
|
export function langFromMime(contentType: string): string {
|
||||||
if (contentType.search(/xml/i) !== -1) {
|
if (contentType.search(/xml/i) !== -1) {
|
||||||
return 'xml';
|
return 'xml';
|
||||||
|
|
Loading…
Reference in New Issue
Block a user