diff --git a/.changeset/inline-link-tooltip-distinguish.md b/.changeset/inline-link-tooltip-distinguish.md new file mode 100644 index 000000000..3d1e9a0a1 --- /dev/null +++ b/.changeset/inline-link-tooltip-distinguish.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Improve the inline link hover popover: same-page anchor links now read as an on-page jump to a section instead of showing the current page's title, and external links show their domain with the full URL as sub-text. diff --git a/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx index 1623dcbb2..40a976a7a 100644 --- a/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx +++ b/packages/gitbook/src/components/DocumentView/InlineLink/InlineLink.tsx @@ -119,10 +119,11 @@ function InlineLinkTooltipWrapper(props: { }) { const { inline, language, resolved, children } = props; + const ref = inline.data.ref; let breadcrumbs = resolved.ancestors ?? []; const isMailto = resolved.href.startsWith('mailto:'); - const isExternal = inline.data.ref.kind === 'url'; - const isSamePage = inline.data.ref.kind === 'anchor' && inline.data.ref.page === undefined; + const isExternal = ref.kind === 'url'; + const isSamePage = ref.kind === 'anchor' && ref.page === undefined; if (isMailto) { resolved.text = resolved.text.split('mailto:')[1] ?? resolved.text; @@ -137,6 +138,13 @@ function InlineLinkTooltipWrapper(props: { label: tString(language, 'link_tooltip_external_link'), }, ]; + // Present the destination domain as the heading, keeping the full URL as sub-text, + // instead of surfacing the raw URL as if it were a page title. + const hostname = getHostname(resolved.href); + if (hostname) { + resolved.subText = resolved.text; + resolved.text = hostname; + } } else if (isSamePage) { breadcrumbs = [ { @@ -144,6 +152,16 @@ function InlineLinkTooltipWrapper(props: { icon: , }, ]; + // A same-page anchor is a jump within the current page, not a link to it, + // so avoid showing the current page's title as the destination. Fall back to the + // humanized anchor as the section label (the true section title isn't resolved here, + // as resolveAnchorText is intentionally disabled for performance). + if (ref.kind === 'anchor') { + const sectionLabel = humanizeAnchor(ref.anchor); + if (sectionLabel) { + resolved.text = sectionLabel; + } + } resolved.subText = undefined; } @@ -161,3 +179,27 @@ function InlineLinkTooltipWrapper(props: { ); } + +/** + * Extract the display hostname of an external URL, without the leading `www.`. + * Returns null if the URL cannot be parsed. + */ +function getHostname(href: string): string | null { + try { + return new URL(href).hostname.replace(/^www\./, ''); + } catch { + return null; + } +} + +/** + * Turn an anchor id (e.g. `installation-guide`) into a readable section label. + * Returns null when the anchor is empty. + */ +function humanizeAnchor(anchor: string): string | null { + const words = anchor.trim().replace(/[-_]+/g, ' ').trim(); + if (!words) { + return null; + } + return words.charAt(0).toUpperCase() + words.slice(1); +}