mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-24 19:32:07 +00:00
Fix an issue where following a link to a different space would not update the ToC or header. (#284)
* Fix an issue where clicking links to other spaces would not update ToC/header * Fix * Remove dev text * Use URL.CanParse for testing the external url --------- Co-authored-by: Samy Pessé <samypesse@gmail.com>
This commit is contained in:
@@ -14,7 +14,7 @@ import { Annotation } from './Annotation/Annotation';
|
||||
import { DocumentContextProps } from './DocumentView';
|
||||
import { Emoji } from './Emoji';
|
||||
import { InlineImage } from './InlineImage';
|
||||
import { Link } from './Link';
|
||||
import { InlineLink } from './InlineLink';
|
||||
import { InlineMath } from './Math';
|
||||
import { Mention } from './Mention';
|
||||
|
||||
@@ -45,7 +45,7 @@ export function Inline<
|
||||
|
||||
switch (inline.type) {
|
||||
case 'link':
|
||||
return <Link {...contextProps} inline={inline} />;
|
||||
return <InlineLink {...contextProps} inline={inline} />;
|
||||
case 'inline-math':
|
||||
return <InlineMath {...contextProps} inline={inline} />;
|
||||
case 'annotation':
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DocumentInlineLink } from '@gitbook/api';
|
||||
import NextLink from 'next/link';
|
||||
|
||||
import { InlineProps } from './Inline';
|
||||
import { Inlines } from './Inlines';
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export async function Link(props: InlineProps<DocumentInlineLink>) {
|
||||
export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
const { inline, document, context } = props;
|
||||
|
||||
const resolved = await context.resolveContentRef(inline.data.ref);
|
||||
@@ -18,11 +18,11 @@ export async function Link(props: InlineProps<DocumentInlineLink>) {
|
||||
}
|
||||
|
||||
return (
|
||||
<NextLink
|
||||
<Link
|
||||
href={resolved.href}
|
||||
className="underline underline-offset-2 text-primary hover:text-primary-700 transition-colors "
|
||||
>
|
||||
<Inlines context={context} document={document} nodes={inline.nodes} />
|
||||
</NextLink>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DocumentInlineMention } from '@gitbook/api';
|
||||
|
||||
import { Link } from '@/components/primitives';
|
||||
import { StyledLink } from '@/components/primitives';
|
||||
|
||||
import { InlineProps } from './Inline';
|
||||
|
||||
@@ -13,5 +13,5 @@ export async function Mention(props: InlineProps<DocumentInlineMention>) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Link href={resolved.href}>{resolved.text}</Link>;
|
||||
return <StyledLink href={resolved.href}>{resolved.text}</StyledLink>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import IconStar from '@geist-ui/icons/star';
|
||||
import { ContentRef, DocumentBlockTable } from '@gitbook/api';
|
||||
import assertNever from 'assert-never';
|
||||
|
||||
import { Checkbox, Emoji, Link } from '@/components/primitives';
|
||||
import { Checkbox, Emoji } from '@/components/primitives';
|
||||
import { StyledLink } from '@/components/primitives';
|
||||
import { getNodeFragmentByName } from '@/lib/document';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { filterOutNullable } from '@/lib/typescript';
|
||||
@@ -125,9 +126,9 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
return (
|
||||
<Tag className={tcls('text-base')}>
|
||||
{files.filter(filterOutNullable).map((file, index) => (
|
||||
<Link key={index} href={file.href}>
|
||||
<StyledLink key={index} href={file.href}>
|
||||
{file.text}
|
||||
</Link>
|
||||
</StyledLink>
|
||||
))}
|
||||
</Tag>
|
||||
);
|
||||
@@ -138,7 +139,9 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
{resolved && resolved.emoji ? (
|
||||
<Emoji code={resolved.emoji} style={['mr-2']} />
|
||||
) : null}
|
||||
{resolved ? <Link href={resolved.href}>{resolved.text}</Link> : null}
|
||||
{resolved ? (
|
||||
<StyledLink href={resolved.href}>{resolved.text}</StyledLink>
|
||||
) : null}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -155,9 +158,9 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
return (
|
||||
<Tag className={tcls('text-base')}>
|
||||
{resolved.filter(filterOutNullable).map((file, index) => (
|
||||
<Link key={index} href={file.href}>
|
||||
<StyledLink key={index} href={file.href}>
|
||||
{file.text}
|
||||
</Link>
|
||||
</StyledLink>
|
||||
))}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { CustomizationContentLink, CustomizationFooterGroup } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ContentRefContext, resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export function FooterLinksGroup(props: {
|
||||
group: CustomizationFooterGroup;
|
||||
context: ContentRefContext;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import IconChevronDown from '@geist-ui/icons/chevronDown';
|
||||
import Link from 'next/link';
|
||||
import { DetailedHTMLProps, HTMLAttributes, useId } from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export type DropdownButtonProps<E extends HTMLElement = HTMLElement> = Omit<
|
||||
Partial<DetailedHTMLProps<HTMLAttributes<E>, E>>,
|
||||
'ref'
|
||||
@@ -111,15 +112,10 @@ export function DropdownMenuItem(props: {
|
||||
}) {
|
||||
const { children, active = false, href } = props;
|
||||
|
||||
// Use a real anchor tag for external links, and a Next.js Link for internal links.
|
||||
// The main reason is to prevent a bug where next.js Link will result in the page not changing when navigating between spaces in a collection
|
||||
const isExternal = href.startsWith('http');
|
||||
const A = isExternal ? 'a' : Link;
|
||||
|
||||
return (
|
||||
<A
|
||||
<Link
|
||||
href={href}
|
||||
{...(isExternal ? {} : { prefetch: false })}
|
||||
prefetch={false}
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-row',
|
||||
@@ -135,6 +131,6 @@ export function DropdownMenuItem(props: {
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</A>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
CustomizationSettings,
|
||||
CustomizationHeaderPreset,
|
||||
} from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ContentRefContext, resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuItem,
|
||||
} from './Dropdown';
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export async function HeaderLink(props: {
|
||||
context: ContentRefContext;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Collection, CustomizationHeaderPreset, CustomizationSettings, Space } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { HeaderMobileMenu } from '@/components/Header/HeaderMobileMenu';
|
||||
import { Image } from '@/components/utils';
|
||||
import { absoluteHref } from '@/lib/links';
|
||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
|
||||
interface HeaderLogoProps {
|
||||
collection: Collection | null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ChevronLeft from '@geist-ui/icons/chevronLeft';
|
||||
import ChevronRight from '@geist-ui/icons/chevronRight';
|
||||
import { CustomizationSettings, Revision, RevisionPageDocument, Space } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
import { t, getSpaceLanguage } from '@/intl/server';
|
||||
@@ -9,6 +8,8 @@ import { pageHref } from '@/lib/links';
|
||||
import { resolvePrevNextPages } from '@/lib/pages';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
|
||||
/**
|
||||
* Show cards to go to previous/next pages at the bottom.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import IconBox from '@geist-ui/icons/box';
|
||||
import IconSearch from '@geist-ui/icons/search';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { atom, useRecoilState } from 'recoil';
|
||||
|
||||
@@ -12,6 +11,7 @@ import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { AskAnswerResult, streamAskQuestion } from './server-actions';
|
||||
import { useSearch, useSearchLink } from './useSearch';
|
||||
import { Link } from '../primitives';
|
||||
|
||||
/**
|
||||
* Store the state of the answer in a global state so that it can be
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { HighlightQuery } from './HighlightQuery';
|
||||
import type { ComputedPageResult } from './server-actions';
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export const SearchPageResultItem = React.forwardRef(function SearchPageResultItem(
|
||||
props: {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import IconSearch from '@geist-ui/icons/search';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
import { t, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { useSearchLink } from './useSearch';
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export const SearchQuestionResultItem = React.forwardRef(function SearchQuestionResultItem(
|
||||
props: {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { HighlightQuery } from './HighlightQuery';
|
||||
import type { ComputedSectionResult } from './server-actions';
|
||||
import { Link } from '../primitives';
|
||||
|
||||
export const SearchSectionResultItem = React.forwardRef(function SearchSectionResultItem(
|
||||
props: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { LinkProps } from 'next/link';
|
||||
import { useQueryStates, parseAsBoolean, parseAsString, UseQueryStatesOptions } from 'nuqs';
|
||||
import React from 'react';
|
||||
|
||||
import { LinkProps } from '../primitives';
|
||||
|
||||
export interface SearchState {
|
||||
query: string;
|
||||
ask: boolean;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import IconExternal from '@geist-ui/icons/externalLink';
|
||||
import { RevisionPageLink } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Emoji } from '@/components/primitives';
|
||||
import { Emoji, Link } from '@/components/primitives';
|
||||
import { ContentRefContext, resolveContentRef } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import IconChevronRight from '@geist-ui/icons/chevronRight';
|
||||
import { motion, stagger, useAnimate } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
|
||||
const show = {
|
||||
opacity: 1,
|
||||
height: 'auto',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { tcls, ClassValue } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from './Link';
|
||||
|
||||
type ButtonProps = {
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import ChevronRight from '@geist-ui/icons/chevronRight';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from './Link';
|
||||
|
||||
export async function Card(props: {
|
||||
href: string;
|
||||
leadingIcon?: React.ReactNode;
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import NextLink, { LinkProps } from 'next/link';
|
||||
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
// Props from Next, which includes NextLinkProps and all the things anchor elements support.
|
||||
type BaseLinkProps = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof NextLinkProps> &
|
||||
NextLinkProps & {
|
||||
children?: React.ReactNode;
|
||||
} & React.RefAttributes<HTMLAnchorElement>;
|
||||
|
||||
// Enforce href is passed as a string (not a URL).
|
||||
export type LinkProps = Omit<BaseLinkProps, 'href'> & { href: string };
|
||||
|
||||
/**
|
||||
* Styled version of Next.js Link component.
|
||||
* Low-level Link component that handles navigation to external urls.
|
||||
* It does not contain any styling.
|
||||
*/
|
||||
export function Link(props: LinkProps & { children: React.ReactNode }) {
|
||||
return (
|
||||
<NextLink
|
||||
{...props}
|
||||
className={tcls(
|
||||
'underline',
|
||||
'underline-offset-2',
|
||||
'decoration-primary/6',
|
||||
'text-primary',
|
||||
'hover:text-primary-700',
|
||||
'transition-colors',
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</NextLink>
|
||||
);
|
||||
export function Link(props: LinkProps) {
|
||||
const { href, prefetch, children, ...domProps } = props;
|
||||
|
||||
// Use a real anchor tag for external links,s and a Next.js Link for internal links.
|
||||
// If we use a NextLink for external links, Nextjs won't rerender the top-level layouts.
|
||||
const isExternal = URL.canParse(props.href);
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a {...domProps} href={href}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return <NextLink {...props}>{children}</NextLink>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link, LinkProps } from '../primitives/Link';
|
||||
|
||||
/**
|
||||
* Styled version of Link component.
|
||||
*/
|
||||
export function StyledLink(props: LinkProps) {
|
||||
return (
|
||||
<Link
|
||||
{...props}
|
||||
className={tcls(
|
||||
'underline',
|
||||
'underline-offset-2',
|
||||
'decoration-primary/6',
|
||||
'text-primary',
|
||||
'hover:text-primary-700',
|
||||
'transition-colors',
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ export * from './Loading';
|
||||
export * from './Card';
|
||||
export * from './Skeleton';
|
||||
export * from './Link';
|
||||
export * from './StyledLink';
|
||||
export * from './DateRelative';
|
||||
export * from './Emoji';
|
||||
export * from './LoadingPane';
|
||||
|
||||
Reference in New Issue
Block a user