diff --git a/components/modules/announcements/Announcement.tsx b/components/modules/announcements/Announcement.tsx index 575f6b6..de165ff 100644 --- a/components/modules/announcements/Announcement.tsx +++ b/components/modules/announcements/Announcement.tsx @@ -38,6 +38,50 @@ const options = { validate: true, } +const isSafeUrl = (url: string): boolean => { + try { + const parsed = new URL(url, "https://placeholder.com") + return ["http:", "https:", "mailto:"].includes(parsed.protocol) + } catch { + return false + } +} + +const parseMarkdownLinks = (text: string): React.ReactNode[] => { + const regex = /\[([^\]]+)\]\(([^)]+)\)/g + const parts: React.ReactNode[] = [] + let lastIndex = 0 + let match: RegExpExecArray | null + + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)) + } + if (isSafeUrl(match[2])) { + parts.push( + + {match[1]} + + ) + } else { + parts.push(match[0]) + } + lastIndex = match.index + match[0].length + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)) + } + + return parts +} + const renderDate = (date: Date, subTitle?: string) => { const dateDisplay = getDateDisplay(date, 7) return ( @@ -71,7 +115,7 @@ const Announcement = ({ ...announcement }: IAnnouncement) => { {announcement.dates && announcement.dates.map((subDate) => renderDate(subDate.date, subDate.subTitle))} {announcement.description && ( - {announcement.description} + {parseMarkdownLinks(announcement.description)} )}