mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Add support for anchor link in OpenAPI blocks (#2858)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
'gitbook': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for anchor links in OpenAPI blocks
|
||||||
@@ -3,12 +3,12 @@ import { Icon } from '@gitbook/icons';
|
|||||||
import { OpenAPIOperation } from '@gitbook/react-openapi';
|
import { OpenAPIOperation } from '@gitbook/react-openapi';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { LoadingPane } from '@/components/primitives';
|
|
||||||
import { fetchOpenAPIBlock } from '@/lib/openapi/fetch';
|
import { fetchOpenAPIBlock } from '@/lib/openapi/fetch';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
import { BlockProps } from '../Block';
|
import { BlockProps } from '../Block';
|
||||||
import { PlainCodeBlock } from '../CodeBlock';
|
import { PlainCodeBlock } from '../CodeBlock';
|
||||||
|
import { Heading } from '../Heading';
|
||||||
|
|
||||||
import './style.css';
|
import './style.css';
|
||||||
import './scalar.css';
|
import './scalar.css';
|
||||||
@@ -54,6 +54,31 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
|||||||
plus: <Icon icon="plus" />,
|
plus: <Icon icon="plus" />,
|
||||||
},
|
},
|
||||||
CodeBlock: PlainCodeBlock,
|
CodeBlock: PlainCodeBlock,
|
||||||
|
renderHeading: (headingProps) => (
|
||||||
|
<Heading
|
||||||
|
document={props.document}
|
||||||
|
ancestorBlocks={props.ancestorBlocks}
|
||||||
|
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
||||||
|
context={props.context}
|
||||||
|
style={headingProps.deprecated ? 'line-through' : undefined}
|
||||||
|
block={{
|
||||||
|
object: 'block',
|
||||||
|
key: `${block.key}-heading`,
|
||||||
|
meta: block.meta,
|
||||||
|
data: {},
|
||||||
|
type: 'heading-2',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
key: `${block.key}-heading-text`,
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{ text: headingProps.title, object: 'leaf', marks: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
defaultInteractiveOpened: context.mode === 'print',
|
defaultInteractiveOpened: context.mode === 'print',
|
||||||
id: block.meta?.id,
|
id: block.meta?.id,
|
||||||
blockKey: block.key,
|
blockKey: block.key,
|
||||||
@@ -62,32 +87,3 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OpenAPIFallback() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="status"
|
|
||||||
aria-busy
|
|
||||||
className={'openapi-block ' + tcls('flex', 'flex-1', 'flex-col', 'gap-3')}
|
|
||||||
>
|
|
||||||
<LoadingPane
|
|
||||||
tile={12}
|
|
||||||
style={['rounded-md', 'h-[47px]', '[max-width:calc(48rem-1px)]']}
|
|
||||||
/>
|
|
||||||
<LoadingPane
|
|
||||||
tile={12}
|
|
||||||
style={['rounded-md', 'h-[35px]', '[max-width:calc(48rem-1px)]']}
|
|
||||||
/>
|
|
||||||
<div className={tcls('flex', 'gap-[25px]')}>
|
|
||||||
<div className={tcls('flex', 'flex-1', 'flex-col', 'gap-3')}>
|
|
||||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[2.5/1]', 'w-full']} />
|
|
||||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[2.5/1]', 'w-full']} />
|
|
||||||
</div>
|
|
||||||
<div className={tcls('flex', 'flex-1', 'flex-col', 'gap-3')}>
|
|
||||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[4/1]', 'w-full']} />
|
|
||||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[4/1]', 'w-full']} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
/* Layout Components */
|
/* Layout Components */
|
||||||
.openapi-operation {
|
.openapi-operation {
|
||||||
content-visibility: auto;
|
|
||||||
contain-intrinsic-height: 600px;
|
|
||||||
@apply flex-1 flex flex-col gap-4 mb-14;
|
@apply flex-1 flex flex-col gap-4 mb-14;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,16 +16,8 @@
|
|||||||
@apply flex flex-col items-start justify-start gap-2;
|
@apply flex flex-col items-start justify-start gap-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openapi-summary-title {
|
|
||||||
@apply font-semibold text-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openapi-summary-title[data-deprecated='true'] {
|
|
||||||
@apply line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openapi-deprecated {
|
.openapi-deprecated {
|
||||||
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded-full text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openapi-deprecated-sunset-date {
|
.openapi-deprecated-sunset-date {
|
||||||
|
|||||||
@@ -15,31 +15,29 @@ import { ClassValue, tcls } from '@/lib/tailwind';
|
|||||||
export function Text(props: { text: DocumentText }) {
|
export function Text(props: { text: DocumentText }) {
|
||||||
const { text } = props;
|
const { text } = props;
|
||||||
|
|
||||||
return (
|
return text.leaves.map((leaf, index) => {
|
||||||
<>
|
return (
|
||||||
{text.leaves.map((leaf, index) => {
|
<React.Fragment key={index}>
|
||||||
return (
|
{leaf.marks
|
||||||
<React.Fragment key={index}>
|
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||||
{leaf.marks
|
.sort((a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0))
|
||||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
.reduce<React.ReactNode>((children, mark, index) => {
|
||||||
.sort(
|
const Mark = MARK_STYLES[mark.type];
|
||||||
(a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0),
|
|
||||||
)
|
|
||||||
.reduce<React.ReactNode>((children, mark, index) => {
|
|
||||||
const Mark = MARK_STYLES[mark.type];
|
|
||||||
|
|
||||||
if (!Mark) {
|
if (!Mark) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
return (
|
||||||
return <Mark mark={mark}>{children}</Mark>;
|
// @ts-ignore
|
||||||
}, leaf.text)}
|
<Mark key="mark" mark={mark}>
|
||||||
</React.Fragment>
|
{children}
|
||||||
);
|
</Mark>
|
||||||
})}
|
);
|
||||||
</>
|
}, leaf.text)}
|
||||||
);
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const MARK_STYLES = {
|
const MARK_STYLES = {
|
||||||
|
|||||||
@@ -25,14 +25,17 @@ export function OpenAPIOperation(props: {
|
|||||||
blockKey: context.blockKey,
|
blockKey: context.blockKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
const description = resolveDescription(operation)?.trim();
|
const description = resolveDescription(operation);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('openapi-operation', className)}>
|
<div className={clsx('openapi-operation', className)}>
|
||||||
<div className="openapi-summary" id={context.id}>
|
<div className="openapi-summary">
|
||||||
<h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
|
{operation.summary
|
||||||
{operation.summary}
|
? context.renderHeading({
|
||||||
</h2>
|
deprecated: operation.deprecated ?? false,
|
||||||
|
title: operation.summary,
|
||||||
|
})
|
||||||
|
: null}
|
||||||
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className="openapi-columns">
|
<div className="openapi-columns">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
|
|
||||||
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
||||||
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
|
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
|
||||||
|
renderHeading: (props: { deprecated: boolean; title: string }) => React.ReactNode;
|
||||||
|
|
||||||
/** Spec url for the Scalar Api Client */
|
/** Spec url for the Scalar Api Client */
|
||||||
specUrl: string;
|
specUrl: string;
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ export function createStateKey(key: string, scope?: string) {
|
|||||||
export function resolveDescription(object: AnyObject) {
|
export function resolveDescription(object: AnyObject) {
|
||||||
return 'x-gitbook-description-html' in object &&
|
return 'x-gitbook-description-html' in object &&
|
||||||
typeof object['x-gitbook-description-html'] === 'string'
|
typeof object['x-gitbook-description-html'] === 'string'
|
||||||
? object['x-gitbook-description-html']
|
? object['x-gitbook-description-html'].trim()
|
||||||
: typeof object.description === 'string'
|
: typeof object.description === 'string'
|
||||||
? object.description
|
? object.description.trim()
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user