diff --git a/.changeset/anchor-links-same-tab.md b/.changeset/anchor-links-same-tab.md new file mode 100644 index 0000000000..0c7e4b0ec1 --- /dev/null +++ b/.changeset/anchor-links-same-tab.md @@ -0,0 +1,5 @@ +--- +"emdash": patch +--- + +Fixes same-page anchor links (e.g. `#section`) in Portable Text content opening in a new tab. On-page jumps now always stay in the same tab, even when the link's "open in new window" option is set. diff --git a/packages/core/src/components/marks/Link.astro b/packages/core/src/components/marks/Link.astro index 130a661a3b..7fdcbf0fbb 100644 --- a/packages/core/src/components/marks/Link.astro +++ b/packages/core/src/components/marks/Link.astro @@ -19,7 +19,11 @@ export interface Props { const { node } = Astro.props; const href = sanitizeHref(node?.markDef?.href); -const blank = node?.markDef?.blank; +// Same-page anchor links (`#section`) are on-page jumps — opening them in a +// new tab is never desirable, so force same-tab regardless of the mark's +// `blank` flag. +const isSamePageAnchor = href.startsWith("#"); +const blank = !isSamePageAnchor && node?.markDef?.blank; --- html.match(/]*>/)?.[0] ?? ""; + +async function renderLink(markDef: Record) { + const c = await AstroContainer.create(); + return c.renderToString(Link, { + props: { node: { markDef } }, + slots: { default: "jump" }, + }); +} + +describe("Link mark: same-page anchor target handling", () => { + test("same-page anchor with blank=true stays in same tab", async () => { + const html = await renderLink({ + _type: "link", + _key: "k1", + href: "#section", + blank: true, + }); + const tag = anchorTag(html); + expect(tag).toContain('href="#section"'); + expect(tag).not.toContain('target="_blank"'); + expect(tag).not.toContain("noopener"); + }); + + test("external link with blank=true still opens in new tab", async () => { + const html = await renderLink({ + _type: "link", + _key: "k2", + href: "https://example.com", + blank: true, + }); + const tag = anchorTag(html); + expect(tag).toContain('href="https://example.com"'); + expect(tag).toContain('target="_blank"'); + expect(tag).toContain("noopener"); + }); + + test("same-page anchor without blank stays in same tab", async () => { + const html = await renderLink({ + _type: "link", + _key: "k3", + href: "#top", + blank: false, + }); + const tag = anchorTag(html); + expect(tag).toContain('href="#top"'); + expect(tag).not.toContain('target="_blank"'); + }); +});