(function() { 'use strict'; var weechat = angular.module('weechat'); weechat.filter('toArray', function () { return function (obj, storeIdx) { if (!(obj instanceof Object)) { return obj; } if (storeIdx) { return Object.keys(obj).map(function (key, idx) { return Object.defineProperties(obj[key], { '$key' : { value: key }, '$idx' : { value: idx, configurable: true } }); }); } return Object.keys(obj).map(function (key) { return Object.defineProperty(obj[key], '$key', { value: key }); }); }; }); weechat.filter('irclinky', function() { return function(text) { if (!text) { return text; } // This regex in no way matches all IRC channel names (they could also begin with &, + or an // exclamation mark followed by 5 alphanumeric characters, and are bounded in length by 50). // However, it matches all *common* IRC channels while trying to minimise false positives. // "#1" is much more likely to be "number 1" than "IRC channel #1". // Thus, we only match channels beginning with a # and having at least one letter in them. var channelRegex = /(^|[\s,.:;?!"'()+@-\~%])(#+[^\x00\x07\r\n\s,:]*[a-z][^\x00\x07\r\n\s,:]*)/gmi; // Call the method we bound to window.openBuffer when we instantiated // the Weechat controller. var substitute = '$1$2'; return text.replace(channelRegex, substitute); }; }); weechat.filter('inlinecolour', function() { return function(text) { if (!text) { return text; } // only match 6-digit colour codes, 3-digit ones have too many false positives (issue numbers, etc) var hexColourRegex = /(^|[^&])(\#[0-9a-f]{6};?)(?!\w)/gmi; var rgbColourRegex = /(.?)(rgba?\((?:\s*\d+\s*,){2}\s*\d+\s*(?:,\s*[\d.]+\s*)?\);?)/gmi; var substitute = '$1$2
'; text = text.replace(hexColourRegex, substitute); text = text.replace(rgbColourRegex, substitute); return text; }; }); // Calls the 'linky' filter unless the disable flag is set. Useful for things like join/quit messages, // so you don't accidentally click a mailto: on someone's hostmask. weechat.filter('conditionalLinkify', ['$filter', function($filter) { return function(text, disable) { if (!text || disable) { return text; } return $filter('linky')(text, '_blank', {rel:'noopener noreferrer'}); }; }]); // apply a filter to an HTML string's text nodes, and do so with not exceedingly terrible performance weechat.filter('DOMfilter', ['$filter', '$sce', function($filter, $sce) { // To prevent nested anchors, we need to know if a filter is going to create them. // Here's a list of names. See #681 for more information. var filtersThatCreateAnchors = ['irclinky']; return function(text, filter) { if (!text || !filter) { return text; } var createsAnchor = filtersThatCreateAnchors.indexOf(filter) > -1; var escape_html = function(text) { // First, escape entities to prevent escaping issues because it's a bad idea // to parse/modify HTML with regexes, which we do a couple of lines down... var entities = {"<": "<", ">": ">", '"': '"', "'": ''', "&": "&", "/": '/'}; return text.replace(/[<>"'&\/]/g, function (char) { return entities[char]; }); }; // hacky way to pass extra arguments without using .apply, which // would require assembling an argument array. PERFORMANCE!!! var extraArgument = (arguments.length > 2) ? arguments[2] : null; var thirdArgument = (arguments.length > 3) ? arguments[3] : null; var filterFunction = $filter(filter); var el = document.createElement('div'); el.innerHTML = text; // Recursive DOM-walking function applying the filter to the text nodes var process = function(node) { if (node.nodeType === 3) { // text node // apply the filter to *escaped* HTML, and only commit changes if // it changed the escaped value. This is because setting the result // as innerHTML causes it to be unescaped. var input = escape_html(node.nodeValue); var value = filterFunction(input, extraArgument, thirdArgument); if (value !== input) { // we changed something. create a new node to replace the current one // we could also only add its children but that would probably incur // more overhead than it would gain us var newNode = document.createElement('span'); newNode.innerHTML = value; var parent = node.parentNode; var sibling = node.nextSibling; parent.removeChild(node); if (sibling) { parent.insertBefore(newNode, sibling); } else { parent.appendChild(newNode); } return newNode; } } // recurse if (node === undefined || node === null) return; node = node.firstChild; while (node) { var nextNode = null; // do not recurse inside links if the filter would create a nested link if (!(createsAnchor && node.tagName === 'A')) { nextNode = process(node); } node = (nextNode ? nextNode : node).nextSibling; } }; process(el); return $sce.trustAsHtml(el.innerHTML); }; }]); // This is used by the cordova app to change link targets to "window.open(, '_system')" // so that they're opened in a browser window and don't navigate away from Glowing Bear weechat.filter('linksForCordova', ['$sce', function($sce) { return function(text) { // XXX TODO this needs to be improved text = text.replace(/ delayed rendering setTimeout(function() { var math = document.querySelector(selector); renderMathInElement(math, { delimiters: [ {left: "$$", right: "$$", display: false}, {left: "\\[", right: "\\]", display: true}, {left: "\\(", right: "\\)", display: false} ] }); }); } return text; }; }); weechat.filter('prefixlimit', function() { return function(input, chars) { if (isNaN(chars)) return input; if (chars <= 0) return ''; if (input && input.length > chars) { input = input.substring(0, chars); return input + '+'; } return input; }; }); weechat.filter('codify', function() { return function(text) { // The groups of this regex are: // 1. Start of line or space, to prevent codifying weird`stuff` like this // 2. Opening single or triple backticks (not 2, not more than 3) // 3. The code block, does not start with another backtick, non-greedy expansion // 4. The closing backticks, identical to group 2 var re = /(^|\s)(```|`)([^`].*?)\2/g; return text.replace(re, function(match, ws, open, code) { var rr = ws + '' + open + '' + code + '' + open + ''; return rr; }); }; }); })();