From 15f61d8bb42daaea13876a65e98eb9ca9eab0363 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 26 Sep 2013 12:34:31 -0700 Subject: [PATCH] Add linkification for messages. --- .jshintignore | 1 + clientapp/helpers/chatHelpers.js | 2 +- clientapp/helpers/htmlify.js | 41 +++++++++++++++++++ clientapp/models/message.js | 7 ++++ clientapp/templates.js | 4 +- clientapp/templates/includes/bareMessage.jade | 2 +- 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 clientapp/helpers/htmlify.js diff --git a/.jshintignore b/.jshintignore index f06f812..3546263 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,5 +1,6 @@ node_modules public +clientapp/helpers/htmlify.js clientapp/libraries clientapp/templates.js clientapp/.build diff --git a/clientapp/helpers/chatHelpers.js b/clientapp/helpers/chatHelpers.js index 9308f36..27f6de2 100644 --- a/clientapp/helpers/chatHelpers.js +++ b/clientapp/helpers/chatHelpers.js @@ -7,7 +7,7 @@ var raf = require('raf-component'); module.exports = { initializeScroll: function () { - var check = _.bind(this.handleScroll, this); + var check = _.bind(_.throttle(this.handleScroll, 100), this); var self = this; function animate() { diff --git a/clientapp/helpers/htmlify.js b/clientapp/helpers/htmlify.js new file mode 100644 index 0000000..8e00178 --- /dev/null +++ b/clientapp/helpers/htmlify.js @@ -0,0 +1,41 @@ +"use strict"; + +var _ = require('underscore'); + + +module.exports = { + toHTML: function (msg, team) { + var html = this.escapeHTML(msg); + html = html.replace(/\n/g, '
'); + html = html.replace(/\t/g, '    '); + html = html.replace(/ {2}/g, '  '); + html = this.linkify(html); + return html; + }, + + linkify: function (text) { + /* + * JavaScript Linkify - v0.3 - 6/27/2009 + * http://benalman.com/projects/javascript-linkify/ + * + * Copyright (c) 2009 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + * + * Some regexps adapted from http://userscripts.org/scripts/review/7122 + */ + var linkify=(function(){var k="[a-z\\d.-]+://",h="(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])",c="(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+",n="(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)",f="(?:"+c+n+"|"+h+")",o="(?:[;/][^#?<>\\s]*)?",e="(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?",d="\\b"+k+"[^<>\\s]+",a="\\b"+f+o+e+"(?!\\w)",m="mailto:",j="(?:"+m+")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@"+f+e+"(?!\\w)",l=new RegExp("(?:"+d+"|"+a+"|"+j+")","ig"),g=new RegExp("^"+k,"i"),b={"'":"`",">":"<",")":"(","]":"[","}":"{","B;":"B+","b:":"b9"},i={callback:function(q,p){return p?''+q+"":q},punct_regexp:/(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/};return function(u,z){z=z||{};var w,v,A,p,x="",t=[],s,E,C,y,q,D,B,r;for(v in i){if(z[v]===undefined){z[v]=i[v]}}while(w=l.exec(u)){A=w[0];E=l.lastIndex;C=E-A.length;if(/[\/:]/.test(u.charAt(C-1))){continue}do{y=A;r=A.substr(-1);B=b[r];if(B){q=A.match(new RegExp("\\"+B+"(?!$)","g"));D=A.match(new RegExp("\\"+r,"g"));if((q?q.length:0)<(D?D.length:0)){A=A.substr(0,A.length-1);E--}}if(z.punct_regexp){A=A.replace(z.punct_regexp,function(F){E-=F.length;return""})}}while(A.length&&A!==y);p=A;if(!g.test(p)){p=(p.indexOf("@")!==-1?(!p.indexOf(m)?"":m):!p.indexOf("irc.")?"irc://":!p.indexOf("ftp.")?"ftp://":"http://")+p}if(s!=C){t.push([u.slice(s,C)]);s=E}t.push([A,p])}t.push([u.substr(s)]);for(v=0;v' + text + '' : text; + } + }); + }, + + escapeHTML: function(s) { + var re = /[&\"'<>]/g, // " + map = {"&": "&", "\"": """, "'": "'", "<": "<", ">": ">"}; + return s.replace(re, function(c) { return map[c]; }); + } +}; diff --git a/clientapp/models/message.js b/clientapp/models/message.js index 7634d7c..02ff7a0 100644 --- a/clientapp/models/message.js +++ b/clientapp/models/message.js @@ -3,6 +3,7 @@ var HumanModel = require('human-model'); var templates = require('../templates'); +var htmlify = require('../helpers/htmlify'); module.exports = HumanModel.define({ @@ -93,6 +94,12 @@ module.exports = HumanModel.define({ return me.getContact(this.from.bare).displayName; } }, + processedBody: { + deps: ['body'], + fn: function () { + return htmlify.toHTML(this.body); + } + }, partialTemplateHtml: { deps: ['edited', 'pending', 'body'], cache: false, diff --git a/clientapp/templates.js b/clientapp/templates.js index 242b220..39ceb9a 100644 --- a/clientapp/templates.js +++ b/clientapp/templates.js @@ -37,7 +37,7 @@ exports.includes.bareMessage = function anonymous(locals) { }, { "class": true, id: true - }) + '>' + jade.escape(null == (jade.interp = message.formattedTime) ? "" : jade.interp) + '

' + jade.escape(null == (jade.interp = message.body) ? "" : jade.interp) + "

"); + }) + '>' + jade.escape(null == (jade.interp = message.formattedTime) ? "" : jade.interp) + '

' + ((jade.interp = message.processedBody) == null ? "" : jade.interp) + "

"); } return buf.join(""); }; @@ -124,7 +124,7 @@ exports.includes.wrappedMessage = function anonymous(locals) { }, { "class": true, id: true - }) + '>' + jade.escape(null == (jade.interp = message.formattedTime) ? "" : jade.interp) + '

' + jade.escape(null == (jade.interp = message.body) ? "" : jade.interp) + "

"); + }) + '>' + jade.escape(null == (jade.interp = message.formattedTime) ? "" : jade.interp) + '

' + ((jade.interp = message.processedBody) == null ? "" : jade.interp) + "

"); } return buf.join(""); }; diff --git a/clientapp/templates/includes/bareMessage.jade b/clientapp/templates/includes/bareMessage.jade index 3777a74..35d1a31 100644 --- a/clientapp/templates/includes/bareMessage.jade +++ b/clientapp/templates/includes/bareMessage.jade @@ -1,3 +1,3 @@ .message(id='chat'+message.cid, class=message.classList) span.timestamp=message.formattedTime - p.body=message.body + p.body !{message.processedBody}