mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-29 13:39:11 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 078eacce45 | |||
| 9f168ff93b | |||
| df2841cf36 | |||
| 70bfdda39c |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Keep links working when their target page is deleted and re-created at the same path, by falling back to the path recorded on the link.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Keep links to GitBook app URLs of spaces in the same site on the site, instead of sending readers to app.gitbook.com. A link to a page that no longer exists now opens its space on the site, and its "Page not found" card says which space it opens.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Restore the Next.js dev-mode OOM patch that was silently dropped by the 16.3.6 upgrade (root `package.json`'s `patchedDependencies` still pinned it to the old `16.3.3` patch file, so Bun stopped applying it).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix the site auth login link sometimes redirecting back to an internal URL after login.
|
||||
@@ -342,6 +342,7 @@
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"decode-named-character-reference@1.0.2": "patches/decode-named-character-reference@1.0.2.patch",
|
||||
"next@16.3.6": "patches/next@16.3.6.patch",
|
||||
},
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
|
||||
+1
-1
@@ -68,6 +68,6 @@
|
||||
"patchedDependencies": {
|
||||
"decode-named-character-reference@1.0.2": "patches/decode-named-character-reference@1.0.2.patch",
|
||||
"@vercel/next@4.4.2": "patches/@vercel%2Fnext@4.4.2.patch",
|
||||
"next@16.3.3": "patches/next@16.3.3.patch"
|
||||
"next@16.3.6": "patches/next@16.3.6.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>
|
||||
: null;
|
||||
|
||||
if (!resolved) {
|
||||
const fallback = resolveContentRefFallback(block.data.ref, context.contentContext);
|
||||
const fallback = resolveContentRefFallback(block.data.ref);
|
||||
if (!fallback) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NotFoundRefHoverCard context={context} fallback={fallback}>
|
||||
<NotFoundRefHoverCard context={context}>
|
||||
<BlockContentRefCard
|
||||
contentRef={block.data.ref}
|
||||
resolved={fallback}
|
||||
|
||||
@@ -79,11 +79,9 @@ export async function InlineLinkButton(
|
||||
? await resolveContentRefInDocument(document, inline.data.ref, context.contentContext)
|
||||
: null;
|
||||
|
||||
const fallback =
|
||||
!resolved && inline.data.ref
|
||||
? resolveContentRefFallback(inline.data.ref, context.contentContext)
|
||||
: null;
|
||||
const href = resolved?.href ?? fallback?.href;
|
||||
const href =
|
||||
resolved?.href ??
|
||||
(inline.data.ref ? resolveContentRefFallback(inline.data.ref)?.href : undefined);
|
||||
const sharedProps: React.ComponentProps<typeof Button> = {
|
||||
...buttonProps,
|
||||
insights: {
|
||||
@@ -107,11 +105,7 @@ export async function InlineLinkButton(
|
||||
);
|
||||
|
||||
if (inline.data.ref && !resolved) {
|
||||
return (
|
||||
<NotFoundRefHoverCard context={context} fallback={fallback}>
|
||||
{button}
|
||||
</NotFoundRefHoverCard>
|
||||
);
|
||||
return <NotFoundRefHoverCard context={context}>{button}</NotFoundRefHoverCard>;
|
||||
}
|
||||
|
||||
return button;
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
resolveContentRefFallback,
|
||||
resolveContentRefInDocument,
|
||||
} from '@/lib/references';
|
||||
import { checkIsExternalURL } from '@/lib/urls';
|
||||
|
||||
export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
const { document, inline, context, ancestorInlines } = props;
|
||||
@@ -37,15 +36,11 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
);
|
||||
|
||||
if (!resolved) {
|
||||
const fallback = resolveContentRefFallback(inline.data.ref, contentContext);
|
||||
const fallback = resolveContentRefFallback(inline.data.ref);
|
||||
return (
|
||||
<NotFoundRefHoverCard context={context} fallback={fallback}>
|
||||
<NotFoundRefHoverCard context={context}>
|
||||
{fallback ? (
|
||||
<InlineLinkAnchor
|
||||
href={fallback.href}
|
||||
contentRef={inline.data.ref}
|
||||
isExternal={checkIsExternalURL(fallback.href)}
|
||||
>
|
||||
<InlineLinkAnchor href={fallback.href} contentRef={inline.data.ref} isExternal>
|
||||
{inlinesElement}
|
||||
</InlineLinkAnchor>
|
||||
) : (
|
||||
@@ -58,7 +53,7 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
<InlineLinkAnchor
|
||||
href={resolved.href}
|
||||
contentRef={inline.data.ref}
|
||||
isExternal={isExternalLink(inline, resolved)}
|
||||
isExternal={inline.data.ref.kind === 'url'}
|
||||
>
|
||||
{inlinesElement}
|
||||
</InlineLinkAnchor>
|
||||
@@ -126,7 +121,7 @@ function InlineLinkTooltipWrapper(props: {
|
||||
|
||||
let breadcrumbs = resolved.ancestors ?? [];
|
||||
const isMailto = resolved.href.startsWith('mailto:');
|
||||
const isExternal = isExternalLink(inline, resolved);
|
||||
const isExternal = inline.data.ref.kind === 'url';
|
||||
const isSamePage = inline.data.ref.kind === 'anchor' && inline.data.ref.page === undefined;
|
||||
|
||||
if (isMailto) {
|
||||
@@ -166,10 +161,3 @@ function InlineLinkTooltipWrapper(props: {
|
||||
</InlineLinkTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A URL link resolved to a path in the site is internal, even though its ref is a URL.
|
||||
*/
|
||||
function isExternalLink(inline: DocumentInlineLink, resolved: ResolvedContentRef): boolean {
|
||||
return inline.data.ref.kind === 'url' && checkIsExternalURL(resolved.href);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ import { Icon } from '@gitbook/icons';
|
||||
|
||||
import type { DocumentContextProps } from '../DocumentView';
|
||||
import { HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives';
|
||||
import { getSpaceLanguage, t, tString } from '@/intl/server';
|
||||
import { getSpaceLanguage, tString } from '@/intl/server';
|
||||
import { defaultLanguage } from '@/intl/translations';
|
||||
import type { ResolvedContentRef } from '@/lib/references';
|
||||
import { checkIsExternalURL } from '@/lib/urls';
|
||||
|
||||
/**
|
||||
* Hover card displayed for a link not found.
|
||||
@@ -13,17 +11,13 @@ import { checkIsExternalURL } from '@/lib/urls';
|
||||
export async function NotFoundRefHoverCard(
|
||||
props: DocumentContextProps & {
|
||||
children: React.ReactNode;
|
||||
/** Where the link goes instead, named in the card when it stays on the site. */
|
||||
fallback?: ResolvedContentRef | null;
|
||||
}
|
||||
) {
|
||||
const {
|
||||
context: { contentContext },
|
||||
children,
|
||||
fallback,
|
||||
} = props;
|
||||
const language = contentContext ? await getSpaceLanguage(contentContext) : defaultLanguage;
|
||||
const destination = fallback && !checkIsExternalURL(fallback.href) ? fallback.text : null;
|
||||
return (
|
||||
<HoverCardRoot>
|
||||
<HoverCardTrigger>{children}</HoverCardTrigger>
|
||||
@@ -33,20 +27,6 @@ export async function NotFoundRefHoverCard(
|
||||
<h5 className="font-semibold">{tString(language, 'notfound_title')}</h5>
|
||||
</div>
|
||||
<p className="text-sm text-tint">{tString(language, 'notfound_link')}</p>
|
||||
{destination ? (
|
||||
<p className="mt-1 flex items-center gap-1.5 text-sm text-tint">
|
||||
<Icon icon="arrow-right" className="size-3 shrink-0 text-tint-subtle" />
|
||||
<span>
|
||||
{t(
|
||||
language,
|
||||
'notfound_link_opens',
|
||||
<span className="font-semibold text-tint-strong">
|
||||
{destination}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</HoverCard>
|
||||
</HoverCardRoot>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useMemo } from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useIsMounted } from '../hooks/useIsMounted';
|
||||
import { Button, type ButtonProps } from '../primitives/Button';
|
||||
import { DropdownMenuItem } from '../primitives/DropdownMenu';
|
||||
import { Link, type LinkInsightsProps, type LinkProps } from '../primitives/Link';
|
||||
@@ -16,9 +17,17 @@ function useSiteAuthLoginHrefWithLocation(href: string) {
|
||||
const rawPathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const currentSearch = searchParams?.toString();
|
||||
const pathname = rawPathname ?? '/';
|
||||
// On the server, usePathname() returns the internal rewritten route (/sites/…, which includes
|
||||
// the site API token), so the location is only added once mounted.
|
||||
// https://nextjs.org/docs/app/api-reference/functions/use-pathname#avoid-hydration-mismatch-with-rewrites
|
||||
const isMounted = useIsMounted();
|
||||
const pathname = isMounted ? (rawPathname ?? '/') : null;
|
||||
|
||||
return useMemo(() => {
|
||||
if (pathname === null) {
|
||||
return href;
|
||||
}
|
||||
|
||||
const baseURL = typeof window !== 'undefined' ? window.location.origin : 'http://localhost';
|
||||
const resolved = URL.canParse(href) ? new URL(href) : new URL(href, baseURL);
|
||||
const siteBasePath = removeTrailingSlash(
|
||||
|
||||
@@ -76,7 +76,6 @@ export const ar: TranslationLanguage = {
|
||||
edit: 'تحرير',
|
||||
notfound_title: 'الصفحة غير موجودة',
|
||||
notfound_link: 'يشير هذا الرابط إلى صفحة تمت إزالتها أو لم تعد موجودة.',
|
||||
notfound_link_opens: 'يفتح: ${1}',
|
||||
notfound: 'الصفحة التي تبحث عنها غير موجودة.',
|
||||
notfound_adaptive_title: 'الصفحة غير متاحة',
|
||||
notfound_adaptive: 'قد تكون هذه الصفحة موجودة، لكن قد تحتاج إلى تسجيل الدخول للوصول إليها.',
|
||||
|
||||
@@ -77,7 +77,6 @@ export const bg: TranslationLanguage = {
|
||||
edit: 'Редактиране',
|
||||
notfound_title: 'Страницата не е намерена',
|
||||
notfound_link: 'Тази връзка сочи към страница, която е премахната или вече не съществува.',
|
||||
notfound_link_opens: 'Отваря: ${1}',
|
||||
notfound: 'Страницата, която търсите, не съществува.',
|
||||
notfound_adaptive_title: 'Страницата не е налична',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -76,7 +76,6 @@ export const cs: TranslationLanguage = {
|
||||
edit: 'Upravit',
|
||||
notfound_title: 'Stránka nenalezena',
|
||||
notfound_link: 'Tento odkaz vede na stránku, která byla odstraněna nebo již neexistuje.',
|
||||
notfound_link_opens: 'Otevře: ${1}',
|
||||
notfound: 'Stránka, kterou hledáte, neexistuje.',
|
||||
notfound_adaptive_title: 'Stránka není dostupná',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -76,7 +76,6 @@ export const da: TranslationLanguage = {
|
||||
edit: 'Rediger',
|
||||
notfound_title: 'Siden blev ikke fundet',
|
||||
notfound_link: 'Dette link peger på en side, der er blevet fjernet eller ikke længere findes.',
|
||||
notfound_link_opens: 'Åbner ${1}',
|
||||
notfound: 'Siden, du leder efter, findes ikke.',
|
||||
notfound_adaptive_title: 'Siden er ikke tilgængelig',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -79,7 +79,6 @@ export const de: TranslationLanguage = {
|
||||
notfound_title: 'Seite nicht gefunden',
|
||||
notfound_link:
|
||||
'Dieser Link verweist auf eine Seite, die entfernt wurde oder nicht mehr existiert.',
|
||||
notfound_link_opens: 'Öffnet ${1}',
|
||||
notfound: 'Die gesuchte Seite existiert nicht.',
|
||||
notfound_adaptive_title: 'Seite nicht verfügbar',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const el: TranslationLanguage = {
|
||||
edit: 'Επεξεργασία',
|
||||
notfound_title: 'Η σελίδα δεν βρέθηκε',
|
||||
notfound_link: 'Αυτός ο σύνδεσμος οδηγεί σε μια σελίδα που έχει αφαιρεθεί ή δεν υπάρχει πλέον.',
|
||||
notfound_link_opens: 'Ανοίγει: ${1}',
|
||||
notfound: 'Η σελίδα που αναζητάτε δεν υπάρχει.',
|
||||
notfound_adaptive_title: 'Η σελίδα δεν είναι διαθέσιμη',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -74,7 +74,6 @@ export const en = {
|
||||
edit: 'Edit',
|
||||
notfound_title: 'Page not found',
|
||||
notfound_link: 'This link points to a page that has been removed or no longer exists.',
|
||||
notfound_link_opens: 'Opens ${1}',
|
||||
notfound: "The page you're looking for doesn't exist.",
|
||||
notfound_adaptive_title: 'Page unavailable',
|
||||
notfound_adaptive: 'This page may exist, but you may need to log in to access it.',
|
||||
|
||||
@@ -78,7 +78,6 @@ export const es: TranslationLanguage = {
|
||||
edit: 'Editar',
|
||||
notfound_title: 'Página no encontrada',
|
||||
notfound_link: 'Este enlace apunta a una página que ha sido eliminada o ya no existe.',
|
||||
notfound_link_opens: 'Abre ${1}',
|
||||
notfound: 'La página que buscas no existe.',
|
||||
notfound_adaptive_title: 'Página no disponible',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -76,7 +76,6 @@ export const et: TranslationLanguage = {
|
||||
edit: 'Muuda',
|
||||
notfound_title: 'Lehte ei leitud',
|
||||
notfound_link: 'See link viitab lehele, mis on eemaldatud või mida enam ei eksisteeri.',
|
||||
notfound_link_opens: 'Avab: ${1}',
|
||||
notfound: 'Lehte, mida otsite, ei eksisteeri.',
|
||||
notfound_adaptive_title: 'Leht pole saadaval',
|
||||
notfound_adaptive: 'See leht võib olemas olla, kuid juurdepääsuks võib olla vaja sisse logida.',
|
||||
|
||||
@@ -77,7 +77,6 @@ export const fi: TranslationLanguage = {
|
||||
edit: 'Muokkaa',
|
||||
notfound_title: 'Sivua ei löytynyt',
|
||||
notfound_link: 'Tämä linkki osoittaa sivulle, joka on poistettu tai jota ei enää ole.',
|
||||
notfound_link_opens: 'Avaa: ${1}',
|
||||
notfound: 'Etsimääsi sivua ei ole olemassa.',
|
||||
notfound_adaptive_title: 'Sivu ei ole saatavilla',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const fr: TranslationLanguage = {
|
||||
edit: 'Modifier',
|
||||
notfound_title: 'Page introuvable',
|
||||
notfound_link: "Ce lien pointe vers une page qui a été supprimée ou n'existe plus.",
|
||||
notfound_link_opens: 'Ouvre ${1}',
|
||||
notfound: 'La page que vous cherchez n’existe pas.',
|
||||
notfound_adaptive_title: 'Page inaccessible',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -76,7 +76,6 @@ export const he: TranslationLanguage = {
|
||||
edit: 'עריכה',
|
||||
notfound_title: 'הדף לא נמצא',
|
||||
notfound_link: 'קישור זה מפנה לדף שהוסר או אינו קיים עוד.',
|
||||
notfound_link_opens: 'פותח: ${1}',
|
||||
notfound: 'הדף שחיפשת אינו קיים.',
|
||||
notfound_adaptive_title: 'הדף אינו זמין',
|
||||
notfound_adaptive: 'ייתכן שהדף קיים, אך ייתכן שעליך להתחבר כדי לגשת אליו.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const hi: TranslationLanguage = {
|
||||
edit: 'संपादित करें',
|
||||
notfound_title: 'पृष्ठ नहीं मिला',
|
||||
notfound_link: 'यह लिंक ऐसे पृष्ठ पर जाता है जिसे हटा दिया गया है या जो अब मौजूद नहीं है।',
|
||||
notfound_link_opens: '${1} खोलता है',
|
||||
notfound: 'आप जिस पृष्ठ को खोज रहे हैं वह मौजूद नहीं है।',
|
||||
notfound_adaptive_title: 'पृष्ठ उपलब्ध नहीं',
|
||||
notfound_adaptive: 'यह पृष्ठ मौजूद हो सकता है, लेकिन इसे देखने के लिए आपको लॉग इन करना पड़ सकता है।',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const hr: TranslationLanguage = {
|
||||
edit: 'Uredi',
|
||||
notfound_title: 'Stranica nije pronađena',
|
||||
notfound_link: 'Ova poveznica vodi na stranicu koja je uklonjena ili više ne postoji.',
|
||||
notfound_link_opens: 'Otvara: ${1}',
|
||||
notfound: 'Stranica koju tražite ne postoji.',
|
||||
notfound_adaptive_title: 'Stranica nije dostupna',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const hu: TranslationLanguage = {
|
||||
edit: 'Szerkesztés',
|
||||
notfound_title: 'Az oldal nem található',
|
||||
notfound_link: 'Ez a hivatkozás egy eltávolított vagy már nem létező oldalra mutat.',
|
||||
notfound_link_opens: 'Megnyitja: ${1}',
|
||||
notfound: 'A keresett oldal nem létezik.',
|
||||
notfound_adaptive_title: 'Az oldal nem érhető el',
|
||||
notfound_adaptive: 'Ez az oldal létezhet, de a hozzáféréshez lehet, hogy be kell jelentkeznie.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const id: TranslationLanguage = {
|
||||
edit: 'Edit',
|
||||
notfound_title: 'Halaman tidak ditemukan',
|
||||
notfound_link: 'Tautan ini mengarah ke halaman yang telah dihapus atau sudah tidak ada.',
|
||||
notfound_link_opens: 'Membuka ${1}',
|
||||
notfound: 'Halaman yang Anda cari tidak ada.',
|
||||
notfound_adaptive_title: 'Halaman tidak tersedia',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -78,7 +78,6 @@ export const it: TranslationLanguage = {
|
||||
edit: 'Modifica',
|
||||
notfound_title: 'Pagina non trovata',
|
||||
notfound_link: 'Questo link punta a una pagina che è stata rimossa o non esiste più.',
|
||||
notfound_link_opens: 'Apre ${1}',
|
||||
notfound: 'La pagina che cerchi non esiste.',
|
||||
notfound_adaptive_title: 'Pagina non disponibile',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const ja: TranslationLanguage = {
|
||||
edit: '編集',
|
||||
notfound_title: 'ページが見つかりません',
|
||||
notfound_link: 'このリンクは、削除されたか、もはや存在しないページを指しています。',
|
||||
notfound_link_opens: '${1} を開きます',
|
||||
notfound: 'お探しのページは存在しません。',
|
||||
notfound_adaptive_title: 'ページにアクセスできません',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const ko: TranslationLanguage = {
|
||||
edit: '수정',
|
||||
notfound_title: '페이지를 찾을 수 없음',
|
||||
notfound_link: '이 링크는 삭제되었거나 더 이상 존재하지 않는 페이지를 가리킵니다.',
|
||||
notfound_link_opens: '${1}(으)로 이동',
|
||||
notfound: '찾으시는 페이지가 존재하지 않습니다.',
|
||||
notfound_adaptive_title: '페이지에 접근할 수 없음',
|
||||
notfound_adaptive: '이 페이지는 존재할 수 있지만, 접근하려면 로그인해야 할 수 있습니다.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const lt: TranslationLanguage = {
|
||||
edit: 'Redaguoti',
|
||||
notfound_title: 'Puslapis nerastas',
|
||||
notfound_link: 'Ši nuoroda veda į puslapį, kuris buvo pašalintas arba nebeegzistuoja.',
|
||||
notfound_link_opens: 'Atidaro: ${1}',
|
||||
notfound: 'Puslapis, kurio ieškote, neegzistuoja.',
|
||||
notfound_adaptive_title: 'Puslapis nepasiekiamas',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -76,7 +76,6 @@ export const lv: TranslationLanguage = {
|
||||
edit: 'Rediģēt',
|
||||
notfound_title: 'Lapa nav atrasta',
|
||||
notfound_link: 'Šī saite norāda uz lapu, kas ir noņemta vai vairs nepastāv.',
|
||||
notfound_link_opens: 'Atver: ${1}',
|
||||
notfound: 'Meklētā lapa nepastāv.',
|
||||
notfound_adaptive_title: 'Lapa nav pieejama',
|
||||
notfound_adaptive: 'Šī lapa var pastāvēt, bet, iespējams, jums jāpierakstās, lai tai piekļūtu.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const ms: TranslationLanguage = {
|
||||
edit: 'Edit',
|
||||
notfound_title: 'Halaman tidak ditemui',
|
||||
notfound_link: 'Pautan ini menghala ke halaman yang telah dialih keluar atau tidak lagi wujud.',
|
||||
notfound_link_opens: 'Membuka ${1}',
|
||||
notfound: 'Halaman yang anda cari tidak wujud.',
|
||||
notfound_adaptive_title: 'Halaman tidak tersedia',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -78,7 +78,6 @@ export const nl: TranslationLanguage = {
|
||||
edit: 'Bewerken',
|
||||
notfound_title: 'Pagina niet gevonden',
|
||||
notfound_link: 'Deze link verwijst naar een pagina die is verwijderd of niet meer bestaat.',
|
||||
notfound_link_opens: 'Opent ${1}',
|
||||
notfound: 'De pagina die je zoekt, bestaat niet.',
|
||||
notfound_adaptive_title: 'Pagina niet beschikbaar',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -78,7 +78,6 @@ export const no: TranslationLanguage = {
|
||||
notfound_title: 'Siden ble ikke funnet',
|
||||
notfound_link:
|
||||
'Denne lenken peker til en side som har blitt fjernet eller ikke lenger eksisterer.',
|
||||
notfound_link_opens: 'Åpner ${1}',
|
||||
notfound: 'Siden du leter etter eksisterer ikke.',
|
||||
notfound_adaptive_title: 'Siden er ikke tilgjengelig',
|
||||
notfound_adaptive: 'Denne siden kan finnes, men du må kanskje logge inn for å få tilgang.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const pl: TranslationLanguage = {
|
||||
edit: 'Edytuj',
|
||||
notfound_title: 'Nie znaleziono strony',
|
||||
notfound_link: 'Ten link prowadzi do strony, która została usunięta lub już nie istnieje.',
|
||||
notfound_link_opens: 'Otwiera: ${1}',
|
||||
notfound: 'Strona, której szukasz, nie istnieje.',
|
||||
notfound_adaptive_title: 'Strona niedostępna',
|
||||
notfound_adaptive: 'Ta strona może istnieć, ale dostęp do niej może wymagać zalogowania.',
|
||||
|
||||
@@ -78,7 +78,6 @@ export const pt_br: TranslationLanguage = {
|
||||
edit: 'Editar',
|
||||
notfound_title: 'Página não encontrada',
|
||||
notfound_link: 'Este link aponta para uma página que foi removida ou não existe mais.',
|
||||
notfound_link_opens: 'Abre ${1}',
|
||||
notfound: 'A página que você está procurando não existe.',
|
||||
notfound_adaptive_title: 'Página indisponível',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const pt: TranslationLanguage = {
|
||||
edit: 'Editar',
|
||||
notfound_title: 'Página não encontrada',
|
||||
notfound_link: 'Esta ligação aponta para uma página que foi removida ou já não existe.',
|
||||
notfound_link_opens: 'Abre ${1}',
|
||||
notfound: 'A página que procura não existe.',
|
||||
notfound_adaptive_title: 'Página indisponível',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const ro: TranslationLanguage = {
|
||||
edit: 'Editează',
|
||||
notfound_title: 'Pagina nu a fost găsită',
|
||||
notfound_link: 'Acest link indică o pagină care a fost eliminată sau nu mai există.',
|
||||
notfound_link_opens: 'Deschide ${1}',
|
||||
notfound: 'Pagina pe care o cauți nu există.',
|
||||
notfound_adaptive_title: 'Pagina nu este disponibilă',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -78,7 +78,6 @@ export const ru: TranslationLanguage = {
|
||||
edit: 'Редактировать',
|
||||
notfound_title: 'Страница не найдена',
|
||||
notfound_link: 'Эта ссылка ведёт на страницу, которая была удалена или больше не существует.',
|
||||
notfound_link_opens: 'Откроется: ${1}',
|
||||
notfound: 'Страница, которую вы ищете, не существует.',
|
||||
notfound_adaptive_title: 'Страница недоступна',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const sk: TranslationLanguage = {
|
||||
edit: 'Upraviť',
|
||||
notfound_title: 'Stránka sa nenašla',
|
||||
notfound_link: 'Tento odkaz smeruje na stránku, ktorá bola odstránená alebo už neexistuje.',
|
||||
notfound_link_opens: 'Otvorí: ${1}',
|
||||
notfound: 'Stránka, ktorú hľadáte, neexistuje.',
|
||||
notfound_adaptive_title: 'Stránka nie je dostupná',
|
||||
notfound_adaptive:
|
||||
|
||||
@@ -77,7 +77,6 @@ export const sl: TranslationLanguage = {
|
||||
edit: 'Uredi',
|
||||
notfound_title: 'Strani ni bilo mogoče najti',
|
||||
notfound_link: 'Ta povezava kaže na stran, ki je bila odstranjena ali ne obstaja več.',
|
||||
notfound_link_opens: 'Odpre: ${1}',
|
||||
notfound: 'Stran, ki jo iščete, ne obstaja.',
|
||||
notfound_adaptive_title: 'Stran ni na voljo',
|
||||
notfound_adaptive: 'Ta stran morda obstaja, vendar se boste za dostop morda morali prijaviti.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const sv: TranslationLanguage = {
|
||||
edit: 'Redigera',
|
||||
notfound_title: 'Sidan hittades inte',
|
||||
notfound_link: 'Den här länken pekar på en sida som har tagits bort eller inte längre finns.',
|
||||
notfound_link_opens: 'Öppnar ${1}',
|
||||
notfound: 'Sidan du letar efter finns inte.',
|
||||
notfound_adaptive_title: 'Sidan är inte tillgänglig',
|
||||
notfound_adaptive: 'Sidan kan finnas, men du kan behöva logga in för att få åtkomst.',
|
||||
|
||||
@@ -75,7 +75,6 @@ export const th: TranslationLanguage = {
|
||||
edit: 'แก้ไข',
|
||||
notfound_title: 'ไม่พบหน้า',
|
||||
notfound_link: 'ลิงก์นี้ชี้ไปยังหน้าที่ถูกลบหรือไม่มีอยู่อีกต่อไป',
|
||||
notfound_link_opens: 'เปิด ${1}',
|
||||
notfound: 'หน้าที่คุณกำลังค้นหาไม่มีอยู่',
|
||||
notfound_adaptive_title: 'หน้าไม่พร้อมใช้งาน',
|
||||
notfound_adaptive: 'หน้านี้อาจมีอยู่ แต่คุณอาจต้องเข้าสู่ระบบเพื่อเข้าถึง',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const tr: TranslationLanguage = {
|
||||
edit: 'Düzenle',
|
||||
notfound_title: 'Sayfa bulunamadı',
|
||||
notfound_link: 'Bu bağlantı kaldırılmış veya artık mevcut olmayan bir sayfaya işaret ediyor.',
|
||||
notfound_link_opens: '${1} açılır',
|
||||
notfound: 'Aradığınız sayfa mevcut değil.',
|
||||
notfound_adaptive_title: 'Sayfa kullanılamıyor',
|
||||
notfound_adaptive: 'Bu sayfa mevcut olabilir, ancak erişmek için oturum açmanız gerekebilir.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const uk: TranslationLanguage = {
|
||||
edit: 'Редагувати',
|
||||
notfound_title: 'Сторінку не знайдено',
|
||||
notfound_link: 'Це посилання веде на сторінку, яку видалено або якої більше не існує.',
|
||||
notfound_link_opens: 'Відкриється: ${1}',
|
||||
notfound: 'Сторінка, яку ви шукаєте, не існує.',
|
||||
notfound_adaptive_title: 'Сторінка недоступна',
|
||||
notfound_adaptive: 'Ця сторінка може існувати, але для доступу до неї може знадобитися вхід.',
|
||||
|
||||
@@ -76,7 +76,6 @@ export const vi: TranslationLanguage = {
|
||||
edit: 'Chỉnh sửa',
|
||||
notfound_title: 'Không tìm thấy trang',
|
||||
notfound_link: 'Liên kết này trỏ đến một trang đã bị xóa hoặc không còn tồn tại.',
|
||||
notfound_link_opens: 'Mở ${1}',
|
||||
notfound: 'Trang bạn đang tìm kiếm không tồn tại.',
|
||||
notfound_adaptive_title: 'Trang không khả dụng',
|
||||
notfound_adaptive: 'Trang này có thể tồn tại, nhưng bạn có thể cần đăng nhập để truy cập.',
|
||||
|
||||
@@ -74,7 +74,6 @@ export const yue: TranslationLanguage = {
|
||||
edit: '編輯',
|
||||
notfound_title: '搵唔到頁面',
|
||||
notfound_link: '呢條連結指向嘅頁面已被移除或已經唔存在。',
|
||||
notfound_link_opens: '開啟 ${1}',
|
||||
notfound: '你搵緊嘅頁面唔存在。',
|
||||
notfound_adaptive_title: '頁面無法使用',
|
||||
notfound_adaptive: '呢個頁面可能存在,但你可能需要登入先可以存取。',
|
||||
|
||||
@@ -74,7 +74,6 @@ export const zh_tw: TranslationLanguage = {
|
||||
edit: '編輯',
|
||||
notfound_title: '找不到頁面',
|
||||
notfound_link: '此連結指向的頁面已被移除或不再存在。',
|
||||
notfound_link_opens: '開啟 ${1}',
|
||||
notfound: '您要尋找的頁面不存在。',
|
||||
notfound_adaptive_title: '頁面無法使用',
|
||||
notfound_adaptive: '此頁面可能存在,但您可能需要登入才能存取。',
|
||||
|
||||
@@ -75,7 +75,6 @@ export const zh: TranslationLanguage = {
|
||||
edit: '编辑',
|
||||
notfound_title: '页面未找到',
|
||||
notfound_link: '此链接指向已被删除或不再存在的页面。',
|
||||
notfound_link_opens: '打开 ${1}',
|
||||
notfound: '您要找的页面不存在。',
|
||||
notfound_adaptive_title: '页面无法访问',
|
||||
notfound_adaptive: '该页面可能存在,但您可能需要登录后才能访问。',
|
||||
|
||||
@@ -2,11 +2,7 @@ import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import type { Revision, RevisionPageDocument, SiteSpace, Space } from '@gitbook/api';
|
||||
|
||||
import {
|
||||
resolveContentRef,
|
||||
resolveContentRefFallback,
|
||||
resolveStringContentRef,
|
||||
} from './references';
|
||||
import { resolveContentRef, resolveStringContentRef } from './references';
|
||||
import type { GitBookAnyContext } from '@/lib/context';
|
||||
import type { GitBookDataFetcher } from '@/lib/data';
|
||||
import { createLinker } from '@/lib/links';
|
||||
@@ -743,78 +739,130 @@ describe('resolveContentRef for direct space links', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveContentRef for application URLs into the site', () => {
|
||||
const alerts = {
|
||||
object: 'space',
|
||||
id: 'space-alerts',
|
||||
title: 'Alerts',
|
||||
urls: {
|
||||
app: 'https://app.gitbook.com/o/org/s/space-alerts/',
|
||||
published: 'https://docs.example.com/analytics-alerts/',
|
||||
},
|
||||
} as unknown as Space;
|
||||
const alertsSiteSpace = {
|
||||
object: 'site-space',
|
||||
id: 'site-space-alerts',
|
||||
path: 'analytics-alerts',
|
||||
space: alerts,
|
||||
title: 'Alerts',
|
||||
urls: { published: 'https://docs.example.com/analytics-alerts/' },
|
||||
} as unknown as SiteSpace;
|
||||
describe('resolveContentRef for a page ref whose page was re-created', () => {
|
||||
function buildPage(id: string, title: string, path: string): RevisionPageDocument {
|
||||
return {
|
||||
object: 'page',
|
||||
id,
|
||||
type: 'document',
|
||||
kind: 'sheet',
|
||||
title,
|
||||
path,
|
||||
slug: path.split('/').pop(),
|
||||
pages: [],
|
||||
tags: [],
|
||||
layout: {},
|
||||
urls: { app: 'https://app.gitbook.com/page' },
|
||||
} as unknown as RevisionPageDocument;
|
||||
}
|
||||
|
||||
const context = {
|
||||
linker: createLinker({ host: 'docs.example.com', spaceBasePath: '/', siteBasePath: '/' }),
|
||||
space: { id: 'space-notes' },
|
||||
site: { object: 'site', id: 'site-1' },
|
||||
sections: null,
|
||||
structure: { type: 'siteSpaces', structure: [alertsSiteSpace] },
|
||||
} as unknown as GitBookAnyContext;
|
||||
function buildRevision(id: string, pages: RevisionPageDocument[]): Revision {
|
||||
return {
|
||||
object: 'revision',
|
||||
id,
|
||||
type: 'edits',
|
||||
pages,
|
||||
files: [],
|
||||
reusableContents: [],
|
||||
tags: [],
|
||||
parents: [],
|
||||
createdAt: '',
|
||||
urls: { app: '' },
|
||||
} as unknown as Revision;
|
||||
}
|
||||
|
||||
it('keeps the page path and anchor of an application URL into a site space', async () => {
|
||||
const result = await resolveContentRef(
|
||||
{
|
||||
kind: 'url',
|
||||
url: 'https://app.gitbook.com/o/org/s/space-alerts/alerts-by-name/amsi-bypass#rules',
|
||||
function buildSpace(id: string): Space {
|
||||
return {
|
||||
object: 'space',
|
||||
id,
|
||||
title: id,
|
||||
organization: 'org',
|
||||
revision: `rev-${id}`,
|
||||
urls: {
|
||||
location: `https://api.gitbook.com/spaces/${id}`,
|
||||
app: `https://app.gitbook.com/o/org/s/${id}/`,
|
||||
published: `https://${id}.gitbook.io/`,
|
||||
},
|
||||
context
|
||||
} as unknown as Space;
|
||||
}
|
||||
|
||||
const recreated = buildPage('page-new', 'Detector', 'alerts-by-name/detector');
|
||||
const other = buildPage('page-other', 'Other', 'alerts-by-name/other');
|
||||
const alertsRevision = buildRevision('rev-alerts', [recreated, other]);
|
||||
const alerts = buildSpace('alerts');
|
||||
const notes = buildSpace('notes');
|
||||
|
||||
const dataFetcher = {
|
||||
getSpace: async ({ spaceId }: { spaceId: string }) =>
|
||||
spaceId === 'alerts'
|
||||
? { data: alerts }
|
||||
: { error: { code: 404, message: 'Not found' } },
|
||||
getRevision: async ({ spaceId }: { spaceId: string }) =>
|
||||
spaceId === 'alerts'
|
||||
? { data: alertsRevision }
|
||||
: { error: { code: 404, message: 'Not found' } },
|
||||
getChangeRequest: async () => ({ error: { code: 404, message: 'Not found' } }),
|
||||
withToken: function () {
|
||||
return this;
|
||||
},
|
||||
} as unknown as GitBookDataFetcher;
|
||||
|
||||
function buildContext(space: Space, revision: Revision): GitBookAnyContext {
|
||||
return {
|
||||
dataFetcher,
|
||||
linker: createLinker({
|
||||
host: 'docs.example.com',
|
||||
spaceBasePath: '/',
|
||||
siteBasePath: '/',
|
||||
}),
|
||||
organizationId: 'org',
|
||||
space,
|
||||
revision,
|
||||
revisionId: revision.id,
|
||||
changeRequest: null,
|
||||
shareKey: undefined,
|
||||
} as unknown as GitBookAnyContext;
|
||||
}
|
||||
|
||||
// Refs only get a typed `path` once @gitbook/api ships it, so the tests add it through a cast.
|
||||
const withPath = (ref: object, path: string) => ({ ...ref, path }) as never;
|
||||
|
||||
it('finds the page at the recorded path when its ID no longer exists', async () => {
|
||||
const result = await resolveContentRef(
|
||||
withPath({ kind: 'page', page: 'page-deleted' }, 'alerts-by-name/detector'),
|
||||
buildContext(alerts, alertsRevision)
|
||||
);
|
||||
|
||||
expect(result?.href).toBe('/analytics-alerts/alerts-by-name/amsi-bypass#rules');
|
||||
expect(result?.text).toBe('Detector');
|
||||
});
|
||||
|
||||
it('leaves an application URL into a space outside the site untouched', async () => {
|
||||
const url = 'https://app.gitbook.com/o/org/s/space-elsewhere/alerts-by-name/amsi-bypass';
|
||||
|
||||
const result = await resolveContentRef({ kind: 'url', url }, context);
|
||||
|
||||
expect(result?.href).toBe(url);
|
||||
});
|
||||
|
||||
it('leaves an application URL into a change request untouched', async () => {
|
||||
const url = 'https://app.gitbook.com/o/org/s/space-alerts/~/changes/1/alerts-by-name';
|
||||
|
||||
const result = await resolveContentRef({ kind: 'url', url }, context);
|
||||
|
||||
expect(result?.href).toBe(url);
|
||||
});
|
||||
|
||||
it('falls back to the site space, not the application, for a page ref that no longer resolves', () => {
|
||||
const fallback = resolveContentRefFallback(
|
||||
{ kind: 'page', space: 'space-alerts', page: 'page-deleted' },
|
||||
context
|
||||
it('finds the page at the recorded path in another space', async () => {
|
||||
const result = await resolveContentRef(
|
||||
withPath(
|
||||
{ kind: 'page', space: 'alerts', page: 'page-deleted' },
|
||||
'alerts-by-name/detector'
|
||||
),
|
||||
buildContext(notes, buildRevision('rev-notes', []))
|
||||
);
|
||||
|
||||
expect(fallback?.href).toBe('/analytics-alerts');
|
||||
expect(fallback?.text).toBe('Alerts');
|
||||
expect(result?.text).toBe('Detector');
|
||||
});
|
||||
|
||||
it('keeps the application fallback for a page ref into a space outside the site', () => {
|
||||
const fallback = resolveContentRefFallback(
|
||||
{ kind: 'page', space: 'space-elsewhere', page: 'page-deleted' },
|
||||
context
|
||||
it('keeps resolving by ID while the page still exists', async () => {
|
||||
const result = await resolveContentRef(
|
||||
withPath({ kind: 'page', page: 'page-other' }, 'alerts-by-name/detector'),
|
||||
buildContext(alerts, alertsRevision)
|
||||
);
|
||||
|
||||
expect(fallback?.href).toBe('https://app.gitbook.com/s/space-elsewhere');
|
||||
expect(fallback?.text).toBe('space');
|
||||
expect(result?.text).toBe('Other');
|
||||
});
|
||||
|
||||
it('does not resolve when neither the ID nor the recorded path exists', async () => {
|
||||
const result = await resolveContentRef(
|
||||
withPath({ kind: 'page', page: 'page-deleted' }, 'alerts-by-name/removed'),
|
||||
buildContext(alerts, alertsRevision)
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import type React from 'react';
|
||||
import type {
|
||||
ContentRef,
|
||||
JSONDocument,
|
||||
Revision,
|
||||
RevisionFile,
|
||||
RevisionPageDocument,
|
||||
RevisionReusableContent,
|
||||
@@ -16,7 +17,7 @@ import type { Filesystem } from '@gitbook/openapi-parser';
|
||||
|
||||
import { getGitBookAppHref } from './app';
|
||||
import { getBlockById, getBlockTitle } from './document';
|
||||
import { resolvePageId } from './pages';
|
||||
import { resolvePageId, resolvePagePath } from './pages';
|
||||
import {
|
||||
findSiteSpaceBy,
|
||||
getFallbackSiteSpacePath,
|
||||
@@ -40,16 +41,8 @@ import {
|
||||
getRevisionReusableContent,
|
||||
ignoreDataThrownError,
|
||||
} from '@/lib/data';
|
||||
import { GITBOOK_APP_URL } from '@/lib/env';
|
||||
import { type GitBookLinker, createLinker, linkerWithAbsoluteURLs } from '@/lib/links';
|
||||
|
||||
/**
|
||||
* Path of an application URL to a space's content: `/o/:org/s/:space/:pagePath`, optionally under
|
||||
* `/sites/:site`. A path under `~/` (a change request or revision) is not published content.
|
||||
*/
|
||||
const APP_SPACE_CONTENT_PATH =
|
||||
/^(?:\/o\/[^/]+)?(?:\/sites\/[^/]+)?\/s\/([^/]+)(?:\/(?!~)(.*?))?\/?$/;
|
||||
|
||||
export interface ResolvedContentRef {
|
||||
/** Text to render in the content ref */
|
||||
text: string;
|
||||
@@ -151,10 +144,9 @@ export async function resolveContentRef(
|
||||
|
||||
switch (contentRef.kind) {
|
||||
case 'url': {
|
||||
const href = resolveAppURLInSite(contentRef.url, context) ?? contentRef.url;
|
||||
return {
|
||||
href,
|
||||
text: href,
|
||||
href: contentRef.url,
|
||||
text: contentRef.url,
|
||||
active: false,
|
||||
};
|
||||
}
|
||||
@@ -201,7 +193,8 @@ export async function resolveContentRef(
|
||||
? activePage
|
||||
? { page: activePage, ancestors: [] }
|
||||
: undefined
|
||||
: resolvePageId(revision.pages, contentRef.page);
|
||||
: (resolvePageId(revision.pages, contentRef.page) ??
|
||||
resolvePageAtRecordedPath(revision, contentRef));
|
||||
|
||||
const page = resolvePageResult?.page;
|
||||
const ancestors =
|
||||
@@ -428,22 +421,28 @@ export function isContentRefInDifferentSpace<Ref extends ContentRef>(
|
||||
* Called if we can't resolve the content ref to have a potential fallback to display to the
|
||||
* user instead of not found.
|
||||
*/
|
||||
export function resolveContentRefFallback(
|
||||
contentRef: ContentRef,
|
||||
context: GitBookAnyContext | undefined
|
||||
): ResolvedContentRef | null {
|
||||
export function resolveContentRefFallback(contentRef: ContentRef): ResolvedContentRef | null {
|
||||
if ('space' in contentRef && contentRef.space) {
|
||||
const linker = context ? getLinkerForSpaceInSite(context, contentRef.space) : null;
|
||||
const inSite = context ? getBestTargetSpaceFromSite(context, contentRef.space) : undefined;
|
||||
return {
|
||||
href: linker?.toPathInSpace('') ?? getGitBookAppHref(`/s/${contentRef.space}`),
|
||||
text: linker && inSite ? getSpaceRefText(inSite, context?.locale) : 'space',
|
||||
href: getGitBookAppHref(`/s/${contentRef.space}`),
|
||||
text: 'space',
|
||||
active: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the page at the path recorded on a page or anchor ref, for when its page ID no longer
|
||||
* exists (the page was deleted and re-created at the same path).
|
||||
*/
|
||||
function resolvePageAtRecordedPath(revision: Revision, contentRef: ContentRef) {
|
||||
// TODO: read `contentRef.path` directly once @gitbook/api ships it on ContentRefPage and
|
||||
// ContentRefAnchor; until then the field is untyped.
|
||||
const { path } = contentRef as { path?: unknown };
|
||||
return typeof path === 'string' && path ? resolvePagePath(revision.pages, path) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to get the best possible target space while resolving a content ref.
|
||||
* It will try to return the space in the site context if it exists to avoid cross-site links.
|
||||
@@ -474,46 +473,6 @@ async function getBestTargetSpace(
|
||||
return fetchedSpace ? { space: fetchedSpace, siteSpace: null, siteSection: null } : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an application URL into a space of the current site to the same page path on the site, so
|
||||
* a link that was never resolved to a page doesn't send visitors to the app. The page isn't looked
|
||||
* up: one that doesn't exist lands on the site's not-found page.
|
||||
*/
|
||||
function resolveAppURLInSite(url: string, context: GitBookAnyContext): string | null {
|
||||
if (!URL.canParse(url)) {
|
||||
return null;
|
||||
}
|
||||
const parsed = new URL(url);
|
||||
if (parsed.origin !== new URL(GITBOOK_APP_URL).origin) {
|
||||
return null;
|
||||
}
|
||||
const match = parsed.pathname.match(APP_SPACE_CONTENT_PATH);
|
||||
const linker = match?.[1] ? getLinkerForSpaceInSite(context, match[1]) : null;
|
||||
if (!linker) {
|
||||
return null;
|
||||
}
|
||||
return linker.toPathForPagePath({
|
||||
path: match?.[2] ?? '',
|
||||
anchor: parsed.hash.slice(1) || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Linker for a space that is part of the current site, or null when it isn't.
|
||||
*/
|
||||
function getLinkerForSpaceInSite(
|
||||
context: GitBookAnyContext,
|
||||
spaceId: string
|
||||
): GitBookLinker | null {
|
||||
const target = getBestTargetSpaceFromSite(context, spaceId);
|
||||
if (!target?.siteSpace || !('site' in context)) {
|
||||
return null;
|
||||
}
|
||||
return context.linker.withOtherSiteSpace({
|
||||
spaceBasePath: getFallbackSiteSpacePath(context, target.siteSpace),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best target space for a content ref, from the current site.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user