This commit is contained in:
Parsa Abbasi 2025-11-06 10:34:57 +01:00 committed by GitHub
commit 2df3cae454
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 506 additions and 28404 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
import * as React from 'react';
\import * as React from 'react';
import { createRoot } from 'react-dom/client';
import styled from 'styled-components';
import { RedocStandalone } from '../src';
import ComboBox from './ComboBox';
import FileInput from './components/FileInput';
const DEFAULT_SPEC = 'museum.yaml';
const DEFAULT_SPEC = 'big-openapi.json';
const NEW_VERSION_PETSTORE = 'openapi-3-1.yaml';
const demos = [

View File

@ -1,787 +0,0 @@
openapi: 3.1.0
info:
title: Redocly Museum API
description: An imaginary, but delightful Museum API for interacting with museum services and information. Built with love by Redocly.
version: 1.0.0
contact:
email: team@redocly.com
url: 'https://redocly.com/docs/cli/'
x-logo:
url: 'https://redocly.github.io/redoc/museum-logo.png'
altText: Museum logo
license:
name: MIT
url: 'https://opensource.org/license/mit/ '
servers:
- url: 'https://api.fake-museum-example.com/v1'
paths:
/museum-hours:
get:
summary: Get museum hours
description: Get upcoming museum operating hours
operationId: getMuseumHours
tags:
- Operations
x-badges:
- name: 'Beta'
position: before
color: purple
parameters:
- $ref: '#/components/parameters/StartDate'
- $ref: '#/components/parameters/PaginationPage'
- $ref: '#/components/parameters/PaginationLimit'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/GetMuseumHoursResponse'
examples:
default:
summary: Museum opening hours
value:
- date: '2023-09-11'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-12'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-13'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-17'
timeOpen: '09:00'
timeClose: '18:00'
closed:
summary: The museum is closed
value: []
'400':
description: Bad request
'404':
description: Not found
/special-events:
post:
security: []
operationId: CreateSpecialEvent
summary: Create special event
tags:
- Events
x-badges:
- name: 'Alpha'
color: purple
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSpecialEventRequest'
examples:
default_example:
$ref: '#/components/examples/CreateSpecialEventRequestExample'
responses:
'200':
description: success
content:
application/json:
schema:
$ref: '#/components/schemas/SpecialEventResponse'
examples:
default_example:
$ref: '#/components/examples/CreateSpecialEventResponseExample'
'400':
description: Bad request
'404':
description: Not found
get:
summary: List special events
description: Return a list of upcoming special events at the museum.
security: []
operationId: listSpecialEvents
x-badges:
- name: 'Gamma'
tags:
- Events
parameters:
- name: startDate
in: query
description: The starting date to retrieve future operating hours from. Defaults to today's date.
schema:
type: string
format: date
example: 2023-02-23
- name: endDate
in: query
description: The end of a date range to retrieve special events for. Defaults to 7 days after `startDate`.
schema:
type: string
format: date
example: 2023-04-18
- name: page
in: query
description: The page number to retrieve.
schema:
type: integer
default: 1
example: 2
- name: limit
in: query
description: The number of days per page.
schema:
type: integer
default: 10
maximum: 30
example: 15
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/ListSpecialEventsResponse'
examples:
default_example:
$ref: '#/components/examples/ListSpecialEventsResponseExample'
'400':
description: Bad request
'404':
description: Not found
/special-events/{eventId}:
get:
summary: Get special event
description: Get details about a special event.
operationId: getSpecialEvent
tags:
- Events
parameters:
- $ref: '#/components/parameters/EventId'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/SpecialEventResponse'
examples:
default_example:
$ref: '#/components/examples/GetSpecialEventResponseExample'
'400':
description: Bad request
'404':
description: Not found
patch:
summary: Update special event
description: Update the details of a special event
operationId: updateSpecialEvent
tags:
- Events
parameters:
- $ref: '#/components/parameters/EventId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateSpecialEventRequest'
examples:
default_example:
$ref: '#/components/examples/UpdateSpecialEventRequestExample'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/SpecialEventResponse'
examples:
default_example:
$ref: '#/components/examples/UpdateSpecialEventResponseExample'
'400':
description: Bad request
'404':
description: Not found
delete:
summary: Delete special event
description: Delete a special event from the collection. Allows museum to cancel planned events.
operationId: deleteSpecialEvent
tags:
- Events
parameters:
- $ref: '#/components/parameters/EventId'
responses:
'204':
description: Success - no content
'400':
description: Bad request
'401':
description: Unauthorized
'404':
description: Not found
/tickets:
post:
summary: Buy museum tickets
description: Purchase museum tickets for general entry or special events.
operationId: buyMuseumTickets
tags:
- Tickets
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BuyMuseumTicketsRequest'
examples:
general_entry:
$ref: '#/components/examples/BuyGeneralTicketsRequestExample'
event_entry:
$ref: '#/components/examples/BuyEventTicketsRequestExample'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/BuyMuseumTicketsResponse'
examples:
general_entry:
$ref: '#/components/examples/BuyGeneralTicketsResponseExample'
event_entry:
$ref: '#/components/examples/BuyEventTicketsResponseExample'
'400':
description: Bad request
'404':
description: Not found
/tickets/{ticketId}/qr:
get:
summary: Get ticket QR code
description: Return an image of your ticket with scannable QR code. Used for event entry.
operationId: getTicketCode
tags:
- Tickets
parameters:
- $ref: '#/components/parameters/TicketId'
responses:
'200':
description: Scannable event ticket in image format
content:
image/png:
schema:
$ref: '#/components/schemas/GetTicketCodeResponse'
'400':
description: Bad request
'404':
description: Not found
components:
schemas:
SpecialEvent:
description: Request payload for creating new special events at the museum.
properties:
name:
description: Name of the special event
type: string
example: Fossil lecture
location:
description: Location where the special event is held
type: string
example: Lecture theatre
eventDescription:
description: Description of the special event
type: string
example: Our panel of experts will share their favorite fossils and explain why they are so great.
dates:
description: List of planned dates for the special event
type: array
items:
type: string
format: date
example: 2024-03-29
price:
description: Price of a ticket for the special event
type: number
format: float
example: 12.50
TicketType:
description: Type of ticket being purchased. Use `general` for regular museum entry and `event` for tickets to special events.
type: string
enum:
- event
- general
x-enumDescriptions:
event: Special event ticket
general: General museum entry ticket
example: event
Date:
type: string
format: date
example: 2023-10-29
Email:
description: Email address for ticket purchaser.
type: string
format: email
example: museum-lover@example.com
Phone:
description: Phone number for the ticket purchaser (optional).
type: string
example: +1(234)-567-8910
BuyMuseumTicketsRequest:
description: Request payload used for purchasing museum tickets.
type: object
properties:
ticketType:
$ref: '#/components/schemas/TicketType'
eventId:
description: Unique identifier for a special event. Required if purchasing tickets for the museum's special events.
$ref: '#/components/schemas/EventId'
ticketDate:
description: Date that the ticket is valid for.
$ref: '#/components/schemas/Date'
email:
$ref: '#/components/schemas/Email'
phone:
$ref: '#/components/schemas/Phone'
required:
- ticketType
- ticketDate
- email
TicketMessage:
description: Confirmation message after a ticket purchase.
type: string
example: Museum general entry ticket purchased
TicketId:
description: Unique identifier for museum ticket. Generated when purchased.
type: string
format: uuid
example: a54a57ca-36f8-421b-a6b4-2e8f26858a4c
TicketConfirmation:
description: Unique confirmation code used to verify ticket purchase.
type: string
example: ticket-event-a98c8f-7eb12
BuyMuseumTicketsResponse:
description: Details for a museum ticket after a successful purchase.
type: object
properties:
message:
$ref: '#/components/schemas/TicketMessage'
eventName:
$ref: '#/components/schemas/EventName'
ticketId:
$ref: '#/components/schemas/TicketId'
ticketType:
$ref: '#/components/schemas/TicketType'
ticketDate:
description: Date the ticket is valid for.
$ref: '#/components/schemas/Date'
confirmationCode:
$ref: '#/components/schemas/TicketConfirmation'
required:
- message
- ticketId
- ticketType
- ticketDate
- confirmationCode
GetTicketCodeResponse:
description: An image of a ticket with a QR code used for museum or event entry.
type: string
format: binary
GetMuseumHoursResponse:
description: List of museum operating hours for consecutive days.
type: array
items:
$ref: '#/components/schemas/MuseumDailyHours'
MuseumDailyHours:
description: Daily operating hours for the museum.
type: object
properties:
date:
description: Date the operating hours apply to.
$ref: '#/components/schemas/Date'
example: 2024-12-31
timeOpen:
type: string
pattern: '^([01]\d|2[0-3]):?([0-5]\d)$'
description: Time the museum opens on a specific date. Uses 24 hour time format (`HH:mm`).
example: 09:00
timeClose:
description: Time the museum closes on a specific date. Uses 24 hour time format (`HH:mm`).
type: string
pattern: '^([01]\d|2[0-3]):?([0-5]\d)$'
example: 18:00
required:
- date
- timeOpen
- timeClose
EventId:
description: Identifier for a special event.
type: string
format: uuid
example: 3be6453c-03eb-4357-ae5a-984a0e574a54
EventName:
type: string
description: Name of the special event
example: Pirate Coding Workshop
EventLocation:
type: string
description: Location where the special event is held
example: Computer Room
EventDescription:
type: string
description: Description of the special event
example: Captain Blackbeard shares his love of the C...language. And possibly Arrrrr (R lang).
EventDates:
type: array
items:
$ref: '#/components/schemas/Date'
description: List of planned dates for the special event
EventPrice:
description: Price of a ticket for the special event
type: number
format: float
example: 25
CreateSpecialEventRequest:
description: Request payload for creating new special events at the museum.
properties:
name:
$ref: '#/components/schemas/EventName'
location:
$ref: '#/components/schemas/EventLocation'
eventDescription:
$ref: '#/components/schemas/EventDescription'
dates:
$ref: '#/components/schemas/EventDates'
price:
$ref: '#/components/schemas/EventPrice'
required:
- name
- location
- eventDescription
- dates
- price
UpdateSpecialEventRequest:
description: Request payload for updating an existing special event. Only included fields are updated in the event.
properties:
name:
$ref: '#/components/schemas/EventName'
location:
$ref: '#/components/schemas/EventLocation'
eventDescription:
$ref: '#/components/schemas/EventDescription'
dates:
$ref: '#/components/schemas/EventDates'
price:
$ref: '#/components/schemas/EventPrice'
ListSpecialEventsResponse:
description: A list of upcoming special events
type: array
items:
$ref: '#/components/schemas/SpecialEventResponse'
SpecialEventResponse:
description: Information about a special event.
properties:
eventId:
$ref: '#/components/schemas/EventId'
name:
$ref: '#/components/schemas/EventName'
location:
$ref: '#/components/schemas/EventLocation'
eventDescription:
$ref: '#/components/schemas/EventDescription'
dates:
$ref: '#/components/schemas/EventDates'
price:
$ref: '#/components/schemas/EventPrice'
required:
- eventId
- name
- location
- eventDescription
- dates
- price
securitySchemes:
MuseumPlaceholderAuth:
type: http
scheme: basic
examples:
BuyGeneralTicketsRequestExample:
summary: General entry ticket
value:
ticketType: general
ticketDate: 2023-09-07
email: todd@example.com
BuyEventTicketsRequestExample:
summary: Special event ticket
value:
ticketType: general
eventId: dad4bce8-f5cb-4078-a211-995864315e39
ticketDate: 2023-09-05
email: todd@example.com
BuyGeneralTicketsResponseExample:
summary: General entry ticket
value:
message: Museum general entry ticket purchased
ticketId: 382c0820-0530-4f4b-99af-13811ad0f17a
ticketType: general
ticketDate: 2023-09-07
confirmationCode: ticket-general-e5e5c6-dce78
BuyEventTicketsResponseExample:
summary: Special event ticket
value:
message: Museum special event ticket purchased
ticketId: b811f723-17b2-44f7-8952-24b03e43d8a9
eventName: Mermaid Treasure Identification and Analysis
ticketType: event
ticketDate: 2023-09-05
confirmationCode: ticket-event-9c55eg-8v82a
CreateSpecialEventRequestExample:
summary: Create special event
value:
name: Mermaid Treasure Identification and Analysis
location: Under the seaaa 🦀 🎶 🌊.
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.
dates:
- 2023-09-05
- 2023-09-08
price: 0
CreateSpecialEventResponseExample:
summary: Special event created
value:
eventId: dad4bce8-f5cb-4078-a211-995864315e39
name: Mermaid Treasure Identification and Analysis
location: Under the seaaa 🦀 🎶 🌊.
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.
dates:
- 2023-09-05
- 2023-09-08
price: 30
GetSpecialEventResponseExample:
summary: Get special event
value:
eventId: 6744a0da-4121-49cd-8479-f8cc20526495
name: Time Traveler Tea Party
location: Temporal Tearoom
eventDescription: Sip tea with important historical figures.
dates:
- 2023-11-18
- 2023-11-25
- 2023-12-02
price: 60
ListSpecialEventsResponseExample:
summary: List of special events
value:
- eventId: f3e0e76e-e4a8-466e-ab9c-ae36c15b8e97
name: Sasquatch Ballet
location: Seattle... probably
eventDescription: They're big, they're hairy, but they're also graceful. Come learn how the biggest feet can have the lightest touch.
dates:
- 2023-12-15
- 2023-12-22
price: 40
- eventId: 2f14374a-9c65-4ee5-94b7-fba66d893483
name: Solar Telescope Demonstration
location: Far from the sun.
eventDescription: Look at the sun without going blind!
dates:
- 2023-09-07
- 2023-09-14
price: 50
- eventId: 6aaa61ba-b2aa-4868-b803-603dbbf7bfdb
name: Cook like a Caveman
location: Fire Pit on East side
eventDescription: Learn to cook on an open flame.
dates:
- 2023-11-10
- 2023-11-17
- 2023-11-24
price: 5
- eventId: 602b75e1-5696-4ab8-8c7a-f9e13580f910
name: Underwater Basket Weaving
location: Rec Center Pool next door.
eventDescription: Learn to weave baskets underwater.
dates:
- 2023-09-12
- 2023-09-15
price: 15
- eventId: dad4bce8-f5cb-4078-a211-995864315e39
name: Mermaid Treasure Identification and Analysis
location: Room Sea-12
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits — kindly donated by Ariel.
dates:
- 2023-09-05
- 2023-09-08
price: 30
- eventId: 6744a0da-4121-49cd-8479-f8cc20526495
name: Time Traveler Tea Party
location: Temporal Tearoom
eventDescription: Sip tea with important historical figures.
dates:
- 2023-11-18
- 2023-11-25
- 2023-12-02
price: 60
- eventId: 3be6453c-03eb-4357-ae5a-984a0e574a54
name: Pirate Coding Workshop
location: Computer Room
eventDescription: Captain Blackbeard shares his love of the C...language. And possibly Arrrrr (R lang).
dates:
- 2023-10-29
- 2023-10-30
- 2023-10-31
price: 45
- eventId: 9d90d29a-2af5-4206-97d9-9ea9ceadcb78
name: Llama Street Art Through the Ages
location: Auditorium
eventDescription: Llama street art?! Alpaca my bags -- let's go!
dates:
- 2023-10-29
- 2023-10-30
- 2023-10-31
price: 45
- eventId: a3c7b2c4-b5fb-4ef7-9322-00a919864957
name: The Great Parrot Debate
location: Outdoor Amphitheatre
eventDescription: See leading parrot minds discuss important geopolitical issues.
dates:
- 2023-11-03
- 2023-11-10
price: 35
- eventId: b92d46b7-4c5d-422b-87a5-287767e26f29
name: Eat a Bunch of Corn
location: Cafeteria
eventDescription: We accidentally bought too much corn. Please come eat it.
dates:
- 2023-11-10
- 2023-11-17
- 2023-11-24
price: 5
UpdateSpecialEventRequestExample:
summary: Update special event request
value:
location: On the beach.
price: 15
UpdateSpecialEventResponseExample:
summary: Update special event
value:
eventId: dad4bce8-f5cb-4078-a211-995864315e39
name: Mermaid Treasure Identification and Analysis
location: On the beach.
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.
dates:
- 2023-09-05
- 2023-09-08
price: 15
GetMuseumHours:
summary: Museum opening hours
value:
- date: '2023-09-11'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-12'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-13'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-14'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-15'
timeOpen: '10:00'
timeClose: '16:00'
- date: '2023-09-18'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-19'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-20'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-21'
timeOpen: '09:00'
timeClose: '18:00'
- date: '2023-09-22'
timeOpen: '10:00'
timeClose: '16:00'
ClosedMuseumHours:
summary: The museum is closed
value: []
parameters:
PaginationPage:
name: page
in: query
description: The page number to retrieve.
schema:
type: integer
default: 1
example: 2
PaginationLimit:
name: limit
in: query
description: The number of days per page.
schema:
type: integer
default: 10
maximum: 30
example: 15
EventId:
name: eventId
in: path
description: An identifier for a special event.
required: true
schema:
type: string
format: uuid
example: dad4bce8-f5cb-4078-a211-995864315e39
StartDate:
name: startDate
in: query
description: The starting date to retrieve future operating hours from. Defaults to today's date.
schema:
type: string
format: date
example: 2023-02-23
EndDate:
name: endDate
in: query
description: The end of a date range to retrieve special events for. Defaults to 7 days after `startDate`.
schema:
type: string
format: date
example: 2023-04-18
TicketId:
name: ticketId
in: path
description: An identifier for a ticket to a museum event. Used to generate ticket image.
required: true
schema:
type: string
format: uuid
example: a54a57ca-36f8-421b-a6b4-2e8f26858a4c
tags:
- name: Operations
x-displayName: About the museum
description: Operational information about the museum.
- name: Events
x-displayName: Upcoming events
description: Special events hosted by the Museum.
- name: Tickets
x-displayName: Buy tickets
description: Museum tickets for general entrance or special events.
x-tagGroups:
- name: Plan your visit
tags:
- Operations
- Events
- name: Purchases
tags:
- Tickets
- name: Entities
tags:
- Schemas
security:
- MuseumPlaceholderAuth: []

View File

@ -9,7 +9,7 @@ const swagger = window.location.search.indexOf('swagger') > -1;
const userUrl = window.location.search.match(/url=(.*)$/);
const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'museum.yaml' : big ? 'big-openapi.json' : 'museum.yaml');
(userUrl && userUrl[1]) || (swagger ? 'big-openapi.json' : big ? 'big-openapi.json' : 'big-openapi.json');
const options: RedocRawOptions = {
nativeScrollbars: false,

View File

@ -115,7 +115,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}) => ({
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
webpackIgnore(/^\.\/SearchWorker\.worker$/),
new CopyWebpackPlugin({
patterns: ['demo/museum.yaml'],
patterns: ['demo/big-openapi.json'],
}),
],
});

