diff --git a/packages/session-ui/component-tests/markdown.spec.ts b/packages/session-ui/component-tests/markdown.spec.ts index f6c28096648..42ea80ad595 100644 --- a/packages/session-ui/component-tests/markdown.spec.ts +++ b/packages/session-ui/component-tests/markdown.spec.ts @@ -1,4 +1,4 @@ -import type { Page } from "@playwright/test" +import type { Locator, Page } from "@playwright/test" import { expect, sourceURL, story } from "../../stories/playwright/story" const png = Buffer.from( @@ -318,8 +318,9 @@ story("shows a stable GitHub mark without changing link text or other sites", as await expect(github).toHaveAttribute("href", "https://github.com/anomalyco/opencode/pull/540") await expect(github).toHaveText("#540") const mark = github.locator(".markdown-link-favicon[data-github]") - expect(await mark.evaluate((icon) => getComputedStyle(icon, "::before").width)).toBe("14px") - expect(await mark.evaluate((icon) => getComputedStyle(icon, "::before").maskImage)).toContain("data:image/svg+xml") + const glyph = mark.locator(".markdown-link-favicon-glyph") + expect(await glyph.evaluate((icon) => getComputedStyle(icon).width)).toBe("14px") + expect(await glyph.evaluate((icon) => getComputedStyle(icon).maskImage)).toContain("data:image/svg+xml") await expect(mark.locator("img")).toHaveCount(0) await expect(markdown.getByRole("link", { name: "GitHub" }).locator(".markdown-link-favicon[data-github]")).toHaveCount( 1, @@ -360,8 +361,10 @@ story("keeps favicon space stable across loading and failure without fetching pr await expect(markdown).toHaveAttribute("data-markdown-ready", "") const docs = markdown.getByRole("link", { name: "docs" }) const image = docs.locator(".markdown-link-favicon img") + const globe = (link: Locator) => link.locator(".markdown-link-favicon-glyph") await expect.poll(() => requested.some((url) => url.includes("developer.mozilla.org"))).toBe(true) await expect(image).toHaveCSS("opacity", "0") + await expect(globe(docs)).toBeVisible() expect((await docs.locator(".markdown-link-favicon").boundingBox())?.width).toBe(14) // A late web font swap also changes the link width; measure after fonts settle. await page.evaluate(() => document.fonts.ready.then(() => undefined)) @@ -369,6 +372,7 @@ story("keeps favicon space stable across loading and failure without fetching pr release() await expect(image).toHaveAttribute("data-loaded", "") await expect(image).toHaveCSS("opacity", "1") + await expect(globe(docs)).toHaveCount(0) await expect(image).not.toHaveAttribute("role", "button") expect(await image.evaluate((favicon) => favicon.onclick)).toBeNull() expect(await docs.evaluate((link) => link.getBoundingClientRect().width)).toBe(width) @@ -384,6 +388,7 @@ story("keeps favicon space stable across loading and failure without fetching pr await expect( markdown.getByRole("link", { name: "missing" }).locator(".markdown-link-favicon img"), ).not.toHaveAttribute("data-loaded", "") + await expect(globe(markdown.getByRole("link", { name: "missing" }))).toBeVisible() await expect(markdown.getByRole("link", { name: "private" }).locator(".markdown-link-favicon img")).toHaveCount(0) await expect(markdown.getByRole("link", { name: "GitHub" }).locator(".markdown-link-favicon img")).toHaveCount(0) expect(requested).toHaveLength(2) diff --git a/packages/session-ui/src/components/markdown.css b/packages/session-ui/src/components/markdown.css index 847b23ac701..82dde48b017 100644 --- a/packages/session-ui/src/components/markdown.css +++ b/packages/session-ui/src/components/markdown.css @@ -153,8 +153,7 @@ margin-inline-end: 4px; } - .markdown-link-favicon::before { - content: ""; + .markdown-link-favicon-glyph { position: absolute; inset: 0; height: 14px; @@ -163,7 +162,7 @@ mask: url("./markdown-globe.svg") center / contain no-repeat; } - .markdown-link-favicon[data-github]::before { + .markdown-link-favicon[data-github] .markdown-link-favicon-glyph { mask-image: url("./markdown-github.svg"); } diff --git a/packages/session-ui/src/components/markdown.tsx b/packages/session-ui/src/components/markdown.tsx index f844a8d4d7a..377cfe571d1 100644 --- a/packages/session-ui/src/components/markdown.tsx +++ b/packages/session-ui/src/components/markdown.tsx @@ -361,6 +361,9 @@ function markExternalLinkFavicons(root: HTMLDivElement) { favicon.className = "markdown-link-favicon" favicon.setAttribute("aria-hidden", "true") const host = url.hostname.toLowerCase() + const glyph = document.createElement("span") + glyph.className = "markdown-link-favicon-glyph" + favicon.appendChild(glyph) if (host === "github.com") favicon.dataset.github = "" else if ( @@ -383,13 +386,19 @@ function markExternalLinkFavicons(root: HTMLDivElement) { }) } +// The globe stands in only until the favicon loads; removing it keeps transparent favicons from showing it through. +function showFavicon(image: HTMLImageElement) { + image.dataset.loaded = "" + image.parentElement?.querySelector(".markdown-link-favicon-glyph")?.remove() +} + function setupExternalLinkFavicons(root: HTMLDivElement) { const loaded = (event: Event) => { const image = event.target if (!(image instanceof HTMLImageElement) || !image.hasAttribute("data-markdown-favicon")) return - if (image.naturalWidth > 0) image.dataset.loaded = "" + if (image.naturalWidth > 0) showFavicon(image) } root.addEventListener("load", loaded, true) @@ -901,7 +910,7 @@ export function Markdown( images?.update(container) previewImages(container) container.querySelectorAll("img[data-markdown-favicon]").forEach((image) => { - if (image.complete && image.naturalWidth > 0) image.dataset.loaded = "" + if (image.complete && image.naturalWidth > 0) showFavicon(image) }) // A button gets the labels of the run that creates it, so the others only need new ones when the language changes.