mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-23 13:45:24 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c9e8580dd |
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"gitbook": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Answer Assistant questions with medium reasoning instead of low, matching AI search.
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"gitbook": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Mark blocks, pages and titles so comments can target them on change request previews.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"gitbook": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix the double slash in the example page URLs of the MCP server's getPage and sendFeedback tools.
|
|
||||||
@@ -62,7 +62,7 @@ export async function* streamAIChatResponse({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
model: AIModel.ReasoningLow,
|
model: AIModel.ReasoningMedium,
|
||||||
previousResponseId,
|
previousResponseId,
|
||||||
toolCall,
|
toolCall,
|
||||||
tools,
|
tools,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { DocumentInlineAnnotation } from '@gitbook/api';
|
|||||||
import { Blocks } from '../Blocks';
|
import { Blocks } from '../Blocks';
|
||||||
import type { InlineProps } from '../Inline';
|
import type { InlineProps } from '../Inline';
|
||||||
import { Inlines } from '../Inlines';
|
import { Inlines } from '../Inlines';
|
||||||
import { getCommentMarkerProps } from '../utils';
|
|
||||||
import { AnnotationPopover } from './AnnotationPopover';
|
import { AnnotationPopover } from './AnnotationPopover';
|
||||||
import { getNodeFragmentByType } from '@/lib/document';
|
import { getNodeFragmentByType } from '@/lib/document';
|
||||||
|
|
||||||
@@ -22,19 +21,13 @@ export function Annotation(props: InlineProps<DocumentInlineAnnotation>) {
|
|||||||
return (
|
return (
|
||||||
<AnnotationPopover
|
<AnnotationPopover
|
||||||
body={
|
body={
|
||||||
// The app doesn't allow comments inside annotation bodies.
|
<Blocks
|
||||||
<div
|
document={document}
|
||||||
{...getCommentMarkerProps(context, 'data-comment-ignore')}
|
ancestorBlocks={[]}
|
||||||
className="contents"
|
context={context}
|
||||||
>
|
nodes={fragment.nodes}
|
||||||
<Blocks
|
style={['contents']}
|
||||||
document={document}
|
/>
|
||||||
ancestorBlocks={[]}
|
|
||||||
context={context}
|
|
||||||
nodes={fragment.nodes}
|
|
||||||
style={['contents']}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
|
|
||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import { NotFoundRefHoverCard } from './NotFoundRefHoverCard';
|
import { NotFoundRefHoverCard } from './NotFoundRefHoverCard';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { Card, type CardProps } from '@/components/primitives';
|
import { Card, type CardProps } from '@/components/primitives';
|
||||||
import {
|
import {
|
||||||
type ResolvedContentRef,
|
type ResolvedContentRef,
|
||||||
@@ -32,7 +31,6 @@ export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>
|
|||||||
return (
|
return (
|
||||||
<NotFoundRefHoverCard context={context}>
|
<NotFoundRefHoverCard context={context}>
|
||||||
<BlockContentRefCard
|
<BlockContentRefCard
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
contentRef={block.data.ref}
|
contentRef={block.data.ref}
|
||||||
resolved={fallback}
|
resolved={fallback}
|
||||||
style={style}
|
style={style}
|
||||||
@@ -41,14 +39,7 @@ export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <BlockContentRefCard contentRef={block.data.ref} resolved={resolved} style={style} />;
|
||||||
<BlockContentRefCard
|
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
contentRef={block.data.ref}
|
|
||||||
resolved={resolved}
|
|
||||||
style={style}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function BlockContentRefCard(
|
function BlockContentRefCard(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type {
|
|||||||
|
|
||||||
import type { DocumentContextProps } from './DocumentView';
|
import type { DocumentContextProps } from './DocumentView';
|
||||||
import { Inlines } from './Inlines';
|
import { Inlines } from './Inlines';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { getNodeFragmentByName, isNodeEmpty } from '@/lib/document';
|
import { getNodeFragmentByName, isNodeEmpty } from '@/lib/document';
|
||||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
@@ -35,11 +34,6 @@ export function Caption(
|
|||||||
block: DocumentBlockImage | DocumentBlockDrawing | DocumentBlockEmbed | DocumentBlockFile;
|
block: DocumentBlockImage | DocumentBlockDrawing | DocumentBlockEmbed | DocumentBlockFile;
|
||||||
withBorder?: boolean;
|
withBorder?: boolean;
|
||||||
withFrame?: boolean;
|
withFrame?: boolean;
|
||||||
/**
|
|
||||||
* Expose the block key for commenting; off inside an `images` block, which carries the key
|
|
||||||
* itself since its images aren't comment targets.
|
|
||||||
*/
|
|
||||||
withBlockKey?: boolean;
|
|
||||||
} & DocumentContextProps
|
} & DocumentContextProps
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
@@ -65,11 +59,8 @@ export function Caption(
|
|||||||
],
|
],
|
||||||
style,
|
style,
|
||||||
align = 'center',
|
align = 'center',
|
||||||
withBlockKey = false,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const anchorProps = withBlockKey ? getCommentAnchorProps(context, block.key) : {};
|
|
||||||
|
|
||||||
const caption = getNodeFragmentByName(block, 'caption');
|
const caption = getNodeFragmentByName(block, 'caption');
|
||||||
const captionParagraph = caption?.nodes[0];
|
const captionParagraph = caption?.nodes[0];
|
||||||
|
|
||||||
@@ -78,15 +69,11 @@ export function Caption(
|
|||||||
captionParagraph.type !== 'paragraph' ||
|
captionParagraph.type !== 'paragraph' ||
|
||||||
isNodeEmpty(captionParagraph)
|
isNodeEmpty(captionParagraph)
|
||||||
) {
|
) {
|
||||||
return (
|
return <div className={tcls(style, wrapperStyle)}>{children}</div>;
|
||||||
<div {...anchorProps} className={tcls(style, wrapperStyle)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<picture {...anchorProps} className={tcls('relative', style)}>
|
<picture className={tcls('relative', style)}>
|
||||||
<div className={tcls(wrapperStyle, captionAlignStyles[align])}>{children}</div>
|
<div className={tcls(wrapperStyle, captionAlignStyles[align])}>{children}</div>
|
||||||
<figcaption
|
<figcaption
|
||||||
className={tcls(
|
className={tcls(
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ export type ClientBlockProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'st
|
|||||||
mode: BlockProps<DocumentBlockCode>['context']['mode'];
|
mode: BlockProps<DocumentBlockCode>['context']['mode'];
|
||||||
themes?: CustomizationThemedCodeTheme;
|
themes?: CustomizationThemedCodeTheme;
|
||||||
embedded?: boolean;
|
embedded?: boolean;
|
||||||
/** Node key for comments, set only where commenting is possible. */
|
|
||||||
commentKey?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10;
|
export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10;
|
||||||
@@ -34,8 +32,7 @@ export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10;
|
|||||||
* It allows us to defer some load to avoid blocking the rendering of the whole page with block highlighting.
|
* It allows us to defer some load to avoid blocking the rendering of the whole page with block highlighting.
|
||||||
*/
|
*/
|
||||||
export function ClientCodeBlock(props: ClientBlockProps) {
|
export function ClientCodeBlock(props: ClientBlockProps) {
|
||||||
const { block, mode, style, inlines, inlineExprVariables, themes, embedded, commentKey } =
|
const { block, mode, style, inlines, inlineExprVariables, themes, embedded } = props;
|
||||||
props;
|
|
||||||
const blockRef = useRef<HTMLDivElement>(null);
|
const blockRef = useRef<HTMLDivElement>(null);
|
||||||
const isInViewportRef = useRef(false);
|
const isInViewportRef = useRef(false);
|
||||||
const [isInViewport, setIsInViewport] = useState(false);
|
const [isInViewport, setIsInViewport] = useState(false);
|
||||||
@@ -148,7 +145,6 @@ export function ClientCodeBlock(props: ClientBlockProps) {
|
|||||||
id={codeBlockBodyId}
|
id={codeBlockBodyId}
|
||||||
isPrint={mode === 'print'}
|
isPrint={mode === 'print'}
|
||||||
embedded={embedded}
|
embedded={embedded}
|
||||||
commentKey={commentKey}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import type {
|
|||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { Blocks } from '../Blocks';
|
import { Blocks } from '../Blocks';
|
||||||
import { getCommentKeyProp, getCommentMarkerProps } from '../utils';
|
|
||||||
import { ClientCodeBlock } from './ClientCodeBlock';
|
import { ClientCodeBlock } from './ClientCodeBlock';
|
||||||
import { CodeBlockRenderer } from './CodeBlockRenderer';
|
import { CodeBlockRenderer } from './CodeBlockRenderer';
|
||||||
import { highlight } from './highlight';
|
import { highlight } from './highlight';
|
||||||
@@ -60,20 +59,14 @@ export async function CodeBlock(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
// The app doesn't allow comments inside annotation bodies.
|
<Blocks
|
||||||
<div
|
|
||||||
key={index}
|
key={index}
|
||||||
{...getCommentMarkerProps(context, 'data-comment-ignore')}
|
document={document}
|
||||||
className="contents"
|
ancestorBlocks={[]}
|
||||||
>
|
context={context}
|
||||||
<Blocks
|
nodes={fragment.nodes}
|
||||||
document={document}
|
style="space-y-4"
|
||||||
ancestorBlocks={[]}
|
/>
|
||||||
context={context}
|
|
||||||
nodes={fragment.nodes}
|
|
||||||
style="space-y-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -99,7 +92,6 @@ export async function CodeBlock(
|
|||||||
theme={theme}
|
theme={theme}
|
||||||
isPrint={context.mode === 'print'}
|
isPrint={context.mode === 'print'}
|
||||||
embedded={embedded}
|
embedded={embedded}
|
||||||
{...getCommentKeyProp(context, block.key)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -122,7 +114,6 @@ export async function CodeBlock(
|
|||||||
mode: context.mode,
|
mode: context.mode,
|
||||||
themes,
|
themes,
|
||||||
embedded,
|
embedded,
|
||||||
...getCommentKeyProp(context, block.key),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { DocumentBlockCode } from '@gitbook/api';
|
|||||||
|
|
||||||
import { AnnotationPopover } from '../Annotation/AnnotationPopover';
|
import { AnnotationPopover } from '../Annotation/AnnotationPopover';
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { getCommentKeyAttributes } from '../utils/commentAnchors';
|
|
||||||
import { AskAICodeButton } from './AskAICodeButton';
|
import { AskAICodeButton } from './AskAICodeButton';
|
||||||
import { CopyCodeButton } from './CopyCodeButton';
|
import { CopyCodeButton } from './CopyCodeButton';
|
||||||
import type { HighlightLine, HighlightTheme, HighlightToken } from './highlight-tokens';
|
import type { HighlightLine, HighlightTheme, HighlightToken } from './highlight-tokens';
|
||||||
@@ -13,8 +12,6 @@ import { tcls } from '@/lib/tailwind';
|
|||||||
|
|
||||||
type CodeBlockRendererProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
|
type CodeBlockRendererProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
|
||||||
theme: HighlightTheme;
|
theme: HighlightTheme;
|
||||||
/** Node key for comments, set only where commenting is possible. */
|
|
||||||
commentKey?: string;
|
|
||||||
'aria-busy'?: boolean;
|
'aria-busy'?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +29,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
|
|||||||
props: CodeBlockRendererProps,
|
props: CodeBlockRendererProps,
|
||||||
ref: React.ForwardedRef<HTMLDivElement>
|
ref: React.ForwardedRef<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
const { block, style, theme, 'aria-busy': ariaBusy, isPrint, embedded, commentKey } = props;
|
const { block, style, theme, 'aria-busy': ariaBusy, isPrint, embedded } = props;
|
||||||
|
|
||||||
const withLineNumbers = Boolean(block.data.lineNumbers) && block.nodes.length > 1;
|
const withLineNumbers = Boolean(block.data.lineNumbers) && block.nodes.length > 1;
|
||||||
const withWrap = block.data.overflow === 'wrap';
|
const withWrap = block.data.overflow === 'wrap';
|
||||||
@@ -53,7 +50,6 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={codeId}
|
id={codeId}
|
||||||
{...getCommentKeyAttributes(commentKey)}
|
|
||||||
aria-busy={ariaBusy}
|
aria-busy={ariaBusy}
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'group/codeblock shiki relative flex shrink flex-col overflow-hidden print:overflow-visible',
|
'group/codeblock shiki relative flex shrink flex-col overflow-hidden print:overflow-visible',
|
||||||
|
|||||||
@@ -218,12 +218,7 @@ export function MermaidCodeBlock(
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Inline slot: hosts the diagram in the document flow until it goes fullscreen. */}
|
{/* Inline slot: hosts the diagram in the document flow until it goes fullscreen. */}
|
||||||
<div
|
<div ref={rootRef} className={tcls('relative', style)} contentEditable={false} />
|
||||||
ref={rootRef}
|
|
||||||
data-block-key={props.commentKey}
|
|
||||||
className={tcls('relative', style)}
|
|
||||||
contentEditable={false}
|
|
||||||
/>
|
|
||||||
{diagramHostRef.current ? createPortal(diagram, diagramHostRef.current) : null}
|
{diagramHostRef.current ? createPortal(diagram, diagramHostRef.current) : null}
|
||||||
<Dialog.Root
|
<Dialog.Root
|
||||||
open={isFullscreen}
|
open={isFullscreen}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { type DocumentBlockColumns, type Length, VerticalAlignment } from '@gitb
|
|||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { Blocks } from '../Blocks';
|
import { Blocks } from '../Blocks';
|
||||||
import { getCommentMarkerProps } from '../utils';
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
export function Columns(props: BlockProps<DocumentBlockColumns>) {
|
export function Columns(props: BlockProps<DocumentBlockColumns>) {
|
||||||
@@ -38,8 +37,6 @@ export function Columns(props: BlockProps<DocumentBlockColumns>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
// The app doesn't allow comments inside columns.
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-ignore')}
|
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'grid w-full @2xl:grid-flow-col @2xl:grid-cols-[repeat(var(--grid-slices),minmax(0,1fr))] grid-cols-1 @2xl:grid-rows-1 gap-x-8 gap-y-4',
|
'grid w-full @2xl:grid-flow-col @2xl:grid-cols-[repeat(var(--grid-slices),minmax(0,1fr))] grid-cols-1 @2xl:grid-rows-1 gap-x-8 gap-y-4',
|
||||||
style
|
style
|
||||||
|
|||||||
@@ -37,12 +37,6 @@ export interface DocumentContext {
|
|||||||
*/
|
*/
|
||||||
withLinkPreviews?: boolean;
|
withLinkPreviews?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the markers comments anchor to (block keys, table cells). Only where commenting is
|
|
||||||
* possible, so public pages don't carry them.
|
|
||||||
*/
|
|
||||||
withCommentAnchors?: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when this document is the main page body — the only place a background page cover is
|
* True when this document is the main page body — the only place a background page cover is
|
||||||
* shown behind the content. Cover-aware text styling (contrast over the cover) is scoped to it
|
* shown behind the content. Cover-aware text styling (contrast over the cover) is scoped to it
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export async function Drawing(props: BlockProps<DocumentBlockDrawing>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Caption {...props} withBlockKey>
|
<Caption {...props}>
|
||||||
<Image
|
<Image
|
||||||
sources={{
|
sources={{
|
||||||
light: {
|
light: {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Caption {...props} withBorder withBlockKey>
|
<Caption {...props} withBorder>
|
||||||
{embed.type === 'rich' ? (
|
{embed.type === 'rich' ? (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export function Details(props: {
|
|||||||
contentIds?: string[];
|
contentIds?: string[];
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
className?: ClassValue;
|
className?: ClassValue;
|
||||||
/** Node key of the expandable, so comments can anchor to it. */
|
|
||||||
commentKey?: string;
|
|
||||||
}) {
|
}) {
|
||||||
const { children, id, className } = props;
|
const { children, id, className } = props;
|
||||||
|
|
||||||
@@ -48,7 +46,6 @@ export function Details(props: {
|
|||||||
<details
|
<details
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={id}
|
id={id}
|
||||||
data-block-key={props.commentKey}
|
|
||||||
open={props.open || openFromHash}
|
open={props.open || openFromHash}
|
||||||
className={tcls(
|
className={tcls(
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { BlockProps } from '../Block';
|
|||||||
import { Blocks } from '../Blocks';
|
import { Blocks } from '../Blocks';
|
||||||
import { shouldShowHashLinks } from '../HashLinkButton';
|
import { shouldShowHashLinks } from '../HashLinkButton';
|
||||||
import { Inlines } from '../Inlines';
|
import { Inlines } from '../Inlines';
|
||||||
import { getCommentAnchorProps, getCommentKeyProp } from '../utils';
|
|
||||||
import { Details } from './Details';
|
import { Details } from './Details';
|
||||||
import { ToggleChevron } from '@/components/primitives';
|
import { ToggleChevron } from '@/components/primitives';
|
||||||
import { getSpaceLanguage, tString } from '@/intl/server';
|
import { getSpaceLanguage, tString } from '@/intl/server';
|
||||||
@@ -38,12 +37,10 @@ export async function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
|||||||
return (
|
return (
|
||||||
<Details
|
<Details
|
||||||
id={id}
|
id={id}
|
||||||
{...getCommentKeyProp(context, block.key)}
|
|
||||||
open={context.mode === 'print' || isDefaultExpanded}
|
open={context.mode === 'print' || isDefaultExpanded}
|
||||||
className={tcls('scroll-mt-(--content-scroll-margin)', style)}
|
className={tcls('scroll-mt-(--content-scroll-margin)', style)}
|
||||||
>
|
>
|
||||||
<summary
|
<summary
|
||||||
{...getCommentAnchorProps(context, titleParagraph.key)}
|
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
'px-4',
|
'px-4',
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export async function File(props: BlockProps<DocumentBlockFile>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Caption {...props} withBorder withBlockKey>
|
<Caption {...props} withBorder>
|
||||||
<div className="flex flex-wrap items-center gap-5 px-5 py-3">
|
<div className="flex flex-wrap items-center gap-5 px-5 py-3">
|
||||||
<div className="flex min-h-8 min-w-14 flex-col items-center justify-center gap-1 border-r border-tint-subtle pr-4">
|
<div className="flex min-h-8 min-w-14 flex-col items-center justify-center gap-1 border-r border-tint-subtle pr-4">
|
||||||
{contentType === 'image' ? (
|
{contentType === 'image' ? (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { HashLinkButton, hashLinkButtonWrapperStyles, shouldShowHashLinks } from
|
|||||||
import { HeadingRevealWrapper } from './HeadingRevealWrapper';
|
import { HeadingRevealWrapper } from './HeadingRevealWrapper';
|
||||||
import { Inlines } from './Inlines';
|
import { Inlines } from './Inlines';
|
||||||
import { getBlockTextStyle } from './spacing';
|
import { getBlockTextStyle } from './spacing';
|
||||||
import { getCommentAnchorProps, getTextAlignment } from './utils';
|
import { getTextAlignment } from './utils';
|
||||||
import { getSpaceLanguage, tString } from '@/intl/server';
|
import { getSpaceLanguage, tString } from '@/intl/server';
|
||||||
import { defaultLanguage } from '@/intl/translations';
|
import { defaultLanguage } from '@/intl/translations';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
@@ -30,7 +30,6 @@ export async function Heading(props: BlockProps<DocumentBlockHeading>) {
|
|||||||
<HeadingRevealWrapper
|
<HeadingRevealWrapper
|
||||||
as={Tag}
|
as={Tag}
|
||||||
id={id}
|
id={id}
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
className={tcls(
|
className={tcls(
|
||||||
textStyle.textSize,
|
textStyle.textSize,
|
||||||
'heading',
|
'heading',
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { DocumentBlockImage, DocumentBlockImages, JSONDocument, Length } fr
|
|||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import { Caption, type CaptionAlign } from './Caption';
|
import { Caption, type CaptionAlign } from './Caption';
|
||||||
import type { DocumentContext } from './DocumentView';
|
import type { DocumentContext } from './DocumentView';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { Image, type ImageResponsiveSize } from '@/components/utils';
|
import { Image, type ImageResponsiveSize } from '@/components/utils';
|
||||||
import { resolveContentRefInDocument } from '@/lib/references';
|
import { resolveContentRefInDocument } from '@/lib/references';
|
||||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||||
@@ -16,7 +15,6 @@ export function Images(props: BlockProps<DocumentBlockImages>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
className={tcls(
|
className={tcls(
|
||||||
style,
|
style,
|
||||||
'flex',
|
'flex',
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import type { DocumentBlockIntegration, RenderIntegrationUI } from '@gitbook/api
|
|||||||
import { ContentKitOutput } from '@gitbook/react-contentkit';
|
import { ContentKitOutput } from '@gitbook/react-contentkit';
|
||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { getCommentAnchorProps, getCommentMarkerProps } from '../utils';
|
|
||||||
import { getWebframePageContext } from './adaptive';
|
import { getWebframePageContext } from './adaptive';
|
||||||
import './contentkit.css';
|
|
||||||
import { contentKitServerContext } from './contentkit';
|
import { contentKitServerContext } from './contentkit';
|
||||||
|
import './contentkit.css';
|
||||||
import {
|
import {
|
||||||
ContentKitWithClientContext,
|
ContentKitWithClientContext,
|
||||||
type WebframeLinkerData,
|
type WebframeLinkerData,
|
||||||
@@ -62,12 +61,7 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tcls(style)}>
|
||||||
// Integration output can render blocks with generated keys.
|
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-block')}
|
|
||||||
className={tcls(style)}
|
|
||||||
>
|
|
||||||
<pre>
|
<pre>
|
||||||
Unexpected error with integration {block.data.integration}:{' '}
|
Unexpected error with integration {block.data.integration}:{' '}
|
||||||
{initialResponse.error.message}
|
{initialResponse.error.message}
|
||||||
@@ -105,12 +99,7 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tcls(style)}>
|
||||||
// Integration output can render blocks with generated keys.
|
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-block')}
|
|
||||||
className={tcls(style)}
|
|
||||||
>
|
|
||||||
<ContentKitWithClientContext
|
<ContentKitWithClientContext
|
||||||
{...contentKitProps}
|
{...contentKitProps}
|
||||||
canAccessVisitorClaims={canAccessVisitorClaims}
|
canAccessVisitorClaims={canAccessVisitorClaims}
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ import type {
|
|||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import { Blocks } from './Blocks';
|
import { Blocks } from './Blocks';
|
||||||
import { getBlockTextStyle } from './spacing';
|
import { getBlockTextStyle } from './spacing';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { Checkbox } from '@/components/primitives';
|
import { Checkbox } from '@/components/primitives';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
||||||
const { block, ancestorBlocks, ...contextProps } = props;
|
const { block, ancestorBlocks, ...contextProps } = props;
|
||||||
const { context } = contextProps;
|
|
||||||
|
|
||||||
const parent = ancestorBlocks[ancestorBlocks.length - 1];
|
const parent = ancestorBlocks[ancestorBlocks.length - 1];
|
||||||
assert(
|
assert(
|
||||||
@@ -48,7 +46,7 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
|||||||
switch (parent.type) {
|
switch (parent.type) {
|
||||||
case 'list-tasks':
|
case 'list-tasks':
|
||||||
return (
|
return (
|
||||||
<ListItemLI block={block} anchorProps={getCommentAnchorProps(context, block.key)}>
|
<ListItemLI block={block}>
|
||||||
<ListItemPrefix block={block}>
|
<ListItemPrefix block={block}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={block.key!}
|
id={block.key!}
|
||||||
@@ -66,7 +64,7 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
|||||||
);
|
);
|
||||||
case 'list-ordered':
|
case 'list-ordered':
|
||||||
return (
|
return (
|
||||||
<ListItemLI block={block} anchorProps={getCommentAnchorProps(context, block.key)}>
|
<ListItemLI block={block}>
|
||||||
<ListItemPrefix block={block}>
|
<ListItemPrefix block={block}>
|
||||||
<PseudoBefore
|
<PseudoBefore
|
||||||
content={getOrderedListItemPrefixContent({
|
content={getOrderedListItemPrefixContent({
|
||||||
@@ -84,7 +82,7 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
|||||||
);
|
);
|
||||||
case 'list-unordered':
|
case 'list-unordered':
|
||||||
return (
|
return (
|
||||||
<ListItemLI block={block} anchorProps={getCommentAnchorProps(context, block.key)}>
|
<ListItemLI block={block}>
|
||||||
<ListItemPrefix block={block}>
|
<ListItemPrefix block={block}>
|
||||||
<PseudoBefore
|
<PseudoBefore
|
||||||
content={getUnorderedListItemsPrefixContent({
|
content={getUnorderedListItemsPrefixContent({
|
||||||
@@ -125,17 +123,9 @@ function getListItemDepth(input: {
|
|||||||
return depth;
|
return depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListItemLI(props: {
|
function ListItemLI(props: { block: DocumentBlockListItem; children: React.ReactNode }) {
|
||||||
block: DocumentBlockListItem;
|
|
||||||
anchorProps: ReturnType<typeof getCommentAnchorProps>;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const textStyle = getBlockTextStyle(props.block);
|
const textStyle = getBlockTextStyle(props.block);
|
||||||
return (
|
return <li className={tcls(textStyle.lineHeight, 'flex items-start')}>{props.children}</li>;
|
||||||
<li {...props.anchorProps} className={tcls(textStyle.lineHeight, 'flex items-start')}>
|
|
||||||
{props.children}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListItemPrefix(props: { block: DocumentBlockListItem; children: React.ReactNode }) {
|
function ListItemPrefix(props: { block: DocumentBlockListItem; children: React.ReactNode }) {
|
||||||
|
|||||||
@@ -3,25 +3,21 @@ import { MathFormula } from '@gitbook/react-math';
|
|||||||
|
|
||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import type { InlineProps } from './Inline';
|
import type { InlineProps } from './Inline';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { getAssetURL } from '@/lib/assets';
|
import { getAssetURL } from '@/lib/assets';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
const assetsUrl = getAssetURL('math');
|
const assetsUrl = getAssetURL('math');
|
||||||
|
|
||||||
export async function BlockMath(props: BlockProps<DocumentBlockMath>) {
|
export async function BlockMath(props: BlockProps<DocumentBlockMath>) {
|
||||||
const { block, style, context } = props;
|
const { block, style } = props;
|
||||||
|
|
||||||
// `MathFormula` only forwards `className`, so the comment anchor needs its own wrapper.
|
|
||||||
return (
|
return (
|
||||||
<div {...getCommentAnchorProps(context, block.key)} className={tcls(style)}>
|
<MathFormula
|
||||||
<MathFormula
|
formula={block.data.formula}
|
||||||
formula={block.data.formula}
|
inline={false}
|
||||||
inline={false}
|
className={tcls(style, 'overflow-x-auto')}
|
||||||
className="overflow-x-auto"
|
assetsUrl={assetsUrl}
|
||||||
assetsUrl={assetsUrl}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { getCommentAnchorProps, getCommentMarkerProps } from '../utils';
|
|
||||||
import { getOpenAPIBlockClientProps } from './context';
|
import { getOpenAPIBlockClientProps } from './context';
|
||||||
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
||||||
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
|
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
|
||||||
@@ -10,14 +9,9 @@ import { tcls } from '@/lib/tailwind';
|
|||||||
* Render an openapi block or an openapi-operation block.
|
* Render an openapi block or an openapi-operation block.
|
||||||
*/
|
*/
|
||||||
export async function OpenAPIOperation(props: BlockProps<AnyOpenAPIOperationsBlock>) {
|
export async function OpenAPIOperation(props: BlockProps<AnyOpenAPIOperationsBlock>) {
|
||||||
const { style, block, context } = props;
|
const { style } = props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
// Its generated headings and descriptions carry keys that aren't page nodes.
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-block')}
|
|
||||||
className={tcls('flex w-full min-w-0', style, 'max-w-full')}
|
|
||||||
>
|
|
||||||
<OpenAPIOperationBody {...props} />
|
<OpenAPIOperationBody {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { getCommentAnchorProps, getCommentMarkerProps } from '../utils';
|
|
||||||
import { getOpenAPIBlockClientProps } from './context';
|
import { getOpenAPIBlockClientProps } from './context';
|
||||||
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
||||||
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
|
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
|
||||||
@@ -10,14 +9,9 @@ import { tcls } from '@/lib/tailwind';
|
|||||||
* Render an openapi-schemas block.
|
* Render an openapi-schemas block.
|
||||||
*/
|
*/
|
||||||
export async function OpenAPISchemas(props: BlockProps<OpenAPISchemasBlock>) {
|
export async function OpenAPISchemas(props: BlockProps<OpenAPISchemasBlock>) {
|
||||||
const { style, block, context } = props;
|
const { style } = props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tcls('flex w-full', style, 'max-w-full')}>
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
// Its generated headings and descriptions carry keys that aren't page nodes.
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-block')}
|
|
||||||
className={tcls('flex w-full', style, 'max-w-full')}
|
|
||||||
>
|
|
||||||
<OpenAPISchemasBody {...props} />
|
<OpenAPISchemasBody {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { getCommentAnchorProps, getCommentMarkerProps } from '../utils';
|
|
||||||
import { getOpenAPIBlockClientProps } from './context';
|
import { getOpenAPIBlockClientProps } from './context';
|
||||||
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
import { OpenAPIBlockLazy } from './OpenAPIBlockLazy';
|
||||||
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
|
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
|
||||||
@@ -10,14 +9,9 @@ import { tcls } from '@/lib/tailwind';
|
|||||||
* Render an openapi block or an openapi-webhook block.
|
* Render an openapi block or an openapi-webhook block.
|
||||||
*/
|
*/
|
||||||
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
|
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
|
||||||
const { style, block, context } = props;
|
const { style } = props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
// Its generated headings and descriptions carry keys that aren't page nodes.
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-block')}
|
|
||||||
className={tcls('flex w-full min-w-0', style, 'max-w-full')}
|
|
||||||
>
|
|
||||||
<OpenAPIWebhookBody {...props} />
|
<OpenAPIWebhookBody {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,19 +4,15 @@ import { CustomizationAIMode } from '@gitbook/api';
|
|||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import { Inlines } from './Inlines';
|
import { Inlines } from './Inlines';
|
||||||
import { getTextAlignment } from './utils';
|
import { getTextAlignment } from './utils';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { getNodeText } from '@/lib/document';
|
import { getNodeText } from '@/lib/document';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
export function Paragraph(props: BlockProps<DocumentBlockParagraph>) {
|
export function Paragraph(props: BlockProps<DocumentBlockParagraph>) {
|
||||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||||
const { context } = contextProps;
|
const { context } = contextProps;
|
||||||
// Like the app, a list item owns the comments on its direct paragraph.
|
|
||||||
const isCommentTarget = ancestorBlocks.at(-1)?.type !== 'list-item';
|
|
||||||
|
|
||||||
const paragraph = (
|
const paragraph = (
|
||||||
<p
|
<p
|
||||||
{...(isCommentTarget ? getCommentAnchorProps(context, block.key) : {})}
|
|
||||||
// Cover-aware contrast text applies only to the page body, not to documents
|
// Cover-aware contrast text applies only to the page body, not to documents
|
||||||
// rendered in overlays (search answers, AI chat) on a background-cover page.
|
// rendered in overlays (search answers, AI chat) on a background-cover page.
|
||||||
data-cover-aware-text={context.isPageBody ? '' : undefined}
|
data-cover-aware-text={context.isPageBody ? '' : undefined}
|
||||||
|
|||||||
@@ -9,14 +9,12 @@ import { validateIconName } from '@gitbook/icons/icons';
|
|||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { CodeBlock } from '../CodeBlock';
|
import { CodeBlock } from '../CodeBlock';
|
||||||
import { getPlainCodeBlock } from '../CodeBlock/highlight-tokens';
|
import { getPlainCodeBlock } from '../CodeBlock/highlight-tokens';
|
||||||
import { COMMENT_IGNORE_CLASSNAME, getCommentKeyProp } from '../utils';
|
|
||||||
import { PromptClient } from './PromptClient';
|
import { PromptClient } from './PromptClient';
|
||||||
import { tcls } from '@/lib/tailwind';
|
|
||||||
|
|
||||||
const PROMPT_COLLAPSED_LINE_COUNT = 10;
|
const PROMPT_COLLAPSED_LINE_COUNT = 10;
|
||||||
|
|
||||||
export function Prompt(props: BlockProps<DocumentBlockPrompt>) {
|
export function Prompt(props: BlockProps<DocumentBlockPrompt>) {
|
||||||
const { block, document, ancestorBlocks, context, isEstimatedOffscreen, style } = props;
|
const { block, document, ancestorBlocks, context, isEstimatedOffscreen } = props;
|
||||||
const contentIcon =
|
const contentIcon =
|
||||||
block.data.icon && validateIconName(block.data.icon) ? block.data.icon : null;
|
block.data.icon && validateIconName(block.data.icon) ? block.data.icon : null;
|
||||||
const prompt = getPromptText(block);
|
const prompt = getPromptText(block);
|
||||||
@@ -41,13 +39,6 @@ export function Prompt(props: BlockProps<DocumentBlockPrompt>) {
|
|||||||
defaultExpanded={defaultExpanded}
|
defaultExpanded={defaultExpanded}
|
||||||
openInAIProviders={getOpenInAIProviders(props)}
|
openInAIProviders={getOpenInAIProviders(props)}
|
||||||
prompt={prompt}
|
prompt={prompt}
|
||||||
{...getCommentKeyProp(context, block.key)}
|
|
||||||
// Only the comment marker: the prompt keeps its own width rather than the block styles.
|
|
||||||
className={
|
|
||||||
tcls(style).split(' ').includes(COMMENT_IGNORE_CLASSNAME)
|
|
||||||
? COMMENT_IGNORE_CLASSNAME
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{promptContent}
|
{promptContent}
|
||||||
</PromptClient>
|
</PromptClient>
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ import { tcls } from '@/lib/tailwind';
|
|||||||
type PromptClientProps = DocumentBlockPrompt['data'] & {
|
type PromptClientProps = DocumentBlockPrompt['data'] & {
|
||||||
contentIcon: IconName | null;
|
contentIcon: IconName | null;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
/** Comment target: its code nodes aren't, so the prompt owns clicks inside it. */
|
|
||||||
commentKey?: string;
|
|
||||||
className?: string;
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,8 +23,6 @@ export function PromptClient(props: PromptClientProps) {
|
|||||||
openInAIProviders = false,
|
openInAIProviders = false,
|
||||||
defaultExpanded = 'hidden',
|
defaultExpanded = 'hidden',
|
||||||
prompt,
|
prompt,
|
||||||
commentKey,
|
|
||||||
className,
|
|
||||||
children,
|
children,
|
||||||
} = props;
|
} = props;
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
@@ -42,14 +37,11 @@ export function PromptClient(props: PromptClientProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-block-key={commentKey}
|
|
||||||
data-comment-block={commentKey ? '' : undefined}
|
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'relative flex w-full flex-col overflow-hidden circular-corners:rounded-2xl rounded-corners:rounded-xl straight-corners:rounded-xs text-tint-strong',
|
'relative flex w-full flex-col overflow-hidden circular-corners:rounded-2xl rounded-corners:rounded-xl straight-corners:rounded-xs text-tint-strong',
|
||||||
'border border-tint-subtle contrast-more:border-tint',
|
'border border-tint-subtle contrast-more:border-tint',
|
||||||
'transition',
|
'transition',
|
||||||
expanded ? 'bg-tint depth-subtle:shadow-xs' : 'bg-tint-base',
|
expanded ? 'bg-tint depth-subtle:shadow-xs' : 'bg-tint-base'
|
||||||
className
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { DocumentBlockReusableContent } from '@gitbook/api';
|
|||||||
|
|
||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import { UnwrappedBlocks } from './Blocks';
|
import { UnwrappedBlocks } from './Blocks';
|
||||||
import { COMMENT_IGNORE_CLASSNAME } from './utils';
|
|
||||||
import { getDataOrNull } from '@/lib/data';
|
import { getDataOrNull } from '@/lib/data';
|
||||||
import { resolveContentRefInDocument } from '@/lib/references';
|
import { resolveContentRefInDocument } from '@/lib/references';
|
||||||
|
|
||||||
@@ -51,9 +50,6 @@ export async function ReusableContent(props: BlockProps<DocumentBlockReusableCon
|
|||||||
nodes={reusableContentDocument.nodes}
|
nodes={reusableContentDocument.nodes}
|
||||||
document={reusableContentDocument}
|
document={reusableContentDocument}
|
||||||
ancestorBlocks={[...ancestorBlocks, block]}
|
ancestorBlocks={[...ancestorBlocks, block]}
|
||||||
// Its blocks carry keys of the reusable content's own document, which comments on the
|
|
||||||
// page can't anchor to. A class, not a wrapper, which would break the page's spacing.
|
|
||||||
blockStyle={context.withCommentAnchors ? COMMENT_IGNORE_CLASSNAME : undefined}
|
|
||||||
context={{
|
context={{
|
||||||
...context,
|
...context,
|
||||||
contentContext: reusableContent.context,
|
contentContext: reusableContent.context,
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import type { DocumentBlockStepperStep } from '@gitbook/api';
|
|||||||
|
|
||||||
import type { BlockProps } from './Block';
|
import type { BlockProps } from './Block';
|
||||||
import { Blocks } from './Blocks';
|
import { Blocks } from './Blocks';
|
||||||
import { getCommentAnchorProps } from './utils';
|
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
|
export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
|
||||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||||
const { context } = contextProps;
|
|
||||||
|
|
||||||
const ancestor = ancestorBlocks[ancestorBlocks.length - 1];
|
const ancestor = ancestorBlocks[ancestorBlocks.length - 1];
|
||||||
assert(ancestor?.type === 'stepper', 'Ancestor block must be a stepper');
|
assert(ancestor?.type === 'stepper', 'Ancestor block must be a stepper');
|
||||||
@@ -34,10 +32,7 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tcls('flex flex-row gap-4 md:gap-8', style)}>
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
className={tcls('flex flex-row gap-4 md:gap-8', style)}
|
|
||||||
>
|
|
||||||
<div className="relative select-none">
|
<div className="relative select-none">
|
||||||
<div
|
<div
|
||||||
className={tcls(
|
className={tcls(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getCommentCellProps } from '../utils';
|
|
||||||
import { getTableCellMerge } from './cellMerges';
|
import { getTableCellMerge } from './cellMerges';
|
||||||
import { getColumnWidth, getViewGridLayout, hasVisibleHeader } from './layout';
|
import { getColumnWidth, getViewGridLayout, hasVisibleHeader } from './layout';
|
||||||
import { RecordColumnValue } from './RecordColumnValue';
|
import { RecordColumnValue } from './RecordColumnValue';
|
||||||
@@ -6,7 +5,7 @@ import { StickyViewGrid } from './StickyViewGrid';
|
|||||||
import type { TableGridViewProps } from './Table';
|
import type { TableGridViewProps } from './Table';
|
||||||
import { TableHoverTable } from './TableHoverTable';
|
import { TableHoverTable } from './TableHoverTable';
|
||||||
import { TableSearchTableBody } from './TableSearch';
|
import { TableSearchTableBody } from './TableSearch';
|
||||||
import { getColumnAlignment, getColumnVerticalAlignment, type VerticalAlignment } from './utils';
|
import { type VerticalAlignment, getColumnAlignment, getColumnVerticalAlignment } from './utils';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
/** Semantic table renderer used when a cell spans multiple records. */
|
/** Semantic table renderer used when a cell spans multiple records. */
|
||||||
@@ -142,7 +141,7 @@ function NativeViewGridBody(
|
|||||||
stickyFirstColumn: boolean;
|
stickyFirstColumn: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { block, view, records, withHeader, stickyFirstColumn, cellMergeLayout, context } = props;
|
const { block, view, records, withHeader, stickyFirstColumn, cellMergeLayout } = props;
|
||||||
const firstVisibleColumn = view.columns[0];
|
const firstVisibleColumn = view.columns[0];
|
||||||
const lastVisibleColumn = view.columns.at(-1);
|
const lastVisibleColumn = view.columns.at(-1);
|
||||||
const recordsById = new Map(records.map((record) => [record[0], record] as const));
|
const recordsById = new Map(records.map((record) => [record[0], record] as const));
|
||||||
@@ -220,7 +219,6 @@ function NativeViewGridBody(
|
|||||||
colSpan={colSpan}
|
colSpan={colSpan}
|
||||||
aria-rowspan={rowSpan}
|
aria-rowspan={rowSpan}
|
||||||
aria-colspan={colSpan}
|
aria-colspan={colSpan}
|
||||||
{...getCommentCellProps(context, recordId, column)}
|
|
||||||
data-table-row-start={recordIndex}
|
data-table-row-start={recordIndex}
|
||||||
data-table-row-end={
|
data-table-row-end={
|
||||||
recordIndex + (cellMerge?.merge.rowSpan ?? 1) - 1
|
recordIndex + (cellMerge?.merge.rowSpan ?? 1) - 1
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getCommentCellProps } from '../utils';
|
|
||||||
import { getMergedCellWidth, getTableCellMerge } from './cellMerges';
|
import { getMergedCellWidth, getTableCellMerge } from './cellMerges';
|
||||||
import { getColumnWidth } from './layout';
|
import { getColumnWidth } from './layout';
|
||||||
import { RecordColumnValue } from './RecordColumnValue';
|
import { RecordColumnValue } from './RecordColumnValue';
|
||||||
@@ -55,7 +54,6 @@ export function RecordRow(
|
|||||||
<div
|
<div
|
||||||
key={column}
|
key={column}
|
||||||
role="cell"
|
role="cell"
|
||||||
{...getCommentCellProps(context, record[0], column)}
|
|
||||||
aria-colspan={cellMerge?.merge.colSpan}
|
aria-colspan={cellMerge?.merge.colSpan}
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'relative flex flex-1 border-r px-3 py-2 align-middle text-sm last:border-r-0',
|
'relative flex flex-1 border-r px-3 py-2 align-middle text-sm last:border-r-0',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import assertNever from 'assert-never';
|
|||||||
import type { DocumentBlockTable, DocumentTableViewGrid } from '@gitbook/api';
|
import type { DocumentBlockTable, DocumentTableViewGrid } from '@gitbook/api';
|
||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { getCommentAnchorProps, getCommentMarkerProps, isBlockOffscreen } from '../utils';
|
import { isBlockOffscreen } from '../utils';
|
||||||
import { type TableCellMergeLayout, createTableCellMergeLayout } from './cellMerges';
|
import { type TableCellMergeLayout, createTableCellMergeLayout } from './cellMerges';
|
||||||
import { getViewGridLayout, hasVisibleHeader } from './layout';
|
import { getViewGridLayout, hasVisibleHeader } from './layout';
|
||||||
import { NativeViewGrid } from './NativeViewGrid';
|
import { NativeViewGrid } from './NativeViewGrid';
|
||||||
@@ -74,12 +74,7 @@ export function Table(props: BlockProps<DocumentBlockTable>) {
|
|||||||
block.data.view.type === 'grid' ? cellMergeLayout.recordGroups : undefined
|
block.data.view.type === 'grid' ? cellMergeLayout.recordGroups : undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div className={tcls(style, 'flex flex-col gap-3')}>
|
||||||
{...getCommentAnchorProps(context, block.key)}
|
|
||||||
// Owns clicks inside, e.g. on blocks of a record's own document; cells still win.
|
|
||||||
{...getCommentMarkerProps(context, 'data-comment-block')}
|
|
||||||
className={tcls(style, 'flex flex-col gap-3')}
|
|
||||||
>
|
|
||||||
{showSearch ? (
|
{showSearch ? (
|
||||||
<TableSearchInput
|
<TableSearchInput
|
||||||
selectColumns={selectColumns}
|
selectColumns={selectColumns}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import type { DocumentContext } from '../DocumentView';
|
|
||||||
|
|
||||||
// Spread rather than set to `undefined`: an `undefined` prop is still written to the RSC payload,
|
|
||||||
// so public pages would keep paying for the attribute names.
|
|
||||||
|
|
||||||
/** Marks blocks comments can't anchor to, passed down as their `style` (see `ReusableContent`). */
|
|
||||||
export const COMMENT_IGNORE_CLASSNAME = 'comment-ignore';
|
|
||||||
|
|
||||||
/** The node key comments anchor to, only where commenting is possible. */
|
|
||||||
export function getCommentAnchorProps(
|
|
||||||
context: DocumentContext,
|
|
||||||
key: string | undefined
|
|
||||||
): { 'data-block-key'?: string } {
|
|
||||||
return context.withCommentAnchors && key ? { 'data-block-key': key } : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A table cell's record and column, only where commenting is possible. */
|
|
||||||
export function getCommentCellProps(
|
|
||||||
context: DocumentContext,
|
|
||||||
record: string,
|
|
||||||
column: string
|
|
||||||
): { 'data-table-record'?: string; 'data-table-column'?: string } {
|
|
||||||
return context.withCommentAnchors
|
|
||||||
? { 'data-table-record': record, 'data-table-column': column }
|
|
||||||
: {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A boolean comment marker, only where commenting is possible. */
|
|
||||||
export function getCommentMarkerProps(
|
|
||||||
context: DocumentContext,
|
|
||||||
name: 'data-comment-block' | 'data-comment-ignore'
|
|
||||||
): Partial<Record<typeof name, ''>> {
|
|
||||||
return context.withCommentAnchors ? { [name]: '' } : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The node key as a `commentKey` prop for components rendering it themselves (e.g. client ones). */
|
|
||||||
export function getCommentKeyProp(
|
|
||||||
context: DocumentContext,
|
|
||||||
key: string | undefined
|
|
||||||
): { commentKey?: string } {
|
|
||||||
return context.withCommentAnchors && key ? { commentKey: key } : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The anchor attribute for a `commentKey` prop, when set. */
|
|
||||||
export function getCommentKeyAttributes(commentKey: string | undefined): {
|
|
||||||
'data-block-key'?: string;
|
|
||||||
} {
|
|
||||||
return commentKey ? { 'data-block-key': commentKey } : {};
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from './isBlockOffscreen';
|
export * from './isBlockOffscreen';
|
||||||
export * from './textAlignment';
|
export * from './textAlignment';
|
||||||
export * from './commentAnchors';
|
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ export async function PageBody(props: {
|
|||||||
? hasTopLevelBlock(document, (block) => block.type === 'updates')
|
? hasTopLevelBlock(document, (block) => block.type === 'updates')
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
// Comments can only be made on change request previews, so public pages skip their markers.
|
|
||||||
const withCommentAnchors = Boolean(context.changeRequest);
|
|
||||||
|
|
||||||
// Render link previews only if there are less than LINK_PREVIEW_MAX_COUNT links in the document.
|
// Render link previews only if there are less than LINK_PREVIEW_MAX_COUNT links in the document.
|
||||||
const withLinkPreviews = document
|
const withLinkPreviews = document
|
||||||
? !hasMoreThan(
|
? !hasMoreThan(
|
||||||
@@ -103,8 +100,6 @@ export async function PageBody(props: {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<main
|
<main
|
||||||
// Comments on the whole page target anywhere in it that isn't a block.
|
|
||||||
{...(withCommentAnchors ? { 'data-comment-page': '' } : {})}
|
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'relative min-w-0 flex-1',
|
'relative min-w-0 flex-1',
|
||||||
'break-anywhere', // Allow words to break if they are too long.
|
'break-anywhere', // Allow words to break if they are too long.
|
||||||
@@ -155,7 +150,6 @@ export async function PageBody(props: {
|
|||||||
page,
|
page,
|
||||||
},
|
},
|
||||||
withLinkPreviews,
|
withLinkPreviews,
|
||||||
withCommentAnchors,
|
|
||||||
isPageBody: true,
|
isPageBody: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -255,8 +255,6 @@ export async function PageHeader(props: {
|
|||||||
<PageTags page={page} revision={revision} />
|
<PageTags page={page} revision={revision} />
|
||||||
{page.layout.title ? (
|
{page.layout.title ? (
|
||||||
<h1
|
<h1
|
||||||
// Comments on the whole page get their pin next to the title.
|
|
||||||
{...(context.changeRequest ? { 'data-comment-page-title': '' } : {})}
|
|
||||||
data-cover-aware-text
|
data-cover-aware-text
|
||||||
data-over-cover
|
data-over-cover
|
||||||
className={tcls(
|
className={tcls(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { joinPathWithBaseURL } from '../paths';
|
|
||||||
import { checkIsHttpURL } from '../urls';
|
import { checkIsHttpURL } from '../urls';
|
||||||
|
|
||||||
/** Matches the `feedback` length accepted by `submitSiteAgentFeedback`. */
|
/** Matches the `feedback` length accepted by `submitSiteAgentFeedback`. */
|
||||||
@@ -14,7 +13,7 @@ export const agentFeedbackDescriptions = {
|
|||||||
goal: 'The broader end goal you were ultimately trying to accomplish (as/on behalf of the user) when you hit this issue. Gives the team the context you were working towards. Optional.',
|
goal: 'The broader end goal you were ultimately trying to accomplish (as/on behalf of the user) when you hit this issue. Gives the team the context you were working towards. Optional.',
|
||||||
|
|
||||||
pageURL: (siteURL: string | undefined) =>
|
pageURL: (siteURL: string | undefined) =>
|
||||||
`The full URL of the page the issue is about${siteURL ? ` (e.g. ${joinPathWithBaseURL(siteURL, 'getting-started')})` : ''}, so the finding is linked to the exact page.`,
|
`The full URL of the page the issue is about${siteURL ? ` (e.g. ${siteURL}/getting-started)` : ''}, so the finding is linked to the exact page.`,
|
||||||
|
|
||||||
pageReference:
|
pageReference:
|
||||||
'The URL of the page the issue is about, so the finding is linked to the exact page. Omit it to report about the page the user is currently viewing.',
|
'The URL of the page the issue is about, so the finding is linked to the exact page. Omit it to report about the page the user is currently viewing.',
|
||||||
|
|||||||
@@ -334,18 +334,6 @@ describe('buildSiteMcpServerCard', () => {
|
|||||||
expect(search).not.toHaveProperty('inputSchema');
|
expect(search).not.toHaveProperty('inputSchema');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gives example page URLs under the path of a subpath site', () => {
|
|
||||||
const context = makeContext({ host: 'docs.example.com', siteBasePath: '/product/' });
|
|
||||||
const tools = createSiteMcpTools(context, { request });
|
|
||||||
const getPage = tools.find((tool) => tool.name === 'getPage');
|
|
||||||
const pageUrl = tools.find((tool) => tool.name === 'sendFeedback')?.inputSchema.pageUrl;
|
|
||||||
const example = 'https://docs.example.com/product/getting-started';
|
|
||||||
|
|
||||||
expect(getPage?.description).toContain(`(e.g. ${example})`);
|
|
||||||
expect(pageUrl?.description).toContain(`(e.g. ${example})`);
|
|
||||||
expect(pageUrl?.parse('getting-started')).toBe(example);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('omits askQuestion when the site has no AI mode', () => {
|
it('omits askQuestion when the site has no AI mode', () => {
|
||||||
const withAI = buildCard({ aiMode: CustomizationAIMode.Assistant });
|
const withAI = buildCard({ aiMode: CustomizationAIMode.Assistant });
|
||||||
const withoutAI = buildCard({ aiMode: CustomizationAIMode.None });
|
const withoutAI = buildCard({ aiMode: CustomizationAIMode.None });
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export function createSiteMcpTools(
|
|||||||
const { request } = options;
|
const { request } = options;
|
||||||
const { dataFetcher, linker, site } = context;
|
const { dataFetcher, linker, site } = context;
|
||||||
const siteUrl = context.siteSpace.urls.published;
|
const siteUrl = context.siteSpace.urls.published;
|
||||||
const examplePageURL = siteUrl ? joinPathWithBaseURL(siteUrl, 'getting-started') : undefined;
|
|
||||||
|
|
||||||
const tools: SiteMcpTool[] = [
|
const tools: SiteMcpTool[] = [
|
||||||
defineTool({
|
defineTool({
|
||||||
@@ -147,7 +146,7 @@ export function createSiteMcpTools(
|
|||||||
|
|
||||||
defineTool({
|
defineTool({
|
||||||
name: 'getPage',
|
name: 'getPage',
|
||||||
description: `Fetch the full markdown content of a specific documentation page from ${site.title}. Use this when you have a page URL and want to read its content. Accepts full URLs (e.g. ${examplePageURL}). Since \`searchDocumentation\` returns partial content, use \`getPage\` to retrieve the complete page when you need more details. The content includes links you can follow to navigate to related pages.`,
|
description: `Fetch the full markdown content of a specific documentation page from ${site.title}. Use this when you have a page URL and want to read its content. Accepts full URLs (e.g. ${siteUrl}/getting-started). Since \`searchDocumentation\` returns partial content, use \`getPage\` to retrieve the complete page when you need more details. The content includes links you can follow to navigate to related pages.`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
url: z
|
url: z
|
||||||
.string()
|
.string()
|
||||||
@@ -161,7 +160,7 @@ export function createSiteMcpTools(
|
|||||||
}
|
}
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: `"${value}" is not a valid URL. Expected a full URL like ${examplePageURL}`,
|
message: `"${value}" is not a valid URL. Expected a full URL like ${siteUrl}/getting-started`,
|
||||||
});
|
});
|
||||||
return z.NEVER;
|
return z.NEVER;
|
||||||
}),
|
}),
|
||||||
@@ -357,7 +356,7 @@ export function createSiteMcpTools(
|
|||||||
if (!url) {
|
if (!url) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: `"${value}" is not a valid URL on this site. Expected a full URL like ${examplePageURL}`,
|
message: `"${value}" is not a valid URL on this site. Expected a full URL like ${siteUrl}/getting-started`,
|
||||||
});
|
});
|
||||||
return z.NEVER;
|
return z.NEVER;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user