diff --git a/src/components/SafeExternalLink.astro b/src/components/SafeExternalLink.astro new file mode 100644 index 0000000..06d3eb7 --- /dev/null +++ b/src/components/SafeExternalLink.astro @@ -0,0 +1,39 @@ +--- +import { toSafeExternalHref } from "../lib/community/shared-content"; + +interface Props { + href: string | null | undefined; + /** Tried under the same safety rules when href is rejected. */ + fallbackHref?: string | null; + /** What to render when no safe destination exists. */ + fallbackTag?: "span" | "div" | "none"; + /** Announce the new tab to screen readers inside the link. */ + announceNewTab?: boolean; + class?: string; +} + +const { + href, + fallbackHref, + fallbackTag = "span", + announceNewTab = false, + class: className, +} = Astro.props; + +const safeHref = toSafeExternalHref(href, { fallback: fallbackHref }); +const Tag = safeHref ? "a" : fallbackTag === "none" ? null : fallbackTag; +const linkAttributes = safeHref + ? { href: safeHref, rel: "noopener noreferrer", target: "_blank" } + : {}; +--- + +{ + Tag && ( + + + {safeHref && announceNewTab && ( + (opens in new tab) + )} + + ) +} diff --git a/src/components/content/PostCard.astro b/src/components/content/PostCard.astro index b65e55c..19f7242 100644 --- a/src/components/content/PostCard.astro +++ b/src/components/content/PostCard.astro @@ -1,5 +1,6 @@ --- import Avatar, { type AvatarSource } from "../Avatar.astro"; +import SafeExternalLink from "../SafeExternalLink.astro"; interface Props { title: string; @@ -47,7 +48,7 @@ const communityHue = hueFromString(source); ---
- + #{tag}} - + {hasActions && (
diff --git a/src/components/content/ShareableContentList.astro b/src/components/content/ShareableContentList.astro index fa49690..1c2f13b 100644 --- a/src/components/content/ShareableContentList.astro +++ b/src/components/content/ShareableContentList.astro @@ -1,6 +1,7 @@ --- import type { ShareCandidateList } from '../../lib/community/share-candidates'; import { actions } from 'astro:actions'; +import SafeExternalLink from '../SafeExternalLink.astro'; import ShareableContentIcon from './ShareableContentIcon.astro'; interface Props { @@ -77,10 +78,9 @@ function formatDate(date: Date | undefined): string {