mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-19 17:15:24 +00:00
Fix scroll to hash and update next to 14.1.3 (#286)
* Update next to 14.1.3 * Scroll to hash when url changes * Simplify tests * Lint and format * Fix TS * Dummy comment * Try async function
This commit is contained in:
+4
-88
@@ -81,94 +81,6 @@ const testCases: TestsCase[] = [
|
||||
{
|
||||
name: 'Rocket.Chat',
|
||||
baseUrl: 'https://docs.rocket.chat',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
url: '~gitbook/pdf?limit=10',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Commerce Layer',
|
||||
baseUrl: 'https://docs.commercelayer.io/core/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
name: 'API Reference',
|
||||
url: 'v/api-reference/',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Naviga',
|
||||
baseUrl: 'https://docs.navigaglobal.com/naviga-dashboard-overview/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: 'v/dashboard-5.4/',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Mattermost',
|
||||
baseUrl: 'https://handbook.mattermost.com/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Tile DB',
|
||||
baseUrl: 'https://docs.tiledb.com/main/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Nimbleway',
|
||||
baseUrl: 'https://docs.nimbleway.com/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Parcellab',
|
||||
baseUrl: 'https://how.parcellab.works/docs/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'CitrusAd',
|
||||
baseUrl: 'https://help.citrusad.com/citrus-ads/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ThousandEyes',
|
||||
baseUrl: 'https://docs.thousandeyes.com/',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
@@ -252,6 +164,10 @@ const testCases: TestsCase[] = [
|
||||
name: 'Emojis',
|
||||
url: 'blocks/emojis',
|
||||
},
|
||||
{
|
||||
name: 'Links',
|
||||
url: 'blocks/links',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
"jsontoxml": "^1.0.1",
|
||||
"katex": "^0.16.9",
|
||||
"memoizee": "^0.4.15",
|
||||
"next": "^14.1.0",
|
||||
"next": "^14.1.3",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^1.15.4",
|
||||
"object-hash": "^3.0.0",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { useScrollToHash } from '@/components/hooks';
|
||||
|
||||
/**
|
||||
* Client component to initialize interactivity for a page.
|
||||
*/
|
||||
export function PageClientLayout(props: {}) {
|
||||
// We use this hook in the page layout to ensure the elements for the blocks
|
||||
// are rendered before we scroll to the hash.
|
||||
useScrollToHash();
|
||||
return null;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { getPagePath } from '@/lib/pages';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PageClientLayout } from './PageClientLayout';
|
||||
import { PagePathParams, fetchPageData, getPathnameParam } from '../../fetch';
|
||||
|
||||
export const runtime = 'edge';
|
||||
@@ -83,6 +84,9 @@ export default async function Page(props: { params: PagePathParams }) {
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<React.Suspense fallback={null}>
|
||||
<PageClientLayout />
|
||||
</React.Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,15 +111,16 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
|
||||
|
||||
function BlockPlaceholder(props: { block: DocumentBlock; style: ClassValue }) {
|
||||
const { block, style } = props;
|
||||
const id = 'meta' in block && block.meta && 'id' in block.meta ? block.meta.id : undefined;
|
||||
|
||||
switch (block.type) {
|
||||
case 'heading-1':
|
||||
case 'heading-2':
|
||||
case 'heading-3':
|
||||
case 'file':
|
||||
return <SkeletonHeading style={style} />;
|
||||
return <SkeletonHeading id={id} style={style} />;
|
||||
case 'paragraph':
|
||||
return <SkeletonSmall style={style} />;
|
||||
return <SkeletonSmall id={id} style={style} />;
|
||||
case 'list-ordered':
|
||||
case 'list-unordered':
|
||||
case 'list-tasks':
|
||||
@@ -129,7 +130,7 @@ function BlockPlaceholder(props: { block: DocumentBlock; style: ClassValue }) {
|
||||
case 'hint':
|
||||
case 'tabs':
|
||||
case 'synced-block':
|
||||
return <SkeletonParagraph style={style} />;
|
||||
return <SkeletonParagraph id={id} style={style} />;
|
||||
case 'expandable':
|
||||
case 'table':
|
||||
case 'swagger':
|
||||
@@ -137,17 +138,17 @@ function BlockPlaceholder(props: { block: DocumentBlock; style: ClassValue }) {
|
||||
case 'divider':
|
||||
case 'content-ref':
|
||||
case 'integration':
|
||||
return <SkeletonCard style={style} />;
|
||||
return <SkeletonCard id={id} style={style} />;
|
||||
case 'embed':
|
||||
case 'images':
|
||||
case 'drawing':
|
||||
return <SkeletonImage style={style} />;
|
||||
return <SkeletonImage id={id} style={style} />;
|
||||
case 'image':
|
||||
case 'code-line':
|
||||
case 'tabs-item':
|
||||
throw new Error('Blocks should be directly rendered by parent');
|
||||
case 'integration':
|
||||
return <SkeletonCard style={style} />;
|
||||
return <SkeletonCard id={id} style={style} />;
|
||||
default:
|
||||
assertNever(block);
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './useScrollActiveId';
|
||||
export * from './useScrollToHash';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Scroll to the current URL hash everytime the URL changes.
|
||||
*/
|
||||
export function useScrollToHash() {
|
||||
const params = useParams();
|
||||
|
||||
const scrollToHash = React.useCallback(() => {
|
||||
const hash = window.location.hash;
|
||||
if (hash) {
|
||||
const element = document.getElementById(hash.slice(1));
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
block: 'start',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// With next.js, the hashchange event is not triggered when the hash changes
|
||||
// Instead a hack is to use the `useParams` hook to listen to changes in the hash
|
||||
// https://github.com/vercel/next.js/discussions/49465#discussioncomment-5845312
|
||||
React.useEffect(() => {
|
||||
scrollToHash();
|
||||
}, [params, scrollToHash]);
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import { LoadingPane } from './LoadingPane';
|
||||
* Placeholder to be used when a content is not yet loaded (in a React.Suspense boundary).
|
||||
* It's used when streaming the content of a page.
|
||||
*/
|
||||
export function SkeletonParagraph(props: { style?: ClassValue }) {
|
||||
const { style } = props;
|
||||
export function SkeletonParagraph(props: { id?: string; style?: ClassValue }) {
|
||||
const { id, style } = props;
|
||||
return (
|
||||
<div role="status" aria-busy className="skeleton-paragraph">
|
||||
<div id={id} role="status" aria-busy className="skeleton-paragraph">
|
||||
<LoadingPane
|
||||
style={[
|
||||
'rounded-md',
|
||||
@@ -25,10 +25,10 @@ export function SkeletonParagraph(props: { style?: ClassValue }) {
|
||||
/**
|
||||
* Placeholder when loading a title.
|
||||
*/
|
||||
export function SkeletonHeading(props: { style?: ClassValue }) {
|
||||
const { style } = props;
|
||||
export function SkeletonHeading(props: { id?: string; style?: ClassValue }) {
|
||||
const { id, style } = props;
|
||||
return (
|
||||
<div role="status" aria-busy className="skeleton-heading">
|
||||
<div id={id} role="status" aria-busy className="skeleton-heading">
|
||||
<LoadingPane
|
||||
tile={12}
|
||||
style={['rounded-md', 'h-[47px]', '[max-width:calc(48rem-1px)]', style]}
|
||||
@@ -40,10 +40,10 @@ export function SkeletonHeading(props: { style?: ClassValue }) {
|
||||
/**
|
||||
* Placeholder when loading an asset (image, video, etc.)
|
||||
*/
|
||||
export function SkeletonImage(props: { style?: ClassValue }) {
|
||||
const { style } = props;
|
||||
export function SkeletonImage(props: { id?: string; style?: ClassValue }) {
|
||||
const { id, style } = props;
|
||||
return (
|
||||
<div role="status" aria-busy className="skeleton-image">
|
||||
<div id={id} role="status" aria-busy className="skeleton-image">
|
||||
<LoadingPane
|
||||
tile={96}
|
||||
style={[
|
||||
@@ -61,10 +61,15 @@ export function SkeletonImage(props: { style?: ClassValue }) {
|
||||
/**
|
||||
* Placeholder when loading a card
|
||||
*/
|
||||
export function SkeletonCard(props: { style?: ClassValue }) {
|
||||
const { style } = props;
|
||||
export function SkeletonCard(props: { id?: string; style?: ClassValue }) {
|
||||
const { id, style } = props;
|
||||
return (
|
||||
<div role="status" aria-busy className={tcls('skeleton-card', 'flex', 'gap-[25px]', style)}>
|
||||
<div
|
||||
id={id}
|
||||
role="status"
|
||||
aria-busy
|
||||
className={tcls('skeleton-card', 'flex', 'gap-[25px]', style)}
|
||||
>
|
||||
<LoadingPane tile={24} delay={0} style={['rounded-md', 'aspect-[1/1.2]', 'w-full']} />
|
||||
<LoadingPane tile={24} delay={1} style={['rounded-md', 'aspect-[1/1.2]', 'w-full']} />
|
||||
<LoadingPane tile={24} delay={2} style={['rounded-md', 'aspect-[1/1.2]', 'w-full']} />
|
||||
@@ -75,10 +80,10 @@ export function SkeletonCard(props: { style?: ClassValue }) {
|
||||
/**
|
||||
* Placeholder when loading small elements
|
||||
*/
|
||||
export function SkeletonSmall(props: { style?: ClassValue }) {
|
||||
const { style } = props;
|
||||
export function SkeletonSmall(props: { id?: string; style?: ClassValue }) {
|
||||
const { id, style } = props;
|
||||
return (
|
||||
<div role="status" aria-busy className="skeleton-small">
|
||||
<div id={id} role="status" aria-busy className="skeleton-small">
|
||||
<LoadingPane
|
||||
tile={12}
|
||||
style={['rounded-md', 'h-[35px]', '[max-width:calc(48rem-1px)]', style]}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ export type StreamResponseChunk<T> = {
|
||||
export function streamResponse<T, P extends any[]>(
|
||||
createGenerator: (...args: P) => AsyncGenerator<T>,
|
||||
) {
|
||||
return (...args: Parameters<typeof createGenerator>) => {
|
||||
return async (...args: Parameters<typeof createGenerator>) => {
|
||||
const generator = createGenerator(...args);
|
||||
return streamChunk<T>(generator);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user