// twister_formatpost.js // 2013 Miguel Freitas // // Format JSON posts and DMs to HTML. var _templatePostRtReference var _templatePostRtBy var _htmlFormatMsgLinkTemplateExternal; var _htmlFormatMsgLinkTemplateUser; var _htmlFormatMsgLinkTemplateHashtag; $(document).ready(function() { // we're setting it here for perfomance improvement purpose // to not search and prepare it for for every post every time _templatePostRtReference = $('#post-rt-reference-template').children().clone(true); _templatePostRtReference.find('.post-text') .on('click', {feeder: '.post-rt-reference'}, openConversationClick); _templatePostRtBy = $('#post-retransmited-by-template').children().clone(true); _htmlFormatMsgLinkTemplateExternal = $('#external-page-link-template') if (_htmlFormatMsgLinkTemplateExternal.length) { _htmlFormatMsgLinkTemplateExternal = _htmlFormatMsgLinkTemplateExternal[0].cloneNode(); _htmlFormatMsgLinkTemplateExternal.removeAttribute('id'); } _htmlFormatMsgLinkTemplateUser = $('#msg-user-link-template') if (_htmlFormatMsgLinkTemplateUser.length) { _htmlFormatMsgLinkTemplateUser = _htmlFormatMsgLinkTemplateUser[0].cloneNode(); _htmlFormatMsgLinkTemplateUser.removeAttribute('id'); } _htmlFormatMsgLinkTemplateHashtag = $('#hashtag-link-template') if (_htmlFormatMsgLinkTemplateHashtag.length) { _htmlFormatMsgLinkTemplateHashtag = _htmlFormatMsgLinkTemplateHashtag[0].cloneNode(); _htmlFormatMsgLinkTemplateHashtag.removeAttribute('id'); } }); // format "userpost" to html element // kind = "original"/"ancestor"/"descendant" function postToElem(post, kind, promoted) { /* "userpost" : { "n" : username, "k" : seq number, "t" : "post" / "dm" / "rt" "msg" : message (post/rt) "time" : unix utc "height" : best height at user "dm" : encrypted message (dm) -opt "rt" : original userpost - opt "sig_rt" : sig of rt - opt "reply" : - opt { "n" : reference username "k" : reference k } } "sig_userpost" : signature by userpost.n */ var username, k, time, msg, rt, content_to_rt, content_to_sigrt, retweeted_by; // Obtain data from userpost var userpost = post.userpost; if (post.sig_wort) userpost.sig_wort = post.sig_wort; if (userpost.rt) { rt = userpost.rt; if (userpost.msg) { username = userpost.n; k = userpost.k; time = userpost.time; msg = userpost.msg; content_to_rt = $.toJSON(userpost); content_to_sigrt = post.sig_userpost; } else { username = rt.n; k = rt.k; time = rt.time; msg = rt.msg; content_to_rt = $.toJSON(rt); content_to_sigrt = userpost.sig_rt; } retweeted_by = userpost.n; } else { username = userpost.n; k = userpost.k; time = userpost.time; msg = userpost.msg; content_to_rt = $.toJSON(userpost); content_to_sigrt = post.sig_userpost; } // Now create the html elements var elem = $.MAL.getPostTemplate().clone(true); elem.removeAttr('id') .addClass(kind) .attr('data-time', time) ; if (post.isNew) elem.addClass('new'); var postData = elem.find('.post-data'); postData.addClass(kind) .attr('data-userpost', $.toJSON(post)) .attr('data-content_to_rt', content_to_rt) .attr('data-content_to_sigrt', content_to_sigrt) .attr('data-screen-name', username) .attr('data-id', k) .attr('data-lastk', userpost.lastk) .attr('data-text', msg) ; if (userpost.reply) { postData.attr('data-replied-to-screen-name', userpost.reply.n) .attr('data-replied-to-id', userpost.reply.k) .find('.post-expand').text(polyglot.t('Show conversation')) ; } else if (userpost.rt && userpost.rt.reply) { postData.attr('data-replied-to-screen-name', userpost.rt.reply.n) .attr('data-replied-to-id', userpost.rt.reply.k) .find('.post-expand').text(polyglot.t('Show conversation')) ; } setPostCommon(elem, username, time); msg = htmlFormatMsg(msg); elem.find('.post-text').html(msg.html); postData.attr('data-text-mentions', msg.mentions.join()); // FIXME no idea why do we need this attribute since we don't use it but use data-reply-to instead if (username !== defaultScreenName) { if (msg.mentions.indexOf(username) === -1) msg.mentions.splice(0, 0, username); } for (var i = msg.mentions.indexOf(defaultScreenName); i !== -1; i = msg.mentions.indexOf(defaultScreenName)) msg.mentions.splice(i, 1); if (msg.mentions.length) var replyTo = '@' + msg.mentions.join(' @') + ' '; else var replyTo = ''; var postTextArea = elem.find('.post-area-new textarea'); postTextArea.attr('data-reply-to', replyTo); if (!defaultScreenName) postTextArea.attr('placeholder', polyglot.t('You have to log in to post replies.')); else postTextArea.attr('placeholder', polyglot.t('reply_to', {fullname: replyTo})+ '...'); postData.attr('data-reply-to', replyTo); if (typeof retweeted_by !== 'undefined') { var postContext = elem.find('.post-context'); if (userpost.msg) { setPostReference(postContext, rt, userpost.sig_rt); } else { postContext.append(_templatePostRtBy.clone(true)) .find('.post-retransmited-by') .attr('href', $.MAL.userUrl(retweeted_by)).text('@' + retweeted_by) ; // let's check original post and grab some possible RT dhtget(username, 'post' + k, 's', function(args, post) { if (post && post.userpost.msg && post.userpost.rt) { var postContext = $('
'); setPostReference(postContext, post.userpost.rt, post.userpost.sig_rt); args.elem.find('.post-text').after(postContext); } }, {elem: elem} ); } postContext.show(); } if (typeof promoted !== 'undefined' && promoted) { elem.find('.post-propagate').remove(); postData.attr('data-promoted', 1); postData.attr('data-screen-name', '!' + username); } else { setPostInfoSent(userpost.n, userpost.k, elem.find('.post-info-sent')); if ($.Options.filterLang.val !== 'disable' && $.Options.filterLangSimulate.val) { // FIXME it's must be stuff from template actually if (typeof post.langFilter !== 'undefined') { if (typeof post.langFilter.prob[0] !== 'undefined') var mlm = ' // ' + polyglot.t('Most possible language: this', {'this': '' + post.langFilter.prob[0].toString() + ''}); else var mlm = ''; elem.append('
' + polyglot.t('This post is treated by language filter', {'treated': '' + (post.langFilter.pass ? polyglot.t('passed') : polyglot.t('blocked')) + ''}) + '
' ) .append('
' + polyglot.t('Reason: this', {'this': '' + post.langFilter.reason + ''}) + mlm +'
' ) ; } else { elem.append('
' + polyglot.t('This post is treated by language filter', {'treated': '' + polyglot.t('not analyzed') + ''}) + '
' ) ; } } } return elem; } function setPostCommon(elem, username, time) { var postInfoName = elem.find('.post-info-name') .text(username).attr('href', $.MAL.userUrl(username)); getFullname(username, postInfoName); //elem.find('.post-info-tag').text("@" + username); // FIXME getAvatar(username, elem.find('.avatar')); elem.find('.post-info-time') .attr('title', timeSincePost(time)) .find('span:last') .text(timeGmtToText(time)) ; } function setPostReference(elem, rt, sig_rt) { elem.append(_templatePostRtReference.clone(true)) .find('.post-rt-reference') .attr('data-screen-name', rt.n) .attr('data-id', rt.k) .attr('data-userpost', $.toJSON({userpost: rt, sig_userpost: sig_rt})) .find('.post-text').html(htmlFormatMsg(rt.msg).html) ; setPostCommon(elem, rt.n, rt.time); } function setPostInfoSent(n, k, item) { if( n === defaultScreenName && k >= 0 ) { getPostMaxAvailability(n,k, function(args,count) { if( count >= 3 ) { // assume 3 peers (me + 2) is enough for "sent" args.item.text("\u2713"); // check mark } else { args.item.text("\u231B"); // hour glass setTimeout(setPostInfoSent,2000,n,k,item); } }, {n:n,k:k,item:item}); } } // format dmdata (returned by getdirectmsgs) to display in "snippet" per user list function dmDataToSnippetItem(dmData, remoteUser) { var dmItem = $("#dm-snippet-template").clone(true); dmItem.removeAttr('id'); dmItem.attr('data-screen-name', remoteUser); dmItem.attr("data-last_id", dmData.id); dmItem.attr("data-time", dmData.time); dmItem.find(".post-info-tag").text("@" + remoteUser); dmItem.find("a.post-info-name").attr("href", $.MAL.userUrl(remoteUser)); dmItem.find("a.dm-chat-link").attr("href", $.MAL.dmchatUrl(remoteUser)); getAvatar( remoteUser, dmItem.find(".post-photo").find("img") ); if( remoteUser.length && remoteUser[0] === '*' ) getGroupChatName( remoteUser, dmItem.find("a.post-info-name") ); else getFullname( remoteUser, dmItem.find("a.post-info-name") ); dmItem.find(".post-text").html(htmlFormatMsg(dmData.text).html); dmItem.find('.post-info-time') .attr('title', timeSincePost(dmData.time)) .find('span:last') .text(timeGmtToText(dmData.time)) ; return dmItem; } // format dmdata (returned by getdirectmsgs) to display in conversation thread function postToElemDM(dmData, localUser, remoteUser) { var senderAlias = (dmData.from && dmData.from.length && dmData.from.charCodeAt(0)) ? dmData.from : (dmData.fromMe ? localUser : remoteUser); var elem = $('#dm-chat-template').clone(true) .removeAttr('id') .addClass(dmData.fromMe ? 'sent' : 'received') ; var elemName = elem.find('.post-info-name') .attr('href', $.MAL.userUrl(senderAlias)); if (senderAlias[0] === '*' ) getGroupChatName(senderAlias, elemName); else getFullname(senderAlias, elemName); getAvatar(senderAlias, elem.find('.post-photo').find('img')); elem.find('.post-info-time') .attr('title', timeSincePost(dmData.time)) .find('span:last') .text(timeGmtToText(dmData.time)) ; setPostInfoSent(senderAlias, dmData.k, elem.find('.post-info-sent')); elem.find('.post-text').html(htmlFormatMsg(dmData.text).html); return elem; } // convert message text to html, featuring @users and links formating. function htmlFormatMsg(msg) { // TODO: add options for emotions; msg = $.emotions(msg); // TODO make markup optionally mutable ? function getSubStrStart(str, startPoint, stopChars, isStopCharMustExist, stopCharsTrailing) { for (var i = startPoint; i > -1; i--) { if (stopChars.indexOf(str[i]) > -1) break; } if (i !== -1 || !isStopCharMustExist) { for (i += 1; i < startPoint + 1; i++) { if (stopCharsTrailing.indexOf(str[i]) === -1) break; } } else i = startPoint + 1; return i; } function getSubStrEnd(str, startPoint, stopChars, isStopCharMustExist, stopCharsTrailing) { for (var i = startPoint; i < str.length; i++) { if (stopChars.indexOf(str[i]) > -1) break; } if (i !== str.length || !isStopCharMustExist) { for (i -= 1; i > startPoint - 1; i--) { if (stopCharsTrailing.indexOf(str[i]) === -1) break; } } else i = startPoint - 1; return i; } function markout(msg, chr, tag) { if ($.Options.postsMarkout.val === 'ignore') return msg; function isWhiteSpacesBetween(i, j) { j++; for (i += 1; i < j; i++) { if (p[i].w) return true; } return false; } function kindOfL(i) { if (stopCharsMarkout.indexOf(msg.str[i]) > -1) { for (var j = i - 1; j > -1; j--) { if (msg.str[j] === chr) { return -1; } else if (stopCharsMarkout.indexOf(msg.str[j]) === -1) { return whiteSpaces.indexOf(msg.str[j]); } } } else if (msg.str[i] === '<') { for (var j = i - 1; j > -1; j--) { if (msg.str[j] === '>') { if (j === 0) { return -10; } else { if (msg.str[j - 1] === chr) { return -1; } else { return kindOfL(j - 1); } } } } } else { return whiteSpaces.indexOf(msg.str[i]); } return 0; } function kindOfR(i) { if (stopCharsMarkout.indexOf(msg.str[i]) > -1) { for (var j = i + 1; j < msg.str.length; j++) { if (msg.str[j] === chr) { return -1; } else if (stopCharsMarkout.indexOf(msg.str[j]) === -1) { return whiteSpaces.indexOf(msg.str[j]); } } } else if (msg.str[i] === '>') { for (var j = i + 1; j < msg.str.length; j++) { if (msg.str[j] === '<') { if (j === msg.str.length - 1) { return -10; } else { if (msg.str[j + 1] === chr) { return -1; } else { return kindOfR(j + 1); } } } } } else { return whiteSpaces.indexOf(msg.str[i]); } return 0; } var i, j, t, l, r, htmlEntityEncoded; var w = false; var p = []; // collecting chars position data for (i = 0; i < msg.str.length; i++) { if (msg.str[i] === chr) { for (j = i + 1; j < msg.str.length; j++) { if (msg.str[j] !== chr) break; } if (i !== 0) { l = kindOfL(i - 1); } else { l = 0; } if (j !== msg.str.length) { r = kindOfR(j); } else { r = 0; } if ((i === 0 && r < 0) || l === -10) { p.push({i: i, k: j - i, t: -1, w: w, a: -1, p: -1}); w = false; } else if ((j === msg.str.length && l < 0) || r === -10) { p.push({i: i, k: j - i, t: 1, w: w, a: -1, p: -1}); w = false; } else { if (l > -1) { if (r > -1) { if (j - i > 2) { l = p.push({i: i, k: j - i - 1, t: -1, w: w, a: -1, p: -1}) - 1; p[l].a = p.push({i: j - 1, k: 1, t: 1, w: false, a: l, p: -1}) - 1; } t = 10; } else t = -1; } else { if (r > -1) t = 1; else t = 0; } if (t !== 10) p.push({i: i, k: j - i, t: t, w: w, a: -1, p: -1}); w = false; } i = j - 1; } else if (!w && whiteSpaces.indexOf(msg.str[i]) > -1) { w = true; } } // calculating dependencies for (i = 0; i < p.length; i++) { if (p[i].t < 1 && p[i].a === -1) { t = i; for (j = i + 1; j < p.length; j++) { if (p[i].t === 0 && isWhiteSpacesBetween(i, j)) { i = j - 1; break; } else if (p[j].t < 1 && p[j].a === -1) { p[t].p = j; t = j; } else if (p[j].t === 1 && p[j].a === -1) { p[i].a = j; p[j].a = i; i = j; break; } } } } for (i = 0; i < p.length; i++) { if (p[i].t === -1 && p[i].a === -1) { for (j = p[i].p; j > -1; j = p[j].p) { if (isWhiteSpacesBetween(i, j)) { i = j - 1; break; } else if (p[j].t === 0 && !(p[j].p > -1 && p[p[j].p].t === 0 && !isWhiteSpacesBetween(j, p[j].p))) { p[j].a = i; p[i].a = j; i = j; break; } } } } // changing the string if (chr === '`' && $.Options.postsMarkout.val === 'apply') { // if $.Options.postsMarkout.val === 'clear' then ` does not escapes anythyng so it needs to be handled like other tags for (i = 0; i < p.length; i++) { if (p[i].a > -1) { if (p[i].t === -1 || (p[i].t === 0 && p[i].a > i)) { if (p[i].k > 1) t = Array(p[i].k).join(chr); else t = ''; j = p[i].a; t = t + msg.str.slice(p[i].i + p[i].k, p[j].i); if (p[j].k > 1) t = t + Array(p[i].k).join(chr); t = '<' + tag + '>' + t + ''; msg.htmlEntities.push(t.replace(/&(?!lt;|gt;)/g, '&')); htmlEntityEncoded = '>' + (msg.htmlEntities.length - 1).toString() + '<'; msg.str = msg.str.slice(0, p[i].i) + htmlEntityEncoded + msg.str.slice(p[j].i + p[j].k); l = htmlEntityEncoded.length - p[j].i - p[j].k + p[i].i; i = j; for (j += 1; j < p.length; j++) p[j].i += l; } } } } else { if ($.Options.postsMarkout.val === 'apply') { t = ''; tag = '<' + tag + '>'; } else { // $.Options.postsMarkout.val === 'clear' so we're clearing markup t = ''; tag = ''; } for (i = 0; i < p.length; i++) { if (p[i].a > -1) { if (p[i].t === -1 || (p[i].t === 0 && p[i].a > i)) { if (p[i].k > 1) msg.htmlEntities.push(tag + Array(p[i].k).join(chr)); else msg.htmlEntities.push(tag); } else if (p[i].t === 1 || (p[i].t === 0 && p[i].a < i)) { if (p[i].k > 1) msg.htmlEntities.push(Array(p[i].k).join(chr) + t); else msg.htmlEntities.push(t); } htmlEntityEncoded = '>' + (msg.htmlEntities.length - 1).toString() + '<'; msg.str = msg.str.slice(0, p[i].i) + htmlEntityEncoded + msg.str.slice(p[i].i + p[i].k); l = htmlEntityEncoded.length - p[i].k; for (j = i + 1; j < p.length; j++) p[j].i += l; } } } return msg; } function newHtmlEntityLink(template, urlTarget, urlName) { template.href = urlTarget; template.innerHTML = urlName; // .innerHTML instead .text to allow markup inside [] of [url name](target) return template.outerHTML; } function msgAddHtmlEntity(msg, strSliceLeft, strSliceRigth, htmlEntity) { msg.htmlEntities.push(htmlEntity); var htmlEntityEncoded = '>' + (msg.htmlEntities.length - 1).toString() + '<'; msg.str = msg.str.slice(0, strSliceLeft) + htmlEntityEncoded + msg.str.slice(strSliceRigth); msg.i = strSliceLeft + htmlEntityEncoded.length - 1; return msg; } function applyHtml(msg) { var t; for (var i = 0; i < msg.str.length - 2; i++) { if (msg.str[i] === '>') { for (var j = i + 2; j < msg.str.length; j++) { if (msg.str[j] === '<') break; } t = msg.htmlEntities[parseInt(msg.str.slice(i + 1, j))]; msg.str = msg.str.slice(0, i) + t + msg.str.slice(j + 1); i = i + t.length - 1; } } return msg.str; } var mentionsChars = 'abcdefghijklmnopqrstuvwxyz_0123456789'; var stopCharsTrailing = '/\\*~_-`.,:;?!%\'"[](){}^|«»…\u201C\u201D\u2026\u2014\u4E00\u3002\uFF0C\uFF1A\uFF1F\uFF01\u3010\u3011\u2047\u2048\u2049'; var stopCharsTrailingUrl = stopCharsTrailing.slice(1); var whiteSpaces = ' \f\n\r\t\v​\u00A0\u1680\u180E\u2000​\u2001\u2002​\u2003\u2004\u2005\u2006​\u2007\u2008​\u2009\u200A\u2028\u2029​\u202F\u205F\u3000'; var whiteSpacesUrl = '\'\"' + whiteSpaces; var stopCharsLeft = '<' + whiteSpaces; var stopCharsRight = '>' + whiteSpaces; var stopCharsRightHashtags = '>/\\.,:;?!%\'"[](){}^|«»…\u201C\u201D\u2026\u2014\u4E00\u3002\uFF0C\uFF1A\uFF1F\uFF01\u3010\u3011\u2047\u2048\u2049' // same as stopCharsTrailing but without '*~_-`' plus '>' + whiteSpaces; var stopCharsMarkout = '/\\*~_-`.,:;?!%+=&\'"[](){}^|«»…\u201C\u201D\u2026\u2014\u4E00\u3002\uFF0C\uFF1A\uFF1F\uFF01\u3010\u3011\u2047\u2048\u2049'; var i, j, k, str; var mentions = []; msg = {str: escapeHtmlEntities(msg), htmlEntities: []}; msg = markout(msg, '`', 'samp'); // tag is kind of monospace, here sequence of chars inside it will be escaped from markup // handling links for (i = 0; i < msg.str.length - 7; i++) { if (msg.str.slice(i, i + 2) === '](' && $.Options.postsMarkout.val !== 'ignore') { // FIXME there can be text with [] inside [] or links with () we need to handle it too j = getSubStrStart(msg.str, i - 2, '[', true, ''); if (j < i - 1) { k = getSubStrEnd(msg.str, i + 3, ')', true, whiteSpaces); if (k > i + 2) { var linkName = msg.str.slice(j, i); // name of possiible link for (i += 2; i < k; i++) { if (whiteSpacesUrl.indexOf(msg.str[i]) === -1) // drop whitespaces and ' and " // apostrophes and quotes to prevent injection of js events break; } if (i < k) { var x = getSubStrEnd(msg.str, i, ':', false, '') + 1; // following check is NOT for real protection (we have blocking CSP rule instead), it's just to aware people if (msg.str[i] === '#' || (x > i && x < k && (msg.str.slice(x - 6, x).toLowerCase() === 'script' // other things would be added when W3C and all the people invent it || msg.str.slice(x - 4, x).toLowerCase() === 'data'))) { msg = msgAddHtmlEntity(msg, j - 1, getSubStrEnd(msg.str, k + 1, ')', true, '') + 2, '…
' + polyglot.t('busted_oh') + ' ' + polyglot.t('busted_avowal') + ':
' + msg.str.slice(i, k + 1) .replace(/&(?!lt;|gt;)/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') + '
' ); } else { if (getSubStrEnd(msg.str, i + 1, whiteSpacesUrl, false, '') < k) // use only first word as href target, others drop silently k = getSubStrEnd(msg.str, i + 1, whiteSpacesUrl, false, ''); if ($.Options.postsMarkout.val === 'apply') { msg = msgAddHtmlEntity(msg, j - 1, getSubStrEnd(msg.str, k + 1, ')', true, '') + 2, newHtmlEntityLink(_htmlFormatMsgLinkTemplateExternal, proxyURL(msg.str.slice(i, k + 1)), applyHtml( // we're trying markup inside [] of []() markout(markout(markout(markout( {str: linkName, htmlEntities: msg.htmlEntities}, '*', 'b'), // bold '~', 'i'), // italic '_', 'u'), // underlined '-', 's') // striketrough ) .replace(/&(?!lt;|gt;)/g, '&') ) ); } else { // $.Options.postsMarkout.val === 'clear' so we're clearing markup str = msg.str.slice(i, k + 1); msg = msgAddHtmlEntity(msg, j - 1, getSubStrEnd(msg.str, k + 1, ')', true, '') + 2, applyHtml( // we're trying to clear markup inside [] of []() markout(markout(markout(markout( {str: linkName, htmlEntities: msg.htmlEntities}, '*', 'b'), // bold '~', 'i'), // italic '_', 'u'), // underlined '-', 's') // striketrough ) .replace(/&(?!lt;|gt;)/g, '&') ); // here we put link target as plain text to handle it usual way (search http[s]:// and so on) i = msg.i + 1 msg.str = msg.str.slice(0, i) + ' ' + str + msg.str.slice(i); /* alternatively we could set up it as link itself but I suppose you don't want it msg = msgAddHtmlEntity(msg, msg.i + 1, msg.i + 1, ' ' + newHtmlEntityLink(_htmlFormatMsgLinkTemplateExternal, proxyURL(str), str) ); */ } } i = msg.i + 1; } } } } else if (msg.str.slice(i, i + 4).toLowerCase() === 'http') { if (msg.str.slice(i + 4, i + 7) === '://' && stopCharsRight.indexOf(msg.str[i + 7]) === -1) { j = getSubStrEnd(msg.str, i + 7, stopCharsRight, false, stopCharsTrailingUrl); if (j > i + 6) { str = msg.str.slice(i, j + 1); msg = msgAddHtmlEntity(msg, i, i + str.length, newHtmlEntityLink(_htmlFormatMsgLinkTemplateExternal, proxyURL(str), str) ); i = msg.i; } } else if (msg.str.slice(i + 4, i + 8).toLowerCase() === 's://' && stopCharsRight.indexOf(msg.str[i + 8]) === -1) { j = getSubStrEnd(msg.str, i + 8, stopCharsRight, false, stopCharsTrailingUrl); if (j > i + 7) { str = msg.str.slice(i, j + 1); msg = msgAddHtmlEntity(msg, i, i + str.length, newHtmlEntityLink(_htmlFormatMsgLinkTemplateExternal, proxyURL(str), str) ); i = msg.i; } } } } // handling mails for (i = 1; i < msg.str.length - 1; i++) { if (msg.str[i] === '@' && stopCharsLeft.indexOf(msg.str[i - 1]) === -1 && stopCharsTrailing.indexOf(msg.str[i - 1]) === -1 && stopCharsRight.indexOf(msg.str[i + 1]) === -1) { j = getSubStrStart(msg.str, i - 1, stopCharsLeft, false, stopCharsTrailing); if (j < i) { k = getSubStrEnd(msg.str, i + 1, stopCharsRight, false, stopCharsTrailing); if (k > i) { str = msg.str.slice(j, k + 1); msg = msgAddHtmlEntity(msg, j, j + str.length, newHtmlEntityLink(_htmlFormatMsgLinkTemplateExternal, 'mailto:' + str.toLowerCase(), str) ); i = msg.i; } } } } // handling mentions for (i = 0; i < msg.str.length - 1; i++) { if (msg.str[i] === '@' && mentionsChars.indexOf(msg.str[i + 1].toLowerCase()) > -1) { for (j = i + 2; j < msg.str.length; j++) { if (mentionsChars.indexOf(msg.str[j].toLowerCase()) === -1) break; } str = msg.str.slice(i + 1, j).toLowerCase(); mentions.push(str); msg = msgAddHtmlEntity(msg, i, i + str.length + 1, newHtmlEntityLink(_htmlFormatMsgLinkTemplateUser, $.MAL.userUrl(str), '@' + str) ); i = msg.i; } } // handling hashtags for (i = 0; i < msg.str.length - 1; i++) { if (msg.str[i] === '#' && msg.str[i + 1] !== '#' && stopCharsRight.indexOf(msg.str[i + 1]) === -1) { j = getSubStrEnd(msg.str, i + 1, stopCharsRightHashtags, false, stopCharsTrailing); if (j > i) { str = msg.str.slice(i + 1, j + 1); msg = msgAddHtmlEntity(msg, i, i + str.length + 1, newHtmlEntityLink(_htmlFormatMsgLinkTemplateHashtag, $.MAL.hashtagUrl(encodeURIComponent(str.toLowerCase())), '#' + str.replace(/&/g, '&')) ); i = msg.i; } } } // handling text style markup msg = markout(markout(markout(markout(msg, '*', 'b'), // bold '~', 'i'), // italic '_', 'u'), // underlined '-', 's') // striketrough ; // handling splitted posts numbering and escaping ampersands, qoutes and apostrophes msg.str = msg.str .replace(/\(\d{1,2}\/\d{1,2}\)$/, function (str) { msg.htmlEntities.push('' + str + ''); // FIXME return '>' + (msg.htmlEntities.length - 1).toString() + '<'; }) .replace(/&(?!lt;|gt;)/g, '&') // FIXME in many cases there is no need to escape ampersand in HTML 5 .replace(/"/g, '"') .replace(/'/g, ''') // applying html entities to msg.str and converting msg to string back msg = applyHtml(msg); // handling linebreaks if ($.Options.displayLineFeeds.val === 'enable') msg = msg.replace(/\n/g, '
'); return {html: msg, mentions: mentions}; } function proxyURL(url) { var proxyOpt = $.Options.useProxy.val; if (proxyOpt !== 'disable' && !$.Options.useProxyForImgOnly.val && url[0] !== '/' && url.indexOf(location.origin) !== 0) { // proxy alternatives may be added to options page FIXME currently not; and we need more fresh proxies if (proxyOpt === 'ssl-proxy-my-addr') { url = 'https://ssl-proxy.my-addr.org/myaddrproxy.php/' + url.substring(0, url.indexOf(':')) + url.substr(url.indexOf('/') + 1); } else if (proxyOpt === 'anonymouse') url = 'http://anonymouse.org/cgi-bin/anon-www.cgi/' + url; } return url; } function escapeHtmlEntities(str) { return str //.replace(/&/g, '&') we do it not here .replace(//g, '>') //.replace(/"/g, '"') //.replace(/'/g, ''') ; } function reverseHtmlEntities(str) { return str .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/&/g, '&'); } function setPostImagePreview(elem, links) { if ($.Options.displayPreview.val === 'enable') { var previewContainer = elem.find('.preview-container'); // was the preview added before... if (!previewContainer.children().length) { // is there any links to images in the post? for (var i = 0; i < links.length; i++) { if (/^[^?]+\.(?:jpe?g|gif|png)$/i.test(links[i].href)) { var url = proxyURL(links[i].href); previewContainer.append($('')); } } } if (previewContainer.children().length) previewContainer.show(); else previewContainer.hide(); } }