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, "
"); + return html; +} diff --git a/web/tsconfig.tsbuildinfo b/web/tsconfig.tsbuildinfo index 9e0c581..f719271 100644 --- a/web/tsconfig.tsbuildinfo +++ b/web/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/api/auth.ts","./src/api/chats.ts","./src/api/http.ts","./src/api/notifications.ts","./src/api/search.ts","./src/api/users.ts","./src/app/app.tsx","./src/chat/types.ts","./src/components/apperrorboundary.tsx","./src/components/authpanel.tsx","./src/components/chatinfopanel.tsx","./src/components/chatlist.tsx","./src/components/messagecomposer.tsx","./src/components/messagelist.tsx","./src/components/newchatpanel.tsx","./src/hooks/userealtime.ts","./src/pages/authpage.tsx","./src/pages/chatspage.tsx","./src/store/authstore.ts","./src/store/chatstore.ts","./src/utils/format.ts","./src/utils/ws.ts"],"version":"5.9.2"} \ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/api/auth.ts","./src/api/chats.ts","./src/api/http.ts","./src/api/notifications.ts","./src/api/search.ts","./src/api/users.ts","./src/app/app.tsx","./src/chat/types.ts","./src/components/apperrorboundary.tsx","./src/components/authpanel.tsx","./src/components/chatinfopanel.tsx","./src/components/chatlist.tsx","./src/components/messagecomposer.tsx","./src/components/messagelist.tsx","./src/components/newchatpanel.tsx","./src/hooks/userealtime.ts","./src/pages/authpage.tsx","./src/pages/chatspage.tsx","./src/store/authstore.ts","./src/store/chatstore.ts","./src/utils/format.ts","./src/utils/formatmessage.tsx","./src/utils/ws.ts"],"version":"5.9.2"} \ No newline at end of file