mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +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 React from 'react';
|
||||
|
||||
import { LoadingPane } from '@/components/primitives';
|
||||
import { fetchOpenAPIBlock } from '@/lib/openapi/fetch';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from '../Block';
|
||||
import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { Heading } from '../Heading';
|
||||
|
||||
import './style.css';
|
||||
import './scalar.css';
|
||||
@@ -54,6 +54,31 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
plus: <Icon icon="plus" />,
|
||||
},
|
||||
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',
|
||||
id: block.meta?.id,
|
||||
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 */
|
||||
.openapi-operation {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-height: 600px;
|
||||
@apply flex-1 flex flex-col gap-4 mb-14;
|
||||
}
|
||||
|
||||
@@ -18,16 +16,8 @@
|
||||
@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 {
|
||||
@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 {
|
||||
|
||||
@@ -15,31 +15,29 @@ import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
export function Text(props: { text: DocumentText }) {
|
||||
const { text } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{text.leaves.map((leaf, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{leaf.marks
|
||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||
.sort(
|
||||
(a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0),
|
||||
)
|
||||
.reduce<React.ReactNode>((children, mark, index) => {
|
||||
const Mark = MARK_STYLES[mark.type];
|
||||
return text.leaves.map((leaf, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{leaf.marks
|
||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||
.sort((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) {
|
||||
return children;
|
||||
}
|
||||
if (!Mark) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return <Mark mark={mark}>{children}</Mark>;
|
||||
}, leaf.text)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Mark key="mark" mark={mark}>
|
||||
{children}
|
||||
</Mark>
|
||||
);
|
||||
}, leaf.text)}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const MARK_STYLES = {
|
||||
|
||||
@@ -25,14 +25,17 @@ export function OpenAPIOperation(props: {
|
||||
blockKey: context.blockKey,
|
||||
};
|
||||
|
||||
const description = resolveDescription(operation)?.trim();
|
||||
const description = resolveDescription(operation);
|
||||
|
||||
return (
|
||||
<div className={clsx('openapi-operation', className)}>
|
||||
<div className="openapi-summary" id={context.id}>
|
||||
<h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
|
||||
{operation.summary}
|
||||
</h2>
|
||||
<div className="openapi-summary">
|
||||
{operation.summary
|
||||
? context.renderHeading({
|
||||
deprecated: operation.deprecated ?? false,
|
||||
title: operation.summary,
|
||||
})
|
||||
: null}
|
||||
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
||||
</div>
|
||||
<div className="openapi-columns">
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
|
||||
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
||||
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
|
||||
renderHeading: (props: { deprecated: boolean; title: string }) => React.ReactNode;
|
||||
|
||||
/** Spec url for the Scalar Api Client */
|
||||
specUrl: string;
|
||||
|
||||
@@ -16,9 +16,9 @@ export function createStateKey(key: string, scope?: string) {
|
||||
export function resolveDescription(object: AnyObject) {
|
||||
return 'x-gitbook-description-html' in object &&
|
||||
typeof object['x-gitbook-description-html'] === 'string'
|
||||
? object['x-gitbook-description-html']
|
||||
? object['x-gitbook-description-html'].trim()
|
||||
: typeof object.description === 'string'
|
||||
? object.description
|
||||
? object.description.trim()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user