import marked from "../third/marked/lib/marked"; import * as blueslip from "./blueslip"; const linkifier_map = new Map(); export let linkifier_list = []; function handleLinkifier(pattern, matches) { let url = linkifier_map.get(pattern); let current_group = 1; for (const match of matches) { const back_ref = "\\" + current_group; url = url.replace(back_ref, match); current_group += 1; } return url; } function python_to_js_linkifier(pattern, url) { // Converts a python named-group regex to a javascript-compatible numbered // group regex... with a regex! const named_group_re = /\(?P<([^>]+?)>/g; let match = named_group_re.exec(pattern); let current_group = 1; while (match) { const name = match[1]; // Replace named group with regular matching group pattern = pattern.replace("(?P<" + name + ">", "("); // Replace named reference in URL to numbered reference url = url.replace("%(" + name + ")s", "\\" + current_group); // Reset the RegExp state named_group_re.lastIndex = 0; match = named_group_re.exec(pattern); current_group += 1; } // Convert any python in-regex flags to RegExp flags let js_flags = "g"; const inline_flag_re = /\(\?([Limsux]+)\)/; match = inline_flag_re.exec(pattern); // JS regexes only support i (case insensitivity) and m (multiline) // flags, so keep those and ignore the rest if (match) { const py_flags = match[1].split(""); for (const flag of py_flags) { if ("im".includes(flag)) { js_flags += flag; } } pattern = pattern.replace(inline_flag_re, ""); } // Ideally we should have been checking that linkifiers // begin with certain characters but since there is no // support for negative lookbehind in javascript, we check // for this condition in `contains_backend_only_syntax()` // function. If the condition is satisfied then the message // is rendered locally, otherwise, we return false there and // message is rendered on the backend which has proper support // for negative lookbehind. pattern = pattern + /(?!\w)/.source; let final_regex = null; try { final_regex = new RegExp(pattern, js_flags); } catch (error) { // We have an error computing the generated regex syntax. // We'll ignore this linkifier for now, but log this // failure for debugging later. blueslip.error("python_to_js_linkifier: " + error.message); } return [final_regex, url]; } export function update_linkifier_rules(linkifiers) { // Update the marked parser with our particular set of linkifiers linkifier_map.clear(); linkifier_list = []; const marked_rules = []; for (const linkifier of linkifiers) { const [regex, final_url] = python_to_js_linkifier(linkifier.pattern, linkifier.url_format); if (!regex) { // Skip any linkifiers that could not be converted continue; } linkifier_map.set(regex, final_url); linkifier_list.push({ pattern: regex, url_format: final_url, }); marked_rules.push(regex); } marked.InlineLexer.rules.zulip.linkifiers = marked_rules; } export function initialize(linkifiers) { update_linkifier_rules(linkifiers); marked.setOptions({linkifierHandler: handleLinkifier}); }