View File

@ -1,6 +1,6 @@
{
"name": "redoc",
"version": "2.5.2",
"version": "2.5.0",
"description": "ReDoc",
"repository": {
"type": "git",
@ -62,7 +62,6 @@
},
"devDependencies": {
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@cypress/webpack-preprocessor": "^5.17.1",
"@size-limit/file": "^11.1.4",
"@types/chai": "^4.2.18",
"@types/dompurify": "^2.2.2",
@ -90,7 +89,6 @@
"copy-webpack-plugin": "^9.0.0",
"core-js": "^3.13.1",
"css-loader": "^5.2.6",
"cypress": "^13.8.1",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2",
"esbuild-loader": "^4.3.0",
@ -126,7 +124,7 @@
"url": "^0.11.1",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.2.1",
"webpack-dev-server": "^4.15.1",
"webpack-node-externals": "^3.0.0",
"workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
},
@ -147,8 +145,8 @@
"lunr": "^2.3.9",
"mark.js": "^8.11.1",
"marked": "^4.3.0",
"mobx-react": "9.2.0",
"openapi-sampler": "^1.6.2",
"mobx-react": "^9.1.1",
"openapi-sampler": "^1.5.0",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.5",
"polished": "^4.2.2",

View File

@ -151,7 +151,7 @@ export const PropertiesTable = styled.table`
border-collapse: separate;
border-radius: 3px;
font-size: ${props => props.theme.typography.fontSize};
direction: ltr;
border-spacing: 0;
width: 100%;

View File

@ -24,6 +24,7 @@ export const H2 = styled.h2`
${headerCommonMixin(2)};
color: ${({ theme }) => theme.colors.text.primary};
margin: 0 0 20px;
text-align: right;
${extensionsHook('H2')};
`;
@ -31,6 +32,7 @@ export const H2 = styled.h2`
export const H3 = styled.h2`
${headerCommonMixin(3)};
color: ${({ theme }) => theme.colors.text.primary};
text-align: right;
${extensionsHook('H3')};
`;

View File

@ -77,7 +77,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
{info.title} {version}
</ApiHeader>
{!hideDownloadButtons && (
<p>
<p style={{ 'direction' : 'ltr', 'textAlign':'left' }}>
{l('downloadSpecification')}:
{downloadUrls?.map(({ title, url }) => {
return (

View File

@ -1,51 +1,211 @@
import { H1, MiddlePanel } from '../../common-elements';
import styled, { extensionsHook } from '../../styled-components';
import * as classnames from 'classnames';
import { darken } from 'polished';
const delimiterWidth = 15;
import { deprecatedCss, ShelfIcon } from '../../common-elements';
import styled, { css, media, ResolvedThemeInterface } from '../../styled-components';
export const ApiInfoWrap = MiddlePanel;
export const ApiHeader = styled(H1)`
margin-top: 0;
margin-bottom: 0.5em;
${extensionsHook('ApiHeader')};
`;
export const DownloadButton = styled.a`
border: 1px solid ${props => props.theme.colors.primary.main};
color: ${props => props.theme.colors.primary.main};
font-weight: normal;
margin-left: 0.5em;
padding: 4px 8px 4px;
export const OperationBadge = styled.span.attrs((props: { type: string; color?: string }) => ({
className: `operation-type ${props.type}`,
}))<{ type: string; color?: string }>`
width: 9ex;
display: inline-block;
text-decoration: none;
cursor: pointer;
${extensionsHook('DownloadButton')};
`;
export const InfoSpan = styled.span`
&::before {
content: '|';
display: inline-block;
opacity: 0.5;
width: ${delimiterWidth}px;
height: ${props => props.theme.typography.code.fontSize};
line-height: ${props => props.theme.typography.code.fontSize};
background-color: ${props => props.color || '#333'};
border-radius: 3px;
background-repeat: no-repeat;
background-position: 6px 4px;
font-size: 7px;
font-family: Verdana, sans-serif; // web-safe
color: white;
text-transform: uppercase;
text-align: center;
font-weight: bold;
vertical-align: middle;
margin-right: 6px;
margin-top: 2px;
margin-left: 6px;
&.get {
background-color: ${({ theme }) => theme.colors.http.get};
}
&:last-child::after {
display: none;
&.post {
background-color: ${({ theme }) => theme.colors.http.post};
}
&.put {
background-color: ${({ theme }) => theme.colors.http.put};
}
&.options {
background-color: ${({ theme }) => theme.colors.http.options};
}
&.patch {
background-color: ${({ theme }) => theme.colors.http.patch};
}
&.delete {
background-color: ${({ theme }) => theme.colors.http.delete};
}
&.basic {
background-color: ${({ theme }) => theme.colors.http.basic};
}
&.link {
background-color: ${({ theme }) => theme.colors.http.link};
}
&.head {
background-color: ${({ theme }) => theme.colors.http.head};
}
&.hook {
background-color: ${({ theme }) => theme.colors.primary.main};
}
&.schema {
background-color: ${({ theme }) => theme.colors.http.basic};
}
`;
export const InfoSpanBoxWrap = styled.div`
function menuItemActive(
depth,
{ theme }: { theme: ResolvedThemeInterface },
option: string,
): string {
if (depth > 1) {
return theme.sidebar.level1Items[option];
} else if (depth === 1) {
return theme.sidebar.groupItems[option];
} else {
return '';
}
}
export const MenuItemUl = styled.ul<{ $expanded: boolean }>`
margin: 0;
padding: 0;
direction: rtl;
text-align: right;
&:first-child {
padding-bottom: 32px;
}
& & {
font-size: 0.929em;
}
${props => (props.$expanded ? '' : 'display: none;')};
`;
export const MenuItemLi = styled.li<{ depth: number }>`
list-style: none inside none;
overflow: hidden;
text-overflow: ellipsis;
padding: 0;
${props => (props.depth === 0 ? 'margin-top: 15px' : '')};
`;
export const InfoSpanBox = styled.div`
export const menuItemDepth = {
0: css`
opacity: 0.7;
text-transform: ${({ theme }) => theme.sidebar.groupItems.textTransform};
font-size: 0.8em;
padding-bottom: 0;
cursor: default;
`,
1: css`
font-size: 0.929em;
text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform};
`,
};
export interface MenuItemLabelType {
$depth: number;
$active: boolean;
$deprecated?: boolean;
$type?: string;
}
export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
className: classnames('-depth' + props.$depth, {
active: props.$active,
}),
}))<MenuItemLabelType>`
cursor: pointer;
color: ${props =>
props.$active
? menuItemActive(props.$depth, props, 'activeTextColor')
: props.theme.sidebar.textColor};
margin: 0;
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
${({ $depth, $type, theme }) =>
($type === 'section' && $depth > 1 && 'padding-left: ' + theme.spacing.unit * 8 + 'px;') || ''}
display: flex;
flex-wrap: wrap;
// hide separator on new lines: idea from https://stackoverflow.com/a/31732902/1749888
margin-left: -${delimiterWidth}px;
justify-content: space-between;
font-family: ${props => props.theme.typography.headings.fontFamily};
${props => menuItemDepth[props.$depth]};
background-color: ${props =>
props.$active
? menuItemActive(props.$depth, props, 'activeBackgroundColor')
: props.theme.sidebar.backgroundColor};
${props => (props.$deprecated && deprecatedCss) || ''};
&:hover {
color: ${props => menuItemActive(props.$depth, props, 'activeTextColor')};
background-color: ${props => menuItemActive(props.$depth, props, 'activeBackgroundColor')};
}
${ShelfIcon} {
height: ${({ theme }) => theme.sidebar.arrow.size};
width: ${({ theme }) => theme.sidebar.arrow.size};
polygon {
fill: ${({ theme }) => theme.sidebar.arrow.color};
}
}
`;
export const MenuItemTitle = styled.span<{ width?: string }>`
display: inline-block;
vertical-align: middle;
width: ${props => (props.width ? props.width : 'auto')};
overflow: hidden;
text-overflow: ellipsis;
`;
export const RedocAttribution = styled.div`
${({ theme }) => css`
font-size: 0.8em;
margin-top: ${theme.spacing.unit * 2}px;
text-align: center;
position: fixed;
width: ${theme.sidebar.width};
bottom: 0;
background: ${theme.sidebar.backgroundColor};
a,
a:visited,
a:hover {
color: ${theme.sidebar.textColor} !important;
padding: ${theme.spacing.unit}px 0;
border-top: 1px solid ${darken(0.1, theme.sidebar.backgroundColor)};
text-decoration: none;
display: flex;
align-items: center;
justify-content: center;
}
`};
img {
width: 15px;
margin-right: 5px;
}
${media.lessThan('small')`
width: 100%;
`};
`;

View File

@ -1,7 +1,6 @@
import * as React from 'react';
import { ShelfIcon } from '../../common-elements';
import { OperationModel } from '../../services';
import { Markdown } from '../Markdown/Markdown';
import { OptionsContext } from '../OptionsProvider';
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
@ -59,7 +58,7 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
color={inverted ? 'black' : 'white'}
size={'20px'}
direction={expanded ? 'up' : 'down'}
style={{ marginRight: '-25px' }}
style={{ marginRight: '-5px' }}
/>
</EndpointInfo>
<ServersOverlay $expanded={expanded} aria-hidden={!expanded}>
@ -70,7 +69,9 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
const basePath = getBasePath(normalizedUrl);
return (
<ServerItem key={normalizedUrl}>
<Markdown source={server.description || ''} compact={true} />
<p style={{ textAlign: 'left', margin:'0 0 5px 0',padding:'0' }}>
{server.description || ''}
</p>
<SelectOnClick>
<ServerUrl>
<span>

View File

@ -1,6 +1,10 @@
import { css } from '../../styled-components';
export const jsonStyles = css`
.redoc-json{
direction: ltr;
}
.redoc-json code > .collapser {
display: none;
pointer-events: none;

View File

@ -26,6 +26,7 @@ export function SanitizedMarkdownHTML({
<OptionsConsumer>
{options => (
<Wrap
style={{'direction':'rtl'}}
className={'redoc-markdown ' + (rest.className || '')}
dangerouslySetInnerHTML={{
__html: sanitize(options.sanitize, rest.html),

View File

@ -28,6 +28,7 @@ export const StyledMarkdownBlock = styled(
>,
)`
font-family: ${props => props.theme.typography.fontFamily};
direction: ltr;
font-weight: ${props => props.theme.typography.fontWeightRegular};
line-height: ${props => props.theme.typography.lineHeight};
@ -105,9 +106,10 @@ export const StyledMarkdownBlock = styled(
blockquote {
margin: 0;
margin-bottom: 1em;
padding: 0 15px;
margin-top: 1em;
padding: 0 0;
color: #777;
border-left: 4px solid #ddd;
border-right: 4px solid #ddd;
}
img {
@ -117,7 +119,8 @@ export const StyledMarkdownBlock = styled(
ul,
ol {
padding-left: 2em;
text-align: right;
padding-right: 2em;
margin: 0;
margin-bottom: 1em;

View File

@ -42,6 +42,10 @@ export class Redoc extends React.Component<RedocProps> {
<StoreProvider value={store}>
<OptionsProvider value={options}>
<RedocWrap className="redoc-wrap">
<ApiContentWrap className="api-content">
<ApiInfo store={store} />
<ContentItems items={menu.items as any} />
</ApiContentWrap>
<StickyResponsiveSidebar menu={menu} className="menu-content">
<ApiLogo info={spec.info} />
{(!options.disableSearch && (
@ -55,10 +59,6 @@ export class Redoc extends React.Component<RedocProps> {
null}
<SideMenu menu={menu} />
</StickyResponsiveSidebar>
<ApiContentWrap className="api-content">
<ApiInfo store={store} />
<ContentItems items={menu.items as any} />
</ApiContentWrap>
<BackgroundStub />
</RedocWrap>
</OptionsProvider>

View File

@ -27,6 +27,7 @@ export const RedocWrap = styled.div`
export const ApiContentWrap = styled.div`
z-index: 1;
direction: rtl;
position: relative;
overflow: hidden;
width: calc(100% - ${props => props.theme.sidebar.width});
@ -35,6 +36,10 @@ export const ApiContentWrap = styled.div`
`};
contain: layout;
p {
text-align: right;
}
`;
export const BackgroundStub = styled.div`
@ -42,7 +47,7 @@ export const BackgroundStub = styled.div`
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
width: ${({ theme }) => {
if (theme.rightPanel.width.endsWith('%')) {
const percents = parseInt(theme.rightPanel.width, 10);

View File

@ -148,8 +148,8 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
<SearchInput
value={this.state.term}
onKeyDown={this.handleKeyDown}
placeholder="Search..."
aria-label="Search"
placeholder="جستجو ..."
aria-label="جستجو"
type="text"
onChange={this.search}
/>

View File

@ -13,6 +13,8 @@ export const SearchInput = styled.input.attrs(() => ({
}))`
width: calc(100% - ${props => props.theme.spacing.unit * 8}px);
box-sizing: border-box;
text-align: right;
direction: rtl;
margin: 0 ${props => props.theme.spacing.unit * 4}px;
padding: 5px ${props => props.theme.spacing.unit * 2}px 5px
${props => props.theme.spacing.unit * 4}px;
@ -91,10 +93,10 @@ export const ClearIcon = styled.i`
display: inline-block;
width: ${props => props.theme.spacing.unit * 2}px;
text-align: center;
right: ${props => props.theme.spacing.unit * 4}px;
left: ${props => props.theme.spacing.unit * 4}px;
line-height: 2em;
vertical-align: middle;
margin-right: 2px;
margin-left: 15px;
cursor: pointer;
font-style: normal;
color: '#666';

View File

@ -1,132 +1,212 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { ShelfIcon } from '../../common-elements/shelfs';
import type { IMenuItem } from '../../services';
import { OperationModel } from '../../services';
import { l } from '../../services/Labels';
import { scrollIntoViewIfNeeded } from '../../utils';
import { shortenHTTPVerb } from '../../utils/openapi';
import { OptionsContext } from '../OptionsProvider';
import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
export interface MenuItemProps {
item: IMenuItem;
onActivate?: (item: IMenuItem) => void;
withoutChildren?: boolean;
children?: React.ReactChild;
{
"name": "redoc",
"version": "2.5.0",
"description": "ReDoc",
"repository": {
"type": "git",
"url": "git://github.com/Redocly/redoc"
},
"browserslist": [
"defaults"
],
"engines": {
"node": ">=6.9",
"npm": ">=3.0.0"
},
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
"license": "MIT",
"keywords": [
"OpenAPI",
"OpenAPI Specification",
"Swagger",
"JSON-Schema",
"API",
"REST",
"documentation",
"React.js"
],
"main": "bundles/redoc.lib.js",
"browser": "bundles/redoc.browser.lib.js",
"types": "typings/index.d.ts",
"scripts": {
"start": "webpack serve --mode=development --env playground --hot --config demo/webpack.config.ts",
"start:prod": "webpack serve --env playground --mode=production --config demo/webpack.config.ts",
"start:benchmark": "webpack serve --mode=production --env.bench --config demo/webpack.config.ts",
"test": "npm run unit && npm run license-check",
"unit": "jest --coverage",
"test:update-snapshot": "jest --updateSnapshot",
"e2e": "cypress run",
"e2e-ci": "cypress run --record",
"bundlesize": "size-limit",
"ts-check": "tsc --noEmit --skipLibCheck",
"cy:open": "cypress open",
"bundle:clean": "rimraf bundles",
"bundle:standalone": "webpack --env production --env standalone --mode=production",
"bundle:lib": "webpack --mode=production && npm run declarations",
"bundle:browser": "webpack --env production --env browser --mode=production",
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:browser && npm run bundle:standalone",
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
"stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
"prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
"lint": "eslint --fix 'src/**/*.{js,ts,tsx}' --cache",
"benchmark": "node ./benchmark/benchmark.js",
"start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
"publish-cdn": "scripts/publish-cdn.sh",
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0;CC0-1.0;Python-2.0 ' --summary",
"docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
"prepare": "husky install",
"pre-commit": "pretty-quick --staged && npm run lint"
},
"devDependencies": {
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@size-limit/file": "^11.1.4",
"@types/chai": "^4.2.18",
"@types/dompurify": "^2.2.2",
"@types/enzyme": "^3.10.5",
"@types/enzyme-to-json": "^1.5.3",
"@types/jest": "^29.5.6",
"@types/json-pointer": "^1.0.30",
"@types/lunr": "^2.3.3",
"@types/mark.js": "^8.11.5",
"@types/marked": "^4.0.3",
"@types/node": "^15.6.1",
"@types/prismjs": "^1.16.5",
"@types/prop-types": "^15.7.3",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/styled-components": "^5.1.1",
"@types/tapable": "^2.2.2",
"@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.18.0",
"@types/yargs": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"beautify-benchmark": "^0.2.4",
"conventional-changelog-cli": "^3.0.0",
"copy-webpack-plugin": "^9.0.0",
"core-js": "^3.13.1",
"css-loader": "^5.2.6",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2",
"esbuild-loader": "^4.3.0",
"eslint": "^7.27.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"fork-ts-checker-webpack-plugin": "^6.2.10",
"html-webpack-plugin": "^5.3.1",
"husky": "^7.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"js-yaml": "^4.1.0",
"license-checker": "^25.0.1",
"lodash.noop": "^3.0.1",
"mobx": "^6.10.2",
"outdent": "^0.8.0",
"prettier": "^2.3.2",
"pretty-quick": "^3.0.0",
"raf": "^3.4.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"rimraf": "^3.0.2",
"shelljs": "^0.8.4",
"size-limit": "^11.1.4",
"style-loader": "^3.3.1",
"styled-components": "^5.3.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tslib": "^2.4.0",
"typescript": "^4.9.0",
"unfetch": "^4.2.0",
"url": "^0.11.1",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-node-externals": "^3.0.0",
"workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
},
"peerDependencies": {
"core-js": "^3.1.4",
"mobx": "^6.0.4",
"react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5"
},
"dependencies": {
"@redocly/openapi-core": "^1.4.0",
"classnames": "^2.3.2",
"decko": "^1.2.0",
"dompurify": "^3.2.4",
"eventemitter3": "^5.0.1",
"json-pointer": "^0.6.2",
"lunr": "^2.3.9",
"mark.js": "^8.11.1",
"marked": "^4.3.0",
"mobx-react": "^9.1.1",
"openapi-sampler": "^1.5.0",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.5",
"polished": "^4.2.2",
"prismjs": "^1.29.0",
"prop-types": "^15.8.1",
"react-tabs": "^6.0.2",
"slugify": "~1.4.7",
"stickyfill": "^1.1.1",
"swagger2openapi": "^7.0.8",
"url-template": "^2.0.8"
},
"size-limit": [
{
"path": "./bundles/redoc.standalone.js",
"limit": "350 kB"
},
{
"path": "./bundles/redoc.lib.js",
"limit": "100 kB"
},
{
"path": "./bundles/redoc.browser.lib.js",
"limit": "100 kB"
}
@observer
export class MenuItem extends React.Component<MenuItemProps> {
ref = React.createRef<HTMLLabelElement>();
activate = (evt: React.MouseEvent<HTMLElement>) => {
this.props.onActivate!(this.props.item);
evt.stopPropagation();
};
componentDidMount() {
this.scrollIntoViewIfActive();
],
"jest": {
"testEnvironment": "jsdom",
"setupFilesAfterEnv": [
"<rootDir>/src/setupTests.ts"
],
"preset": "ts-jest",
"collectCoverageFrom": [
"src/**/*.{ts,tsx}"
],
"coverageReporters": [
"json",
"lcov",
"text-summary"
],
"coveragePathIgnorePatterns": [
"\\.d\\.ts$",
"/benchmark/",
"/node_modules/",
"src/services/__tests__/models/helpers.ts"
],
"modulePathIgnorePatterns": [
"/benchmark/",
"src/services/__tests__/models/helpers.ts"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"moduleNameMapper": {
"\\.(css|less)$": "<rootDir>/src/empty.js"
}
componentDidUpdate() {
this.scrollIntoViewIfActive();
}
scrollIntoViewIfActive() {
if (this.props.item.active && this.ref.current) {
scrollIntoViewIfNeeded(this.ref.current);
},
"prettier": {
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "avoid"
}
}
render() {
const { item, withoutChildren } = this.props;
return (
<MenuItemLi
tabIndex={0}
onClick={this.activate}
onKeyDown={evt => {
// Space or Enter key will activate the menu item
if (evt.key === 'Enter' || evt.key === ' ') {
this.props.onActivate!(this.props.item);
evt.stopPropagation();
}
}}
depth={item.depth}
data-item-id={item.id}
role="menuitem"
aria-label={item.sidebarLabel}
aria-expanded={item.expanded}
>
{item.type === 'operation' ? (
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
) : (
<MenuItemLabel $depth={item.depth} $active={item.active} $type={item.type} ref={this.ref}>
{item.type === 'schema' && <OperationBadge type="schema">schema</OperationBadge>}
<MenuItemTitle width="calc(100% - 38px)" title={item.sidebarLabel}>
{item.sidebarLabel}
{this.props.children}
</MenuItemTitle>
{(item.depth > 0 && item.items.length > 0 && (
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
)) ||
null}
</MenuItemLabel>
)}
{!withoutChildren && item.items && item.items.length > 0 && (
<MenuItems
expanded={item.expanded}
items={item.items}
onActivate={this.props.onActivate}
/>
)}
</MenuItemLi>
);
}
}
export interface OperationMenuItemContentProps {
item: OperationModel;
children?: React.ReactChild;
}
export const OperationMenuItemContent = observer((props: OperationMenuItemContentProps) => {
const { item } = props;
const ref = React.createRef<HTMLLabelElement>();
const { showWebhookVerb } = React.useContext(OptionsContext);
React.useEffect(() => {
if (props.item.active && ref.current) {
scrollIntoViewIfNeeded(ref.current);
}
}, [props.item.active, ref]);
return (
<MenuItemLabel
$depth={item.depth}
$active={item.active}
$deprecated={item.deprecated}
ref={ref}
>
{item.badges &&
item.badges?.map(({ name, color }) => (
<OperationBadge type="badge" color={color} key={name}>
{name}
</OperationBadge>
))}
{item.isWebhook ? (
<OperationBadge type="hook">
{showWebhookVerb ? item.httpVerb : l('webhook')}
</OperationBadge>
) : (
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
)}
<MenuItemTitle tabIndex={0} width="calc(100% - 38px)">
{item.sidebarLabel}
{props.children}
</MenuItemTitle>
</MenuItemLabel>
);
});

View File

@ -7,8 +7,6 @@ import { OptionsContext } from '../OptionsProvider';
import { MenuItems } from './MenuItems';
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
import { RedocAttribution } from './styled.elements';
import RedoclyLogo from './Logo';
@observer
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
@ -27,12 +25,6 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
}}
>
<MenuItems items={store.items} onActivate={this.activate} root={true} />
<RedocAttribution>
<a target="_blank" rel="noopener noreferrer" href="https://redocly.com/redoc/">
<RedoclyLogo />
API docs by Redocly
</a>
</RedocAttribution>
</PerfectScrollbarWrap>
);
}

View File

@ -24,6 +24,7 @@ export const OperationBadge = styled.span.attrs((props: { type: string; color?:
vertical-align: middle;
margin-right: 6px;
margin-top: 2px;
margin-left: 6px;
&.get {
background-color: ${({ theme }) => theme.colors.http.get};
@ -87,6 +88,8 @@ function menuItemActive(
export const MenuItemUl = styled.ul<{ $expanded: boolean }>`
margin: 0;
padding: 0;
direction: rtl;
text-align: right;
&:first-child {
padding-bottom: 32px;

View File

@ -133,7 +133,7 @@ const defaultTheme: ThemeInterface = {
},
},
sidebar: {
width: '260px',
width: '300px',
backgroundColor: '#fafafa',
textColor: '#333333',
activeTextColor: theme =>