mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 23:55:20 +00:00
Try more blocks
This commit is contained in:
@@ -1,19 +1,39 @@
|
||||
# GitBook
|
||||
|
||||
## TODOs
|
||||
Next.js application to render GitBook published content.
|
||||
|
||||
- OG cover image
|
||||
- [ ] Redirect to the customization one if any
|
||||
- Favicon
|
||||
- [ ] Redirect to the customization one if any
|
||||
- [ ] Render one
|
||||
- Blocks
|
||||
- [ ] Code block syntax highlighting
|
||||
- Theme
|
||||
- [ ] Toggler
|
||||
- Page feedback
|
||||
- Edit on GitHub
|
||||
- Header links
|
||||
## Development
|
||||
|
||||
#### Installation
|
||||
|
||||
Clone the repository and use [Bun](https://bun.sh/) to install dependencies and run the local development server.
|
||||
|
||||
```
|
||||
bun install
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
|
||||
To develop and test a GitBook space locally, first create a [GitBook API token](https://app.gitbook.com/account/developer) and put it in a `.env.local` file:
|
||||
|
||||
```
|
||||
GITBOOK_TOKEN=gb_api_abc
|
||||
```
|
||||
|
||||
#### Start the local server
|
||||
|
||||
Run the Next.js development server.
|
||||
|
||||
```
|
||||
bun dev
|
||||
```
|
||||
|
||||
Then open the space in your web browser: `http://localhost:3000/<space>/`.
|
||||
|
||||
#### Other commands
|
||||
|
||||
- `bun format`: format the code
|
||||
- `bun lint`: lint the code
|
||||
|
||||
## Differences
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@geist-ui/icons": "^1.0.2",
|
||||
"@gitbook/api": "^0.11.1",
|
||||
"@readme/openapi-parser": "^2.5.0",
|
||||
"ajv": "^8.12.0",
|
||||
"assert-never": "^1.2.1",
|
||||
"bun-types": "^1.0.7",
|
||||
"katex": "^0.16.9",
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ListOrdered } from './ListOrdered';
|
||||
import { ListUnordered } from './ListUnordered';
|
||||
import { ListItem } from './ListItem';
|
||||
import { CodeBlock } from './CodeBlock';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Hint } from './Hint';
|
||||
import { DocumentContextProps } from './DocumentView';
|
||||
import { Images } from './Images';
|
||||
@@ -17,6 +16,8 @@ import { Embed } from './Embed';
|
||||
import { Quote } from './Quote';
|
||||
import {
|
||||
DocumentBlockCode,
|
||||
DocumentBlockDivider,
|
||||
DocumentBlockDrawing,
|
||||
DocumentBlockEmbed,
|
||||
DocumentBlockExpandable,
|
||||
DocumentBlockFile,
|
||||
@@ -33,10 +34,15 @@ import {
|
||||
DocumentBlockSwagger,
|
||||
DocumentBlockTable,
|
||||
DocumentBlockTabs,
|
||||
DocumentBlockTabsItem,
|
||||
DocumentBlockTaskListItem,
|
||||
} from '@gitbook/api';
|
||||
import { BlockMath } from './Math';
|
||||
import { File } from './File';
|
||||
import { ListTasks } from './ListTasks';
|
||||
import { Divider } from './Divider';
|
||||
import assertNever from 'assert-never';
|
||||
import { Drawing } from './Drawing';
|
||||
|
||||
export type SupportedBlock =
|
||||
| DocumentBlockParagraph
|
||||
@@ -56,10 +62,15 @@ export type SupportedBlock =
|
||||
| DocumentBlockEmbed
|
||||
| DocumentBlockQuote
|
||||
| DocumentBlockMath
|
||||
| DocumentBlockFile;
|
||||
| DocumentBlockFile
|
||||
| DocumentBlockDivider
|
||||
| DocumentBlockDrawing;
|
||||
|
||||
export type AncestorBlock = SupportedBlock | DocumentBlockTabsItem;
|
||||
|
||||
export interface BlockProps<Block extends SupportedBlock> extends DocumentContextProps {
|
||||
block: Block;
|
||||
ancestorBlocks: AncestorBlock[];
|
||||
style?: ClassValue;
|
||||
}
|
||||
|
||||
@@ -77,6 +88,8 @@ export function Block<T extends SupportedBlock>(props: BlockProps<T>) {
|
||||
return <ListOrdered {...props} {...contextProps} block={block} />;
|
||||
case 'list-unordered':
|
||||
return <ListUnordered {...props} {...contextProps} block={block} />;
|
||||
case 'list-tasks':
|
||||
return <ListTasks {...props} {...contextProps} block={block} />;
|
||||
case 'list-item':
|
||||
return <ListItem {...props} {...contextProps} block={block} />;
|
||||
case 'code':
|
||||
@@ -101,7 +114,11 @@ export function Block<T extends SupportedBlock>(props: BlockProps<T>) {
|
||||
return <BlockMath {...props} {...contextProps} block={block} />;
|
||||
case 'file':
|
||||
return <File {...props} {...contextProps} block={block} />;
|
||||
case 'divider':
|
||||
return <Divider {...props} {...contextProps} block={block} />;
|
||||
case 'drawing':
|
||||
return <Drawing {...props} {...contextProps} block={block} />;
|
||||
default:
|
||||
return <div className={tcls(style)}>Unsupported block {block.type}</div>;
|
||||
assertNever(block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { Block } from './Block';
|
||||
import { AncestorBlock, Block, SupportedBlock } from './Block';
|
||||
import { DocumentContextProps } from './DocumentView';
|
||||
import { tcls, ClassValue } from '@/lib/tailwind';
|
||||
|
||||
export function Blocks<T extends { key?: string }, Tag extends React.ElementType = 'div'>(
|
||||
export function Blocks<T extends SupportedBlock, Tag extends React.ElementType = 'div'>(
|
||||
props: DocumentContextProps & {
|
||||
/** Blocks to render */
|
||||
nodes: T[];
|
||||
|
||||
/** Ancestors of the blocks */
|
||||
ancestorBlocks: AncestorBlock[];
|
||||
|
||||
/** HTML tag to use */
|
||||
tag?: Tag;
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { DocumentBlockDivider } from '@gitbook/api';
|
||||
import { BlockProps } from './Block';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
export function Divider(props: BlockProps<DocumentBlockDivider>) {
|
||||
const { style } = props;
|
||||
|
||||
return <hr className={tcls(style)} />;
|
||||
}
|
||||
@@ -18,5 +18,13 @@ export function DocumentView(
|
||||
) {
|
||||
const { document, style, ...context } = props;
|
||||
|
||||
return <Blocks nodes={document.nodes} blockStyle={'mt-6'} style={style} {...context} />;
|
||||
return (
|
||||
<Blocks
|
||||
nodes={document.nodes}
|
||||
ancestorBlocks={[]}
|
||||
blockStyle={'mt-6'}
|
||||
style={style}
|
||||
{...context}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { DocumentBlockDrawing } from '@gitbook/api';
|
||||
import { BlockProps } from './Block';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
export function Drawing(props: BlockProps<DocumentBlockDrawing>) {
|
||||
const { style } = props;
|
||||
|
||||
return <div className={tcls(style)}>TODO</div>;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { DocumentBlockExpandable } from '@gitbook/api';
|
||||
import { Inlines } from '../Inlines';
|
||||
|
||||
export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
const { block, style, context } = props;
|
||||
const { block, style, ancestorBlocks, context } = props;
|
||||
|
||||
const title = getNodeFragmentByType(block, 'expandable-title');
|
||||
const body = getNodeFragmentByType(block, 'expandable-body');
|
||||
@@ -16,7 +16,12 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
<summary className={tcls('cursor-pointer', 'px-4', 'py-3')}>
|
||||
<Inlines nodes={title.nodes[0].nodes} context={context} />
|
||||
</summary>
|
||||
<Blocks nodes={body.nodes} context={context} style={['px-5', 'py-3']} />
|
||||
<Blocks
|
||||
nodes={body.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
context={context}
|
||||
style={['px-5', 'py-3']}
|
||||
/>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import IconCheckInCircle from '@geist-ui/icons/checkInCircle';
|
||||
import { getBlockTextStyle } from './spacing';
|
||||
|
||||
export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
const { block, style, ...contextProps } = props;
|
||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||
const hintStyle = HINT_STYLES[block.data.style];
|
||||
const firstLine = getBlockTextStyle(block.nodes[0]);
|
||||
|
||||
@@ -40,7 +40,12 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
>
|
||||
<hintStyle.icon className={tcls('w-5', 'h-5')} />
|
||||
</div>
|
||||
<Blocks {...contextProps} nodes={block.nodes} style={['flex-1']} />
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
nodes={block.nodes}
|
||||
style={['flex-1']}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { BlockProps } from './Block';
|
||||
import { Blocks } from './Blocks';
|
||||
import { DocumentBlockListItem } from '@gitbook/api';
|
||||
import { DocumentBlockListItem, DocumentBlockTaskListItem } from '@gitbook/api';
|
||||
|
||||
export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
||||
const { block, ...contextProps } = props;
|
||||
export function ListItem(props: BlockProps<DocumentBlockListItem | DocumentBlockTaskListItem>) {
|
||||
const { block, ancestorBlocks, ...contextProps } = props;
|
||||
|
||||
const isTaskList = ancestorBlocks[ancestorBlocks.length - 1]?.type === 'list-tasks';
|
||||
|
||||
return (
|
||||
<li className={tcls('text-base', 'font-normal')}>
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
nodes={block.nodes}
|
||||
blockStyle={tcls('mt-6', 'first:mt-0', 'last:mt-0')}
|
||||
/>
|
||||
{isTaskList ? (
|
||||
<div className={tcls('flex', 'flex-row')}>
|
||||
<input type="checkbox" disabled checked={block.data?.checked} />
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
blockStyle={tcls('mt-6', 'first:mt-0', 'last:mt-0', 'flex-1')}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
blockStyle={tcls('mt-6', 'first:mt-0', 'last:mt-0')}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import { BlockProps } from './Block';
|
||||
import { Blocks } from './Blocks';
|
||||
|
||||
export function ListOrdered(props: BlockProps<DocumentBlockListOrdered>) {
|
||||
const { block, style, ...contextProps } = props;
|
||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||
|
||||
return (
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
tag="ol"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={['list-decimal', 'ps-8', style]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { DocumentBlockListTasks, DocumentBlockListUnordered } from '@gitbook/api';
|
||||
import { BlockProps } from './Block';
|
||||
import { Blocks } from './Blocks';
|
||||
|
||||
export function ListTasks(props: BlockProps<DocumentBlockListTasks>) {
|
||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||
|
||||
return (
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
tag="ul"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={['list-disc', 'ps-8', style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -3,13 +3,14 @@ import { BlockProps } from './Block';
|
||||
import { Blocks } from './Blocks';
|
||||
|
||||
export function ListUnordered(props: BlockProps<DocumentBlockListUnordered>) {
|
||||
const { block, style, ...contextProps } = props;
|
||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||
|
||||
return (
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
tag="ul"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={['list-disc', 'ps-8', style]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -3,13 +3,14 @@ import { Blocks } from './Blocks';
|
||||
import { DocumentBlockQuote } from '@gitbook/api';
|
||||
|
||||
export function Quote(props: BlockProps<DocumentBlockQuote>) {
|
||||
const { block, style, ...contextProps } = props;
|
||||
const { block, style, ancestorBlocks, ...contextProps } = props;
|
||||
|
||||
return (
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
tag="blockquote"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={[style, 'border-l-4', 'pl-4', 'border-slate-200', 'dark:border-slate-900']}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
return (
|
||||
<Blocks
|
||||
tag={Tag}
|
||||
ancestorBlocks={[]}
|
||||
nodes={fragment.nodes}
|
||||
style={[columnStyle, 'w-full']}
|
||||
context={context}
|
||||
|
||||
@@ -5,14 +5,20 @@ import { DynamicTabs } from './DynamicTabs';
|
||||
import { DocumentBlockTabs } from '@gitbook/api';
|
||||
|
||||
export function Tabs(props: BlockProps<DocumentBlockTabs>) {
|
||||
const { block, style, context } = props;
|
||||
const { block, ancestorBlocks, style, context } = props;
|
||||
|
||||
return (
|
||||
<DynamicTabs
|
||||
tabs={block.nodes.map((tab, index) => ({
|
||||
id: tab.key!,
|
||||
title: tab.data.title,
|
||||
children: <Blocks nodes={tab.nodes} context={context} />,
|
||||
title: tab.data.title ?? '',
|
||||
children: (
|
||||
<Blocks
|
||||
nodes={tab.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block, tab]}
|
||||
context={context}
|
||||
/>
|
||||
),
|
||||
}))}
|
||||
style={style}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user