/** * Turn plain message text into safe HTML where URLs become clickable links that * open in a new tab. Used by the ticket history (RequestForm) and the staff chat * (Drawer.vue / pages/chat.vue) — anywhere user-entered text is shown and may * contain a link. * * Safety: every non-URL segment AND the link href/label are HTML-escaped, so the * result is safe to bind with `v-html` — the only markup we ever inject is the * `` tags we build ourselves. Newlines are left intact (callers render inside * a `white-space: pre-wrap` container). */ const escapeHtml = (s: string): string => s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') // http(s):// or bare www. — stop at whitespace / '<', and don't swallow trailing // punctuation (.,;:!?) or closing brackets/quotes that usually end a sentence. const URL_RE = /\b((?:https?:\/\/|www\.)[^\s<]+[^\s<.,;:!?)\]}'"])/gi export const linkifyHtml = (input: any): string => { const text = input == null ? '' : String(input) let out = '' let last = 0 URL_RE.lastIndex = 0 let m: RegExpExecArray | null while ((m = URL_RE.exec(text)) !== null) { out += escapeHtml(text.slice(last, m.index)) const url = m[0] const href = /^www\./i.test(url) ? `https://${url}` : url out += `${escapeHtml(url)}` last = m.index + url.length } out += escapeHtml(text.slice(last)) return out } export default linkifyHtml