diff --git a/web/src/components/MessageList.tsx b/web/src/components/MessageList.tsx index b781c98..0b56a53 100644 --- a/web/src/components/MessageList.tsx +++ b/web/src/components/MessageList.tsx @@ -5,6 +5,7 @@ import type { Message, MessageReaction } from "../chat/types"; import { useAuthStore } from "../store/authStore"; import { useChatStore } from "../store/chatStore"; import { formatTime } from "../utils/format"; +import { formatMessageHtml } from "../utils/formatMessage"; type ContextMenuState = { x: number; @@ -555,7 +556,7 @@ function renderContent(messageType: string, text: string | null) { ); } - return
{text}
; + return ; } function renderStatus(status: string | undefined): string { diff --git a/web/src/utils/formatMessage.tsx b/web/src/utils/formatMessage.tsx new file mode 100644 index 0000000..6223ac1 --- /dev/null +++ b/web/src/utils/formatMessage.tsx @@ -0,0 +1,32 @@ +function escapeHtml(input: string): string { + return input + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function sanitizeHref(input: string): string { + const normalized = input.trim(); + if (/^https?:\/\//i.test(normalized)) { + return normalized; + } + return "#"; +} + +export function formatMessageHtml(text: string): string { + let html = escapeHtml(text); + + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, label: string, href: string) => { + const safeHref = sanitizeHref(href); + return `${label}`; + }); + html = html.replace(/\*\*([^*]+)\*\*/g, "$1"); + html = html.replace(/\*([^*]+)\*/g, "$1"); + html = html.replace(/__([^_]+)__/g, "$1"); + html = html.replace(/`([^`]+)`/g, "$1");
+ html = html.replace(/\|\|([^|]+)\|\|/g, "$1");
+ html = html.replace(/\n/g, "