diff --git a/src/includer/generators/common.ts b/src/includer/generators/common.ts index 5fdcfd5..2b718ab 100644 --- a/src/includer/generators/common.ts +++ b/src/includer/generators/common.ts @@ -48,6 +48,10 @@ function code(text: string) { return EOL + ['```', text, '```'].join(EOL) + EOL; } +function method(text: string) { + return `${text.toUpperCase()} {.openapi__method}` +} + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ function table(data: any[][]) { const [names, ...rest] = data; @@ -93,6 +97,6 @@ function anchor(ref: string) { return link(ref, `#${slugify(ref).toLowerCase()}`); } -export {meta, list, link, title, body, mono, bold, table, code, cut, block, page, tabs, anchor}; +export {meta, list, link, title, body, mono, bold, table, code, cut, block, page, tabs, anchor, method}; -export default {meta, list, link, title, body, mono, bold, table, code, cut, block, tabs, anchor}; +export default {meta, list, link, title, body, mono, bold, table, code, cut, block, tabs, anchor, method}; diff --git a/src/includer/generators/endpoint.ts b/src/includer/generators/endpoint.ts index ab49685..0b070c6 100644 --- a/src/includer/generators/endpoint.ts +++ b/src/includer/generators/endpoint.ts @@ -1,7 +1,7 @@ import {JSONSchema6} from 'json-schema'; import stringify from 'json-stringify-safe'; -import {meta, page, block, title, body, table, code, cut, tabs, bold} from './common'; +import {meta, page, block, title, body, table, code, cut, tabs, bold, method} from './common'; import { INFO_TAB_NAME, SANDBOX_TAB_NAME, @@ -110,30 +110,36 @@ function sandbox({ } function request(data: Endpoint) { - const {path, method, servers} = data; - const requestTableCols = ['method', 'url']; + const {path, method: type, servers} = data; + let description: string | undefined; - const hrefs = block(servers.map(({url}) => code(url + '/' + path))); + const url = block(servers.map(({url}) => code(url + '/' + path))); - const requestTableRow = [code(method.toUpperCase()), hrefs]; + const requestTableRow = [method(type), `${url}`]; - if (servers.every((server: Server) => server.description)) { - requestTableCols.push('description'); - - const descriptions = block(servers.map(({description}) => code(description as string))); - requestTableRow.push(descriptions); + if (servers.every((server: Server) => server.description)) { + description = block(servers.map(({description}) => description)); } - const requestTable = table([ - requestTableCols, - requestTableRow, - ]); + const requestTable = block([ + '
', + `
`, + ...requestTableRow, + '
', + '
' + ]) - return block([ + const result = [ title(2)(REQUEST_SECTION_NAME), requestTable, - ]); + ] + + if (description) { + result.push(`${description}{.openapi__request__description}`) + } + + return block(result); } function parameters(allRefs: Refs, pagePrintedRefs: Set, params?: Parameters) { @@ -195,7 +201,7 @@ function openapiBody(allRefs: Refs, pagePrintedRefs: Set, obj?: Schema) const {type = 'schema', schema} = obj; const sectionTitle = title(4)('Body'); - let result = [ + let result: any[] = [ sectionTitle, ]; @@ -274,9 +280,11 @@ function response(allRefs: Refs, visited: Set, resp: Response) { } return block([ + `
`, title(2)(header), body(resp.description), resp.schemas?.length && block(resp.schemas.map((s) => openapiBody(allRefs, visited, s))), + '
' ]); } diff --git a/src/includer/models.ts b/src/includer/models.ts index 5a01317..342c8cd 100644 --- a/src/includer/models.ts +++ b/src/includer/models.ts @@ -156,7 +156,6 @@ export type Tag = { export type Endpoints = Endpoint[]; - export type Endpoint = { id: string; operationId?: string; @@ -172,6 +171,7 @@ export type Endpoint = { security: Security[]; noindex?: boolean; hidden?: boolean; + deprecated?: boolean; }; export type Specification = { diff --git a/src/runtime/index.scss b/src/runtime/index.scss index 0269f32..0881b7a 100644 --- a/src/runtime/index.scss +++ b/src/runtime/index.scss @@ -1,5 +1,108 @@ .openapi { + --dc-openapi-methods-post: rgba(50, 186, 118, 0.5); + --dc-openapi-methods-put: rgba(50, 186, 118, 0.3); + --dc-openapi-methods-get: rgba(54, 151, 241, 0.5); + --dc-openapi-methods-patch: rgba(255, 190, 92, 0.9); + --dc-openapi-methods-delete: rgba(255, 0, 61, 0.5); + + --dc-openapi-status-code-ok: rgb(48, 170, 110); + --dc-openapi-status-code-redirect: rgb(52, 139, 220); + --dc-openapi-status-code-client: rgb(233, 3, 58); + --dc-openapi-status-code-server: rgb(255, 190, 92); + + --dc-openapi-highlight: rgb(233, 174, 86); + &__required { color: var(--yc-color-text-danger); } + + & #request { + margin-top: 0; + } + + & .openapi__deprecated { + text-decoration: line-through; + } + + & &__request { + display: flex; + align-items: center; + overflow-y: scroll; + width: 100%; + + &__wrapper { + position: relative; + } + + & * { + margin: 0; + } + + & > .openapi__method { + background-color: var(--method); + padding: 8px 16px; + color: #fff; + border-radius: 5px 0 0 5px; + } + + & > .yfm-clipboard { + border: 2px solid var(--method); + border-radius: 0 5px 5px 0; + white-space: nowrap; + overflow: visible; + position: initial; + + code { + padding: 6px 20px 6px 6px; + } + + pre, code { + border-radius: 0; + overflow: visible; + } + + svg { + display: block; + opacity: 0; + transition: opacity 100ms; + position: absolute; + right: -8px; + top: 10px; + } + + &:hover svg { + opacity: 1; + } + } + + } + + & h3 { + transition: color 100ms; + + &.highlight { + color: var(--dc-openapi-highlight); + } + } + + & .openapi__request__description { + margin: 20px 0 0 0; + font-size: 1.1em; + } + + & h2[id^="2"] { + color: var(--dc-openapi-status-code-ok); + } + + & h2[id^="3"] { + color: var(--dc-openapi-status-code-redirect); + } + + & h2[id^="4"] { + color: var(--dc-openapi-status-code-client); + } + + & h2[id^="5"] { + color: var(--dc-openapi-status-code-server); + } } diff --git a/src/runtime/index.tsx b/src/runtime/index.tsx index 561ede0..315e864 100644 --- a/src/runtime/index.tsx +++ b/src/runtime/index.tsx @@ -1,14 +1,38 @@ -import React, {useEffect, useState} from 'react'; -import {createPortal} from 'react-dom'; -import {unescape} from 'html-escaper'; +import React, { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { unescape } from 'html-escaper'; -import {Sandbox} from './sandbox'; +import { Sandbox } from './sandbox'; import './index.scss'; export const Runtime: React.FC = () => { const [sandbox, setSandbox] = useState(null); + useEffect(() => { + document.addEventListener('click', (event: any) => { + if (!event?.target?.closest('.openapi')) { + return; + } + + const id = event?.target?.hash; + + if (!id) { + return; + } + + const anchor = document.querySelector(id); + + if (anchor.classList.contains('highlight')) { + return; + } + + anchor.classList.toggle('highlight'); + + setTimeout(() => anchor.classList.toggle('highlight'), 1_000) + }) + }, []); + useEffect(() => { setSandbox(document.querySelector('.yfm-sandbox-js')); }); @@ -20,7 +44,7 @@ export const Runtime: React.FC = () => { try { const props = JSON.parse(unescape(sandbox.dataset.props)); - return createPortal(, sandbox); + return createPortal(, sandbox); } catch (error) { console.log(error);