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:
Steven H
2024-03-15 19:09:29 +00:00
committed by GitHub
parent 5c7c17fe02
commit 9f75929269
21 changed files with 94 additions and 58 deletions
+2 -2
View File
@@ -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>
);
}
+2 -2
View File
@@ -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>
);
+2 -1
View File
@@ -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;
+5 -9
View File
@@ -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>
);
}
+1 -1
View File
@@ -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;
+3 -2
View File
@@ -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 -1
View File
@@ -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: {
+2 -1
View File
@@ -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',
+2 -2
View File
@@ -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;
+2 -1
View File
@@ -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;
+26 -19
View File
@@ -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>;
}
+24
View File
@@ -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>
);
}
+1
View File
@@ -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';