// https://mathiasbynens.be/demo/url-regex // https://gist.github.com/dperini/729294 import { escapeHTML } from './html' /// Remove scheme, www. and trailing slash for display purposes export function toHumanReadableUrl(url: string): string { return url.replace(/^(https?:\/\/(www\.)?|mailto:)/gi, '').replace(/\/$/, '') } // export const RX_URL = /((?:(?:https?|ftp):)?\/\/(?:\S+@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4])|(?:(?:[a-z0-9\u00A1-\uFFFF][\w\u00A1-\uFFFF-]{0,62})?[a-z0-9\u00A1-\uFFFF]\.)+[a-z\u00A1-\uFFFF]{2,}\.?)(?::\d{2,5})?(?:[/?#]\S*)?)/gi /** @deprecated use linkifyPlainText */ export function linkifyPlainTextSimple(text: string): string { return text .split(RX_URL) .map((part, i) => { const escapedPart = escapeHTML(part) return i % 2 ? `${toHumanReadableUrl( escapedPart, )}` : escapedPart }) .join('') } // Advanced implementation that also handles email addresses and simple URLs without protocol const RX_EMAIL = /((mailto:)?[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi const RX_SIMPLE_URL = /([a-zA-Z0-9]+\.[a-zA-Z]{2,})/gi function handleUrlString(part: string): string { var url = part var rests = [] // Remove trailing punctuation from URLs const urlWithoutPunctuation = url.replace(/[.,!?;:]*$/, '') rests.unshift(url.slice(urlWithoutPunctuation.length)) // Full URL with protocol if (url.includes('://')) { // Remove trailing closing brackets if unbalanced url = urlWithoutPunctuation const openingBrackets = (url.match(/[\(\[\{]/g) || []).length const closingBrackets = (url.match(/[\)\]\}]/g) || []).length if (closingBrackets > openingBrackets) { const diff = closingBrackets - openingBrackets const rx = new RegExp(`[\\)\\]\\}]{0,${diff}}$`, 'g') const urlWithoutTrailing = url.replace(rx, '') rests.unshift(url.slice(urlWithoutTrailing.length)) url = urlWithoutTrailing } } // Email address else if (url.includes('@')) { if (!url.startsWith('mailto:')) url = `mailto:${urlWithoutPunctuation}` } // Simple URL without protocol else { url = `https://${urlWithoutPunctuation}` } const escapedRest = escapeHTML(rests.join('')) const escapedHumanReadableUrl = escapeHTML(toHumanReadableUrl(url)) const escapedUrl = escapeHTML(url) const classInsecureHttp = url.startsWith('http://') ? ' class="_warn"' : '' return `${escapedHumanReadableUrl}${escapedRest}` } var rxJoined: RegExp | undefined = undefined export interface LinkifyOptions { } export function linkifyPlainText(text: string, options: LinkifyOptions = {}): string { if (rxJoined == null) { rxJoined = new RegExp(`((${RX_URL.source})|(${RX_EMAIL.source})|(${RX_SIMPLE_URL.source}))`, 'gi') } else { rxJoined.lastIndex = 0 } var index = 0 const parts: string[] = [] text.replace(rxJoined, (match, ...args) => { const offset = args[args.length - 2] const leading = text.slice(index, offset) ?? '' parts.push(escapeHTML(leading)) index = offset + match.length parts.push(handleUrlString(match)) return match }) return parts.join('') + escapeHTML(text.slice(index)) } export function linkifyPlainTextWithLineBreaks(text: string, options: LinkifyOptions = {}): string { return text.split('\n').map(l => linkifyPlainText(l, options)).join('
') } // export function encodeQuery(data: Record, filterValue?: (value: any) => boolean) { const pairs = [] for (let [key, value] of Object.entries(data)) { if (value != null) { if (!Array.isArray(value)) value = [value] for (const v of value) { if (filterValue && !filterValue(v)) continue else if (v == null) continue pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`) } } } return pairs.join('&') } export function parseQuery(queryString: string) { const query: any = {} while (queryString.startsWith('?')) { queryString = queryString.substring(1) } const pairs = queryString.split('&') for (let i = 0; i < pairs.length; i++) { const part = pairs[i] const idx = part.indexOf('=') if (part.length === 0) continue const key = idx < 0 ? part : decodeURIComponent(part.substring(0, idx)) const value = idx < 0 ? '' : decodeURIComponent(part.substring(idx + 1)) if (query[key] != null) { if (!Array.isArray(query[key])) query[key] = [query[key]] query[key].push(value) } else { query[key] = value } } return query }