// twister_formatpost.js
// 2013 Miguel Freitas
//
// Format JSON posts and DMs to HTML.
// 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
* /
// Obtain data from userpost
var postJson = $ . toJSON ( post ) ;
var userpost = post [ "userpost" ] ;
if ( "rt" in userpost ) {
var rt = userpost [ "rt" ] ;
var n = rt [ "n" ] ;
var k = rt [ "k" ] ;
var t = rt [ "time" ] ;
var msg = rt [ "msg" ] ;
var content _to _rt = $ . toJSON ( rt ) ;
var content _to _sigrt = userpost [ "sig_rt" ] ;
var retweeted _by = userpost [ "n" ] ;
} else {
var n = userpost [ "n" ] ;
var k = userpost [ "k" ] ;
var t = userpost [ "time" ] ;
var msg = userpost [ "msg" ]
var content _to _rt = $ . toJSON ( userpost ) ;
var content _to _sigrt = post [ "sig_userpost" ] ;
var retweeted _by = undefined ;
}
// Now create the html elements
var elem = $ . MAL . getPostTemplate ( ) . clone ( true ) ;
elem . removeAttr ( 'id' ) ;
elem . addClass ( kind ) ;
elem . attr ( 'data-time' , t ) ;
if ( post [ "isNew" ] )
elem . addClass ( 'new' ) ;
var postData = elem . find ( ".post-data" ) ;
postData . addClass ( kind ) ;
postData . attr ( 'data-userpost' , postJson ) ;
postData . attr ( 'data-content_to_rt' , content _to _rt ) ;
postData . attr ( 'data-content_to_sigrt' , content _to _sigrt ) ;
postData . attr ( 'data-screen-name' , n ) ;
postData . attr ( 'data-id' , k ) ;
postData . attr ( 'data-lastk' , userpost [ "lastk" ] ) ;
postData . attr ( 'data-text' , msg ) ;
if ( "reply" in userpost ) {
postData . attr ( 'data-replied-to-screen-name' , userpost [ "reply" ] [ "n" ] ) ;
postData . attr ( 'data-replied-to-id' , userpost [ "reply" ] [ "k" ] ) ;
postData . find ( '.post-expand' ) . text ( polyglot . t ( "Show conversation" ) ) ;
} else if ( "rt" in userpost && "reply" in userpost [ "rt" ] ) {
postData . attr ( 'data-replied-to-screen-name' , userpost [ "rt" ] [ "reply" ] [ "n" ] ) ;
postData . attr ( 'data-replied-to-id' , userpost [ "rt" ] [ "reply" ] [ "k" ] ) ;
postData . find ( '.post-expand' ) . text ( polyglot . t ( "Show conversation" ) ) ;
}
var postInfoName = elem . find ( ".post-info-name" ) ;
postInfoName . attr ( 'href' , $ . MAL . userUrl ( n ) ) ;
postInfoName . text ( n ) ;
getFullname ( n , postInfoName ) ;
elem . find ( ".post-info-tag" ) . text = "@" + n ;
getAvatar ( n , elem . find ( ".avatar" ) ) ;
elem . find ( ".post-info-time" ) . text ( timeGmtToText ( t ) ) ;
elem . find ( ".post-info-time" ) . attr ( "title" , timeSincePost ( t ) ) ;
var mentions = [ ] ;
htmlFormatMsg ( msg , elem . find ( ".post-text" ) , mentions ) ;
postData . attr ( 'data-text-mentions' , mentions ) ;
var replyTo = "" ;
if ( n != defaultScreenName )
replyTo += "@" + n + " " ;
for ( var i = 0 ; i < mentions . length ; i ++ ) {
if ( mentions [ i ] != n && mentions [ i ] != defaultScreenName ) {
replyTo += "@" + mentions [ i ] + " " ;
}
}
if ( ! defaultScreenName )
{
elem . find ( ".post-area-new textarea" ) . attr ( "placeholder" , polyglot . t ( "You have to log in to post replies." ) ) ;
}
else
{
elem . find ( ".post-area-new textarea" ) . attr ( "placeholder" , polyglot . t ( "reply_to" , { fullname : replyTo } ) + "..." ) ;
}
elem . find ( ".post-area-new textarea" ) . attr ( "data-reply-to" , replyTo ) ;
postData . attr ( "data-reply-to" , replyTo ) ;
if ( retweeted _by != undefined ) {
elem . find ( ".post-context" ) . show ( ) ;
var retweetedByElem = elem . find ( ".post-retransmited-by" ) ;
retweetedByElem . attr ( "href" , $ . MAL . userUrl ( retweeted _by ) ) ;
retweetedByElem . text ( '@' + retweeted _by ) ;
}
if ( typeof ( promoted ) !== 'undefined' && promoted ) {
elem . find ( '.post-propagate' ) . remove ( ) ;
} else {
if ( $ . Options . getFilterLangOpt ( ) !== 'disable' && $ . Options . getFilterLangSimulateOpt ( ) ) {
// 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' : '<em>' + post [ 'langFilter' ] [ 'prob' ] [ 0 ] . toString ( ) + '</em>' } ) ;
else
var mlm = '' ;
elem . append ( '<div class="langFilterSimData">' + polyglot . t ( 'This post is treated by language filter' , { 'treated' : '<em>' + ( ( post [ 'langFilter' ] [ 'pass' ] ) ? polyglot . t ( 'passed' ) : polyglot . t ( 'blocked' ) ) + '</em>' } ) + '</div>' ) ;
elem . append ( '<div class="langFilterSimData">' + polyglot . t ( 'Reason: this' , { 'this' : '<em>' + post [ 'langFilter' ] [ 'reason' ] + '</em>' } ) + mlm + '</div>' ) ;
} else {
elem . append ( '<div class="langFilterSimData">' + polyglot . t ( 'This post is treated by language filter' , { 'treated' : '<em>' + polyglot . t ( 'not analyzed' ) + '</em>' } ) + '</div>' ) ;
}
}
}
return elem ;
}
// 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-dm-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 ( escapeHtmlEntities ( dmData . text ) ) ;
dmItem . find ( ".post-info-time" ) . text ( timeGmtToText ( dmData . time ) ) ;
dmItem . find ( ".post-info-time" ) . attr ( "title" , timeSincePost ( dmData . time ) ) ;
return dmItem ;
}
// format dmdata (returned by getdirectmsgs) to display in conversation thread
function dmDataToConversationItem ( dmData , localUser , remoteUser ) {
var classDm = dmData . fromMe ? "sent" : "received" ;
var dmItem = $ ( "#dm-chat-template" ) . clone ( true ) ;
dmItem . removeAttr ( 'id' ) ;
dmItem . addClass ( classDm ) ;
getAvatar ( dmData . fromMe ? localUser : remoteUser , dmItem . find ( ".post-photo" ) . find ( "img" ) ) ;
dmItem . find ( ".post-info-time" ) . text ( timeGmtToText ( dmData . time ) ) ;
dmItem . find ( ".post-info-time" ) . attr ( "title" , timeSincePost ( dmData . time ) ) ;
var mentions = [ ] ;
htmlFormatMsg ( dmData . text , dmItem . find ( ".post-text" ) , mentions ) ;
return dmItem ;
}
// convert message text to html, featuring @users and links formating.
// todo: hashtags
function htmlFormatMsg ( msg , output , mentions ) {
var tmp ;
var match = null ;
var index ;
var strUrlRegexp = "http[s]?://" ;
var strEmailRegexp = "\\S+@\\S+\\.\\S+" ;
var strSplitCounterR = "\\(\\d{1,2}\\/\\d{1,2}\\)$" ;
var reAll = new RegExp ( "(?:^|[ \\n\\t.,:\\/?!])(#|@|" + strUrlRegexp + "|" + strEmailRegexp + "|" + strSplitCounterR + ")" ) ;
var reHttp = new RegExp ( strUrlRegexp ) ;
var reEmail = new RegExp ( strEmailRegexp ) ;
var reSplitCounter = new RegExp ( strSplitCounterR ) ;
msg = escapeHtmlEntities ( msg ) ;
while ( msg != undefined && msg . length ) {
match = reAll . exec ( msg ) ;
if ( match ) {
index = ( match [ 0 ] === match [ 1 ] ) ? match . index : match . index + 1 ;
if ( match [ 1 ] == "@" ) {
output . append ( _formatText ( msg . substr ( 0 , index ) ) ) ;
tmp = msg . substr ( index + 1 ) ;
var username = _extractUsername ( tmp ) ;
if ( username . length ) {
if ( mentions . indexOf ( username ) < 0 )
mentions . push ( username ) ;
var userLinkTemplate = $ ( "#msg-user-link-template" ) . clone ( true ) ;
userLinkTemplate . removeAttr ( "id" ) ;
userLinkTemplate . attr ( "href" , $ . MAL . userUrl ( username ) ) ;
userLinkTemplate . text ( "@" + username ) ;
output . append ( userLinkTemplate ) ;
msg = tmp . substr ( String ( username ) . length ) ;
continue ;
}
output . append ( '@' ) ;
msg = tmp ;
continue ;
}
if ( reHttp . exec ( match [ 1 ] ) ) {
output . append ( _formatText ( msg . substr ( 0 , index ) ) ) ;
tmp = msg . substring ( index ) ;
var space = tmp . search ( /[ \n\t]/ ) ;
var url ;
if ( space != - 1 ) url = tmp . substring ( 0 , space ) ; else url = tmp ;
if ( url . length ) {
msg = tmp . substr ( String ( url ) . length ) ;
url = reverseHtmlEntities ( url ) ;
var extLinkTemplate = $ ( "#external-page-link-template" ) . clone ( true ) ;
extLinkTemplate . removeAttr ( "id" ) ;
if ( $ . Options . getUseProxyOpt ( ) !== 'disable' && ! $ . Options . getUseProxyForImgOnlyOpt ( ) ) {
//proxy alternatives may be added to options page...
if ( $ . Options . getUseProxyOpt ( ) === 'ssl-proxy-my-addr' ) {
extLinkTemplate . attr ( 'href' , 'https://ssl-proxy.my-addr.org/myaddrproxy.php/' +
url . substring ( 0 , url . indexOf ( ':' ) ) +
url . substr ( url . indexOf ( '/' ) + 1 ) ) ;
} else if ( $ . Options . getUseProxyOpt ( ) === 'anonymouse' ) {
extLinkTemplate . attr ( 'href' , 'http://anonymouse.org/cgi-bin/anon-www.cgi/' + url ) ;
}
} else {
extLinkTemplate . attr ( "href" , url ) ;
}
extLinkTemplate . text ( url ) ;
extLinkTemplate . attr ( "title" , url ) ;
output . append ( extLinkTemplate ) ;
continue ;
}
}
if ( reEmail . exec ( match [ 1 ] ) ) {
output . append ( _formatText ( msg . substr ( 0 , index ) ) ) ;
tmp = msg . substring ( index ) ;
var space = tmp . search ( /[ \n\t]/ ) ;
var email ;
if ( space != - 1 ) email = tmp . substring ( 0 , space ) ; else email = tmp ;
if ( email . length ) {
var extLinkTemplate = $ ( "#external-page-link-template" ) . clone ( true ) ;
extLinkTemplate . removeAttr ( "id" ) ;
extLinkTemplate . attr ( "href" , "mailto:" + email ) ;
extLinkTemplate . text ( email ) ;
extLinkTemplate . attr ( "title" , email ) ;
output . append ( extLinkTemplate ) ;
msg = tmp . substr ( String ( email ) . length ) ;
continue ;
}
}
if ( match [ 1 ] == "#" ) {
output . append ( _formatText ( msg . substr ( 0 , index ) ) ) ;
tmp = msg . substr ( index + 1 ) ;
var hashtag = _extractHashtag ( tmp ) ;
if ( hashtag . length ) {
var hashtag _lc = '' ;
for ( var i = 0 ; i < hashtag . length ; i ++ ) {
var c = hashtag [ i ] ;
hashtag _lc += ( c >= 'A' && c <= 'Z' ) ? c . toLowerCase ( ) : c ;
}
var hashtagLinkTemplate = $ ( "#hashtag-link-template" ) . clone ( true ) ;
hashtagLinkTemplate . removeAttr ( "id" ) ;
hashtagLinkTemplate . attr ( "href" , $ . MAL . hashtagUrl ( hashtag _lc ) ) ;
hashtagLinkTemplate . text ( "#" + hashtag ) ;
output . append ( hashtagLinkTemplate ) ;
msg = tmp . substr ( String ( hashtag ) . length ) ;
continue ;
}
output . append ( '#' ) ;
msg = tmp ;
continue ;
}
if ( reSplitCounter . exec ( match [ 1 ] ) ) {
output . append ( _formatText ( msg . substr ( 0 , index ) ) ) ;
tmp = msg . substring ( index ) ;
if ( tmp . length ) {
var splitCounter = $ ( '<span class="splited-post-counter"></span>' ) ;
splitCounter . text ( tmp ) ;
output . append ( splitCounter ) ;
msg = "" ;
continue ;
}
}
}
output . append ( _formatText ( msg ) ) ;
msg = "" ;
}
}
// internal function for htmlFormatMsg
function _formatText ( msg )
{
// TODO: add options for emotions and linefeeds
//msg = $.emotions(msg);
if ( $ . Options . getLineFeedsOpt ( ) == "enable" )
msg = msg . replace ( /\n/g , '<br />' ) ;
return msg ;
}
function _extractUsername ( s ) {
var username = "" ;
for ( var i = 0 ; i < s . length ; i ++ ) {
var c = s . charCodeAt ( i ) ;
if ( ( c >= 'a' . charCodeAt ( 0 ) && c <= 'z' . charCodeAt ( 0 ) ) ||
( c >= 'A' . charCodeAt ( 0 ) && c <= 'Z' . charCodeAt ( 0 ) ) ||
( c >= '0' . charCodeAt ( 0 ) && c <= '9' . charCodeAt ( 0 ) ) ||
c == '_' . charCodeAt ( 0 ) ) {
username += s [ i ] ;
} else {
break ;
}
}
return username . toLowerCase ( ) ;
}
// internal function for htmlFormatMsg
function _extractHashtag ( s ) {
var hashtag = "" ;
s = reverseHtmlEntities ( s ) ;
for ( var i = 0 ; i < s . length ; i ++ ) {
if ( " \n\t.,:/?!;'\"()[]{}*#" . indexOf ( s [ i ] ) < 0 ) {
hashtag += s [ i ] ;
} else {
break ;
}
}
return hashtag ;
}
function escapeHtmlEntities ( str ) {
return str
. replace ( /&/g , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( /"/g , '"' )
. replace ( /'/g , ''' ) ;
}
function reverseHtmlEntities ( str ) {
return str
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( /"/g , '"' )
. replace ( /'/g , "'" )
. replace ( /&/g , '&' ) ;
}