UNPKG

glipdown

Version:
451 lines (403 loc) 14.2 kB
/*globals window, Client */ var TLDS = TLDS || []; if (typeof require != 'undefined') { TLDS = require('./tlds').TLDS; } function loadValidTLDS() { var tldsArray = []; TLDS.forEach(function(tld){ tldsArray.push('\\.' + tld + '(?!\\.|\\w)'); }); return tldsArray.join('|'); } var validLinkMarkDownRegEx = new RegExp('[a-zA-Z0-9](' + loadValidTLDS() + ')|^https?:\\/\\/[a-zA-Z0-9]', "i"); var _ = _ || null; if ((typeof require != 'undefined') && !_) { var _ = require('underscore'); } var anchor_tag_regexp = new RegExp('<a.*?>.*?<\/a>', 'g'); var tokenized_anchor_tag_regexp = new RegExp('{{-{{a.*?}}-}}.*?{{-{{\/a}}-}}', 'g'); var escaped_anchor_tag_regexp = new RegExp('&(?:amp;){0,2}lt;a.*?&(?:amp;){0,2}gt;.*?&(?:amp;){0,2}lt;(?:\/|&(?:amp;){0,2}#x2F;)a&(?:amp;){0,2}gt;', 'g'); var any_tag_regexp = new RegExp('<[a-z].*?>', 'g'); var tokenized_any_tag_regexp = new RegExp('{{-{{[a-z].*?}}-}}', 'g'); var escaped_any_tag_regexp = new RegExp('&(?:amp;){0,2}lt;[a-z].*?&(?:amp;){0,2}?gt;', 'g'); function build_location_cache(cache, raw_string, match_pattern_array) { var location_cache; var string_to_match = typeof raw_string === 'string' ? raw_string : ''; var regexp_array = Array.isArray(match_pattern_array) ? match_pattern_array : []; if (cache && cache.built) { return cache; }; location_cache = { built: true, locations: [] }; regexp_array.forEach(function(match_pattern) { var index = 0; var position; var match_regexp = match_pattern instanceof RegExp ? match_pattern : new RegExp(); var matches = string_to_match.match(match_regexp) || []; var matched_tag_locations = {}; for (index = 0; index < matches.length; index++) { current_match = matches[index]; position = string_to_match.indexOf( current_match, matched_tag_locations[current_match] || 0 ); matched_tag_locations[current_match] = position + 1; location_cache.locations.push({ start: position, end: position + current_match.length }); } }); return location_cache; } function is_in_location_cache(cache, location_to_check) { var found_in_cache; var location_object_array; var array_length; var location_object; if (!cache || !cache.locations || !Array.isArray(cache.locations) || typeof location_to_check !== 'number' ) { return false; } location_object_array = cache.locations.slice().reverse(); array_length = location_object_array.length; while(array_length && !found_in_cache) { location_object = location_object_array[array_length -1]; found_in_cache = location_to_check > location_object.start && location_to_check < location_object.end; array_length -= 1; } return found_in_cache; } var Markdown = function(raw, options) { var options = options || {}; var phone_util; var is_valid_pstn = function () { return false; }; var team_join_link_regexp; if (!raw) { return ''; } if (!options.dont_escape) { raw = _.escape(raw); } if (typeof Client !== 'undefined') { phone_util = Client.get_controller('Phone_Number'); is_valid_pstn = phone_util && typeof phone_util.is_valid_pstn === 'function' ? phone_util.is_valid_pstn : is_valid_pstn; } var code_blocks = {}; var block_count = 0; var quote_tag = options.use_blockquote ? 'span class="blockquote"' : 'q'; var end_quote_tag = options.use_blockquote ? '/span' : '/q'; var link_last_offset = 0; var link_was_inside_tag = 0; var link_array = []; var score_array= []; var timestamp_array = []; var anchor_tag_location_cache; if (typeof window !== 'undefined' && window.location && window.location.origin) { team_join_link_regexp = new RegExp('^' + window.location.origin + '/r/join/'); } var val = raw.replace(/\&\#x2F;/g, '/') // not sure why underscore replaces these...docs don't even claim that it does .replace(/\\]|\\\)/g, function (match) { return { '\\]': '%5D', '\\\)': '%29' }[match] }) .replace(/\[([^\]]*?)]\(([^)]*?)\)/g, function(full_match, text, link) { var link_pieces; var group_id; text = String(text).replace(/%5D/g, ']'); link = String(link).replace(/'%29/g, ')'); if (text === 'code' || !validLinkMarkDownRegEx.test(link)) { return full_match; } text = text.replace(/\\([\[\]()])/g, '$1'); link_pieces = link.split('/'); group_id = link_pieces[link_pieces.length - 1]; return team_join_link_regexp && link.match(team_join_link_regexp) && link.indexOf('join') !== 0 ? "<div onclick='Router.join_from_url(" + group_id + ", true)' class='team_join_link'>" + link + "</div>" : "<a href='" + link + "' target='_blank' rel='noreferrer'>" + text + "</a>"; }) .replace(/%5D|%29/g, function (match) { //for the rare cases of escaped brackets happening outside of links return { '%5D': '\\]', '%29': '\\\)' }[match] }) .replace(/((^|\n)&gt; ([^\n]*))+\n?/g, function(full_match) { return "<" + quote_tag + ">\n" + full_match.trim().replace(/^&gt; /gm, '') + "\n<" + end_quote_tag + ">"; }) .replace(/\[code\]([\s\S]*?)(\[\/code\]|$)/gi, function(full_match, text) { var code; try { code = unescape(text); code = code.replace(/</g, '&lt;').replace(/>/g, '&gt;'); } catch (error) { code = text.replace(/(\%\w\w)/g, function (fm, esc) { return unescape(esc); }); } code_blocks['code_' + block_count] = code; var to_return = '[code_' + block_count + ']'; block_count++; return to_return; }) .replace(Markdown.global_url_regex, function( full_match, maybe_email1, maybe_email2, link, proto_and_slashes, protocol, junk, junkpoint5, junk1, junk2, junk3, junk4, junk5, junk6, last_char, offset, full_str ) { var link_pieces; var group_id; var link_token = '{{--LINK-' + link_array.length + '--}}'; var include_space = full_match.match(/ $/); if (!validLinkMarkDownRegEx.test(link)) { return full_match; } var start = full_str.substr(link_last_offset, offset - link_last_offset); link_last_offset = offset; var inside_tag = link_was_inside_tag ? (start.match(/.*>[^<]*$/) ? false : true) : start.match(/.*<[^>]*$/); if (inside_tag) { link_was_inside_tag = true; return full_match; } link_was_inside_tag = false; var inside_post_tag = start.match(/.*(\{\{-\{\{)(?![\s\S]*\}\}-\}\})[\s\S]*$/); if (inside_post_tag) { return full_match; } var lead_match = full_str.substring(0, offset); var lead_str = lead_match + full_match; var link_matches = lead_str.replace(/\{\{\-\{\{/g, '<').replace(/\}\}-\}\}/g, '>').match(/^[\s\S]*<a[\s\S]*?>([\s\S]*?)$/); if (link_matches) { var link_match = link_matches[1]; if (!link_match.match(/<\/a>/) || link_match.match(/<\/a>$/)) { return full_match; } } //console.warn("nomatch:", +new Date() - sub_bench); if (team_join_link_regexp && link.match(team_join_link_regexp) && link.indexOf('join') !== 0) { link_pieces = link.split('/'); group_id = link_pieces[link_pieces.length - 1]; link_array.push("<div onclick='Router.join_from_url(" + group_id + ", true)' class='team_join_link'>" + link + "</div>"); return link_token; } // ensures that the characters ".,?:;()*&!" will not become // apart of a link if they are placed at the end of one link = link.replace('&amp;', '&'); link = link.replace(/[.,?:;()*&!~]$/, function (m) { last_char = m + (include_space ? ' ' : ''); return ''; }); link_array.push("<a href='" + ( maybe_email2 && !protocol ? 'mailto:' + maybe_email2 : (protocol ? '' : 'http://') ) + link + "' target='_blank' rel='noreferrer'>" + (maybe_email2 ? maybe_email2 : '') + link + "</a>"); return link_token + last_char; }) .replace(/\|([^\n]*)\|(\s|\n|$)/g, function(full_match, text) { var cols = text.split(/\|/); if (!cols.length) { return text; } var percent = (100 / cols.length).toFixed(0); return "<table><tr valign='top'><td width='" + percent + "%'>" + cols.join("</td><td width='" + percent + "%'>") + "</tr></table>"; }) .replace(/<\/table><table>/g, '') .replace(/((^|\n)\* [^\n]*)+/g, function(full_match) { var parts = full_match.split(/\n/); if (parts[0].length === 0) { parts.shift(); } return "<ul><li>" + parts.map(function (part) { return part.replace(/^\* /, ''); }). join("</li><li>") + "</li></ul>"; }) .replace(/((^|\n)\d+\. [^\n]*)+/g, function (full_match) { var parts = full_match.split(/\n/); var start = parseInt(full_match, 10); var start_text = ""; if (parts[0].length === 0) { parts.shift(); } if (!isNaN(start) && start > 1) { start_text = " start=" + start + " style='counter-reset: li " + (start - 1) + "'"; } return "<ol" + start_text +"><li>" + parts.map(function (part) { return part.replace(/^\d+. /, ''); }).join("</li><li>") + "</li></ol>"; }).replace(new RegExp("<" + quote_tag + ">((?:.|\\n)+)<" + end_quote_tag + ">", 'gm'), function(full_match, p1) { return "<" + quote_tag + ">" + p1.trim() + "<" + end_quote_tag + ">"; }) .replace(/<\/([ou])l>\n/g, '</$1l>') .replace(/\*\*([^\*\*]+)\*\*/g, function (full_match, text) { return "<b>" + text + "</b>"; }) .replace(/\*([^\*]+)\*/g, function (full_match, text) { return "<i>" + text + "</i>"; }) .replace(/__([^__]+)__/g, function (full_match, text) { return "<u>" + text + "</u>"; }) .replace(/~~([^~~]+)~~/g, function (full_match, text) { return "<strike>" + text + "</strike>"; }) .replace(/\{\{\[\[-([^\{\{\[\[]*?)-\]\]\}\}/g, function(full_match, text) { return "<span class='search_match_stream'>" + text + "</span>"; }) .replace(/\[code_(\w+)\]/g, function(full_match, which) { return "<pre class=codesnippet>" + code_blocks['code_' + which] + "</pre>"; }) .replace(Markdown.timestamp_regex, function skip_timestamps( match, offset, full_str) { var time_token = '{{--TIME-' + timestamp_array.length + '--}}'; timestamp_array.push(match); return time_token; }) .replace(Markdown.score_regex, function skip_score( match, offset, full_str) { var score_token = '{{--SCORE-' + score_array.length + '--}}'; score_array.push(match); return score_token; }) .replace(Markdown.potential_phone_regex, function mark_phone_numbers( match, offset, full_str ) { var in_anchor_tag; var is_valid_number = match !== '911' && match !== '999' && is_valid_pstn(match); var regexp_to_check; if (!is_valid_number) { return match; } regexp_to_check = [ anchor_tag_regexp, tokenized_anchor_tag_regexp, escaped_anchor_tag_regexp, any_tag_regexp, tokenized_any_tag_regexp, escaped_any_tag_regexp ]; anchor_tag_location_cache = build_location_cache( anchor_tag_location_cache, full_str, regexp_to_check ); in_anchor_tag = is_in_location_cache(anchor_tag_location_cache, offset); return !in_anchor_tag ? "<a href='tel:" + match + "' class='markdown_phone_number'>" + match + '</a>' : match; }) .replace(/{{--LINK-(\d*)--}}/g, function (link_token, link_index) { return link_array[link_index] || link_token; }) .replace(/mailto:<a href=/g, function (full_match, which) { return "<a href="; }) .replace(/{{--TIME-(\d*)--}}/g, function(time_token, time_index) { return timestamp_array[time_index] || time_token; }) .replace(/{{--SCORE-(\d*)--}}/g, function(score_token, score_index) { return score_array[score_index] || score_token; }); return val; }; var Markdown_Is_Valid_Link = function (link) { return validLinkMarkDownRegEx.test(link); }; Markdown.tld_url_regex = validLinkMarkDownRegEx; Markdown.url_regex = /^((ftp|https?):\/\/)?[-\w]+\.([-\w]+\.)*(\d+\.\d+\.\d+|[-A-Za-z]+)(:\d+)?($|(\/\S?(\/\S)*\/?)|(\#\S?)|(\?\S?))/i; Markdown.global_url_regex = /(([a-zA-Z0-9\!\#\$\%\&\'\*\+\-\/\=\?\%\_\`\{\|\}\~\.]+@)?)(((ftp|https?):\/\/)?[-\w]+\.([-\w]+\.)*(\d+\.\d+\.\d+|[-A-Za-z]+)(:\d+)?(((\/([A-Za-z0-9-\._~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=])*)+)\??([A-Za-z0-9-\._~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\%])*)?)([^A-Za-z]|$)/gi; Markdown.score_regex = /(?:(\s|\n|^|,))\d{1,2}-(\d{1,2}(?!\d|-))/g; Markdown.timestamp_regex = /\d\d\d\d(?:[-, ])[01]\d(?:[-, ])[0-3]\d [0-2]\d:\d\d:[0-5]\d/g; Markdown.potential_phone_regex = /\+?(?:\d{1,4} ?)?(?:(?:\(\d{1,4}\)|\d(?:(?: |\-)?\d){0,3})(?:(?: |\-)?\d){2,}|(?:\(\d{2,4}\)|\d(?:(?: |\-)?\d){1,3})(?:(?: |\-)?\d){1,})(?:(?: x| ext.?)\d{1,5}){0,1}/g; var Markdown_For_Search = function (raw, options) { options = options || {}; if (!raw) { return ''; } if (!options.dont_escape) { raw = _.escape(raw); } return raw.replace(/\{\{\[\[-([^\{\{\[\[]*?)-\]\]\}\}/g, function(full_match, text) { return "<span class='search_match_stream'>" + text + "</span>"; }); }; var Remove_Markdown = function (raw, options) { options = options || {}; return (options.dont_escape ? raw : _.escape(raw)). replace(/\&\#x2F;/g, '/'). // not sure why underscore replaces these...docs don't even claim that it does replace(/\[code\]/g, ''). replace(/\[\/code\]/g, ''). replace(/\|([^\n]*)\|/g, function (full_match, text) { var cols = text.split(/\|/); if (!cols.length) { return text; } return cols.join('\t'); }). replace(/\*\*(\S[^\*\*]*?\S)\*\*/g, function (full_match, text) { return text; }). // replace(/\`(.*?)\`/g, function(full_match, text) { // return text; // }). replace(/\*(\S[^\*]*?\S)\*/g, function (full_match, text) { return text; }). replace(/__(\S[^__]*?\S)__/g, function (full_match, text) { return text; }). replace(/~~(\S[^~~]*?\S)~~/g, function (full_match, text) { return text; }). replace(/(^|\n)&gt; ([^\n]*)/g, function (full_match, start, text) { return start + " " + text + " "; }). replace(/\[([^\]]*?)\]\(([\s\S]*?)\)/g, function (full_match, text, link) { return text; }); }; if (typeof exports == 'object') { exports.Markdown = Markdown; exports.Remove_Markdown = Remove_Markdown; exports.Markdown_For_Search = Markdown_For_Search; exports.Markdown_Is_Valid_Link = Markdown_Is_Valid_Link; }