// ==UserScript==
// @name Tsu Helper
// @namespace tsu-helper
// @description Tsu script that adds a bunch of tweaks to make Tsu more user friendly.
// @include http://*tsu.co*
// @include https://*tsu.co*
// @version 2.6
// @copyright 2014-2015 Armando Lüscher
// @author Armando Lüscher
// @oujs:author noplanman
// @grant none
// @homepageURL https://j.mp/tsu-helper
// @supportURL https://j.mp/tsu-helper-issues
// ==/UserScript==
/**
* For changelog see https://j.mp/tsu-helper-changelog
*/
/**
* How nice of you to visit! I've tried to make this code as clean as possible with lots of
* comments for everybody to learn from.
*
* Because that is what this life is about, to learn from each other and to grow together!
*
* If you have any questions, ideas, feature requests, (pretty much anything) about it, just ask :-)
*
* Simply visit the GitHub page here: https://j.mp/tsu-helper-issues and choose "New Issue".
* I will then get back to you as soon as I can ;-)
*/
// Make sure we have jQuery loaded.
if ( ! ( 'jQuery' in window ) ) { return false; }
// Run everything as soon as the DOM is set up.
jQuery( document ).ready(function( $ ) {
// Display Debug options? (for public).
var publicDebug = false;
/**
* Base64 library, just decoder: http://www.webtoolkit.info/javascript-base64.html
* @param {string} e Base64 string to decode.
*/
function base64_decode(e){var t='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';var n='';var r,i,s;var o,u,a,f;var l=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,'');while(l<e.length){o=t.indexOf(e.charAt(l++));u=t.indexOf(e.charAt(l++));a=t.indexOf(e.charAt(l++));f=t.indexOf(e.charAt(l++));r=o<<2|u>>4;i=(u&15)<<4|a>>2;s=(a&3)<<6|f;n=n+String.fromCharCode(r);if(a!=64){n=n+String.fromCharCode(i);}if(f!=64){n=n+String.fromCharCode(s);}}return n;}
// Check if a string starts with a certain string.
'function'!=typeof String.prototype.startsWith&&(String.prototype.startsWith=function(t){return this.slice(0,t.length)==t;});
// Check if a string ends with a certain string.
'function'!=typeof String.prototype.endsWith&&(String.prototype.endsWith=function(t){return this.slice(-t.length)==t;});
// Check if a string contains a certain string.
'function'!=typeof String.prototype.contains&&(String.prototype.contains=function(t){return this.indexOf(t)>=0;});
// Add stringify to jQuery (https://gist.github.com/chicagoworks/754454).
jQuery.extend({stringify:function(r){if('JSON'in window)return JSON.stringify(r);var n=typeof r;if('object'!=n||null===r)return'string'==n&&(r='"'+r+'"'),String(r);var t,i,e=[],o=r&&r.constructor==Array;for(t in r)i=r[t],n=typeof i,r.hasOwnProperty(t)&&('string'==n?i='"'+i+'"':'object'==n&&null!==i&&(i=jQuery.stringify(i)),e.push((o?'':'"'+t+'":')+String(i)));return(o?'[':'{')+String(e)+(o?']':'}');}});
// Serialize form data to save settings (http://stackoverflow.com/questions/1184624/convert-form-data-to-js-object-with-jquery).
$.fn.serializeObject=function(){var i={},e=this.serializeArray();return $.each(e,function(){void 0!==i[this.name]?(i[this.name].push||(i[this.name]=[i[this.name]]),i[this.name].push(this.value||"")):i[this.name]=this.value||"";}),i;};
/**
* Like WP, return "selected" attribute text.
* @param {string} val1 Value to compare.
* @param {string} val2 Value to compare with.
* @return {string} "Selected" text or nothing.
*/
function selected( val1, val2 ) {
return ( val1 === val2 ) ? ' selected="selected"' : '';
}
/**
* Like WP, return "checked" attribute text.
* @param {string} val1 Value to compare.
* @param {string} val2 Value to compare with.
* @return {string} "Checked" text or nothing.
*/
function checked( val1, val2 ) {
// Compare to "true" by default.
val2 = ( undefined === val2 ) ? true : val2;
return ( val1 === val2 ) ? ' checked="checked"' : '';
}
/**
* All settings related methods and variables.
*/
var Settings = {
// All available settings with default values.
settingsDefault : {
debugLevel : 'disabled', // Debugging level. (disabled,[l]og,[i]nfo,[w]arning,[e]rror)
hideAds : false, // Hide all ads.
quickMention : true, // Add quick mention links.
emphasizeNRP : true, // Emphasize nested replies parents.
checkSocial : true, // Check the social network sharing.
checkMaxHM : true, // Check for maximum hashtags and mentions.
notifReloaded : 10 // How many items to display on the Notifications popup (0=disabled)
},
// Init with default settings on "load".
settings : {},
// Name used for the settings cookie.
cookieName : 'tsu-helper-settings',
/**
* Set default settings.
*/
setDefaults : function( $form ) {
Settings.populateForm( $form, true );
},
/**
* Load settings from cookie.
*/
load : function() {
// Init with defaults and add all loaded settings.
$.extend( true, Settings.settings, Settings.settingsDefault );
var savedJSON = $.cookie( Settings.cookieName );
if ( savedJSON ) {
$.extend( Settings.settings, $.parseJSON( savedJSON ) );
}
return Settings.settings;
},
/**
* Populate the passed form with the current settings.
* @param {jQuery} $form The form to be populated.
* @param {boolean} defaults Load the default values?
*/
populateForm : function( $form, defaults ) {
if ( $form ) {
for ( var setting in Settings.settings ) {
if ( Settings.settings.hasOwnProperty( setting ) ) {
var $input = $( '[name=' + setting + ']', $form );
var val = ( defaults ) ? Settings.settingsDefault[ setting ] : Settings.settings[ setting ];
if ( 'checkbox' === $input.attr( 'type' ) ) {
$input.prop( 'checked', val );
} else {
$input.val( val );
}
}
}
}
},
/**
* Save settings to cookie.
*/
save : function( $form ) {
// First save?
if ( undefined === $.cookie( Settings.cookieName ) && ! confirm( 'Settings will be saved in a cookie. Ok?' ) ) {
return false;
}
// If a form is passed, use those values.
if ( $form ) {
// Default to false and then get form settings.
// This is necessary for checkbox inputs as they are assigned by checked state, not value.
Settings.settings.hideAds = false;
Settings.settings.quickMention = false;
Settings.settings.emphasizeNRP = false;
Settings.settings.checkSocial = false;
Settings.settings.checkMaxHM = false;
$.extend( Settings.settings, $form.serializeObject() );
}
$.cookie( Settings.cookieName, $.stringify( Settings.settings ), { expires: 999, path: '/' } );
return true;
}
};
// Load settings from cookie.
var settings = Settings.load();
/**
* All updater and version related variables.
*/
var Updater = {
// The local version.
localVersion : 2.6,
// The remote version (loaded in the "check" method).
remoteVersion : null,
// URL where to get the newest script.
scriptURL : 'https://openuserjs.org/install/noplanman/Tsu_Helper.user.js',
// Version details.
versionAPIURL : 'https://api.github.com/repos/noplanman/tsu-helper/contents/VERSION',
// Get the remote version on GitHub.
init : function() {
try {
var response = $.ajax({
type: 'GET',
url: Updater.versionAPIURL,
async: false
}).fail(function() {
doLog( 'Couldn\'t get remote version number for Tsu Helper.', 'w' );
}).responseJSON;
// Set the remote version.
Updater.remoteVersion = parseFloat( base64_decode( response.content ) );
doLog( 'Versions: Local (' + Updater.localVersion + '), Remote (' + Updater.remoteVersion + ')', 'i' );
} catch( e ) {
doLog( 'Couldn\'t get remote version number for Tsu Helper.', 'w' );
}
},
/**
* Is there a newer version available?
* @return {Boolean} If there is a newer version available.
*/
hasUpdate : function() {
return ( Updater.remoteVersion > Updater.localVersion );
}
};
// Initialise the updater to fetch the remote version.
Updater.init();
/**
* TSU constants.
*/
var TSUConst = {
// Define the maximum number of hashtags and mentions allowed.
maxHashtags : 10,
maxMentions : 10,
// Texts for all possible notifications.
kindsTexts : {
friend_request_accepted : 'Friend Requests accepted',
new_comment_on_post : 'Comments on your Posts',
new_comment_on_post_you_commented_on : 'Comments on other Posts',
new_follower : 'New Followers',
new_like_on_post : 'Likes on your Posts',
new_like_on_comment : 'Likes on your Comments',
new_post_on_your_wall : 'Posts on your Wall',
someone_mentioned_you_in_a_post : 'Mentioned in a Post or Comment',
someone_shared_your_post : 'Shares of your Posts',
donation_received : 'Donations received',
someone_joined_your_network : 'Users who joined your Network'
}
};
/**
* Page related things.
*/
var Page = {
// The current page.
current : '',
/**
* Get the current page to know which queries to load and observe and
* also for special cases of how the Friends and Followers details are loaded.
*/
init : function() {
doLog( 'Getting current page.', 'i' );
if ( $( 'body.newsfeed' ).length ) { Page.current = 'home'; // Home feed.
} else if ( $( 'body.notifications.show' ).length
|| $( 'body.notifications.index' ).length ) { Page.current = 'notifications'; // Show notifications.
} else if ( $( 'body.search_hashtag' ).length ) { Page.current = 'hashtag'; // Hashtag page.
} else if ( $( 'body.profile.diary' ).length ) { Page.current = 'diary'; // Diary.
} else if ( $( 'body.show_post' ).length ) { Page.current = 'post'; // Single post.
} else if ( $( 'body.dashboard' ).length ) { Page.current = 'analytics'; // Analytics.
Observer.queryToObserve = ''; // No observer necessary!
} else if ( $( 'body.messages' ).length ) { Page.current = 'messages'; // Messages.
Observer.queryToLoad = '.messages_content .message_box';
Observer.queryToObserve = '.messages_content';
}
// Group queries to load.
if ( Page.is( 'has-posts' ) ) {
queryToLoad = '.comment';
// Add userlinks to query.
Observer.queryToLoadFF = '.card .card_sub .info';
}
Observer.queryToLoad += ',' + Observer.queryToLoadFF;
doLog( 'Current page: ' + Page.current, 'i' );
},
/**
* Check if the passed page is the current one.
* @param {string} pages Comma seperated list of pages.
* @return {boolean} If the current page is in the list.
*/
is : function( pages ) {
// To make things easier, allow shortcuts.
pages = pages.replace( /has-userlinks/g, 'has-posts messages' );
pages = pages.replace( /has-posts/g, 'home hashtag diary post' );
// Make an array.
pages = pages.split( ' ' );
// Is the current page in the passed page list?
for ( var i = pages.length - 1; i >= 0; i-- ) {
if ( Page.current === pages[i] ) {
return true;
}
}
return false;
}
};
/**
* Quick Mention links.
*/
var QM = {
// The currently active textarea to insert the @mentions.
$activeReplyTextArea : null,
/**
* Add text to the passed textarea input field.
* @param {jQuery} $textArea jQuery object of the textarea input field.
* @param {string} text Text to add.
*/
addTextToTextArea : function( $textArea, text ) {
if ( $textArea ) {
var textAreaText = $textArea.val();
var caretPos1 = $textArea[0].selectionStart;
var caretPos2 = $textArea[0].selectionEnd;
$textArea.val( textAreaText.substring( 0, caretPos1 ) + text + textAreaText.substring( caretPos2 ) );
$textArea[0].selectionStart = $textArea[0].selectionEnd = caretPos1 + text.length;
$textArea.focus();
}
},
/**
* Add the @mention links to the replies.
*/
load : function() {
// Make sure the setting is enabled and we're on the right page.
if ( ! settings.quickMention || ! Page.is( 'has-posts' ) ) {
return;
}
doLog( 'Adding Quick Mention links.', 'i' );
// Process all reply links to autofocus the reply textarea input field.
$( '.load_more_post_comment_replies' ).not( '.th-qm-reply-processed' ).each(function() {
var $replyLink = $( this );
$replyLink.click(function() {
var $postComment = $replyLink.closest( '.post_comment' );
var $replyContainer = $postComment.siblings( '.comment_reply_container' );
var $textArea = $replyContainer.children( '.post_write_comment' ).find( '#comment_text' );
// This gets called before the "official" click, so the logic is inversed!
// And delay everything a bit too, as it gets lazy-loaded.
if ( $replyContainer.is( ':visible' ) ) {
setTimeout(function() {
// Only set the active textarea null if it's this one.
if ( $textArea[0] === QM.$activeReplyTextArea[0] ) {
QM.$activeReplyTextArea = null;
// Hide all @ links.
$( '.th-qm-reply' ).hide();
}
}, 100);
} else {
setTimeout(function() {
$postComment.find( '.th-qm-reply' ).show();
$textArea.focus();
}, 100);
}
});
$replyLink.addClass( 'th-qm-reply-processed' );
});
// Process all comment / reply textarea input fields to set themselves as active on focus.
$( '.post_write_comment #comment_text' ).not( '.th-qm-textarea-processed' ).each(function() {
$( this ).focusin( function() {
QM.$activeReplyTextArea = $( this );
$( '.th-qm-active-input' ).removeClass( 'th-qm-active-input' );
QM.$activeReplyTextArea.closest( '.expandingText_parent' ).addClass( 'th-qm-active-input' );
});
$( this ).addClass( 'th-qm-textarea-processed' );
});
// Link for all comments.
$( '.post_comment_header' ).not( '.th-qm-added' ).each(function() {
var $head = $( this );
var $commentArea = $head.closest( '.post_comment' );
// Get just the last part of the href, the username.
var hrefBits = $head.find( 'a' ).attr( 'href' ).split( '/' );
var atUsername = '@' + hrefBits[ hrefBits.length - 1 ] + ' ';
var $mentionLink = $( '<a/>', {
class : 'th-qm-reply',
html : '@ +',
title : 'Add ' + atUsername + 'to current reply.',
click : function() {
QM.addTextToTextArea( QM.$activeReplyTextArea, atUsername );
}
})
.hide(); // Start hidden, as it will appear with the mouse over event.
// Show / hide link on hover / blur if there is an active reply input selected.
$commentArea.hover(
function() { if ( QM.$activeReplyTextArea && QM.$activeReplyTextArea.length ) { $mentionLink.show(); } },
function() { $mentionLink.hide(); }
);
$head.addClass( 'th-qm-added' );
// Position the @ link.
var $profilePic = $head.find( '.post_profile_picture' );
var offset = $profilePic.position();
$mentionLink.offset({ top: offset.top + $profilePic.height(), left: offset.left });
$head.append( $mentionLink );
});
// Link for all textareas.
$( '.post_write_comment' ).not( '.th-qm-added' ).each(function() {
var $commentArea = $( this );
var $commentInput = $commentArea.find( '#comment_text' );
var $head = null;
var linkElement = null;
var isReply = $commentArea.hasClass( 'reply' );
// Is this a nested comment? Then use the previous reply as the username.
if ( isReply ) {
$head = $commentArea.closest( '.comment' ).find( '.post_comment .post_comment_header' );
linkElement = 'a';
} else {
// Get the current post to determine the username.
var $post = $commentArea.closest( '.post' );
// Defaults as if we have a shared post.
$head = $post.find( '.share_header' );
linkElement = '.evac_user a';
// If it's not a share, get the post header.
if ( 0 === $head.length ) {
$head = $post.find( '.post_header' );
linkElement = '.post_header_pp a';
}
}
// Get just the last part of the href, the username.
var hrefBits = $head.find( linkElement ).attr( 'href' ).split( '/' );
var atUsername = '@' + hrefBits[ hrefBits.length - 1 ] + ' ';
var $mentionLink = $( '<a/>', {
class : 'th-qm-comment',
html : '@ >',
title : 'Add ' + atUsername + 'to this ' + ( ( isReply ) ? 'reply.' : 'comment.' ),
click : function() {
QM.addTextToTextArea( $commentInput, atUsername );
}
})
.hide(); // Start hidden, as it will appear with the mouse over event.
// Show / hide link on hover / blur.
$commentArea.hover(
function() { $mentionLink.show(); },
function() { $mentionLink.hide(); }
);
$commentArea.addClass( 'th-qm-added' );
$commentArea.find( '.post_profile_picture' ).parent().after( $mentionLink );
});
}
};
/**
* The MutationObserver to detect page changes.
*/
var Observer = {
// The mutation observer object.
observer : null,
// The elements that we are observing.
queryToObserve : 'body',
// The query of objects that trigger the observer.
queryToLoad : '',
// The query of userlinks to look for.
queryToLoadFF : '',
/**
* Start observing for DOM changes.
*/
init : function() {
// Check if we can use the MutationObserver.
if ( 'MutationObserver' in window ) {
// Are we observing anything on this page?
if ( '' === Observer.queryToObserve ) {
return;
}
var toObserve = document.querySelector( Observer.queryToObserve );
if ( toObserve ) {
doLog( 'Started Observer.', 'i' );
Observer.observer = new MutationObserver( function( mutations ) {
function itemsInArray( needles, haystack ) {
for ( var i = needles.length - 1; i >= 0; i-- ) {
if ( $.inArray( needles[ i ], haystack ) > -1 ) {
return true;
}
}
return false;
}
// Helper to determine if added or removed nodes have a specific class.
function mutationNodesHaveClass( mutation, classes ) {
classes = classes.split( ' ' );
// Added nodes.
for ( var ma = mutation.addedNodes.length - 1; ma >= 0; ma-- ) {
var addedNode = mutation.addedNodes[ ma ];
// In case the node has no className (e.g. textnode), just ignore it.
if ( 'className' in addedNode && 'string' === typeof addedNode.className && itemsInArray( addedNode.className.split( ' ' ), classes ) ) {
return true;
}
}
// Removed nodes.
for ( var mr = mutation.removedNodes.length - 1; mr >= 0; mr-- ) {
var removedNode = mutation.removedNodes[ mr ];
// In case the node has no className (e.g. textnode), just ignore it.
if ( 'className' in removedNode && 'string' === typeof removedNode.className && itemsInArray( removedNode.className.split( ' ' ), classes ) ) {
return true;
}
}
return false;
}
doLog( mutations.length + ' DOM changes.' );
doLog( mutations );
// Only react to changes we're interested in.
for ( var m = mutations.length - 1; m >= 0; m-- ) {
var $hoverCard = $( '.tooltipster-user-profile' );
// Are we on a hover card?
if ( $hoverCard.length && mutationNodesHaveClass( mutations[ m ], 'tooltipster-user-profile' ) ) {
FFC.loadUserHoverCard( $hoverCard.find( '.card .card_sub .info' ) );
}
// Run all functions responding to DOM updates.
// When loading a card, only if it's not a hover card, as those get loaded above.
if ( mutationNodesHaveClass( mutations[ m ], 'post comment message_content_feed message_box' )
|| ( mutationNodesHaveClass( mutations[ m ], 'card' ) && $hoverCard.length === 0 ) ) {
FFC.loadAll();
QM.load();
emphasizeNestedRepliesParents();
tweakMessagesPage();
}
}
});
// Observe child and subtree changes.
Observer.observer.observe( toObserve, {
childList: true,
subtree: true
});
}
} else {
// If we have no MutationObserver, use "waitForKeyElements" function.
// Instead of using queryToObserve, we wait for the ones that need to be loaded, queryToLoad.
$.getScript( 'https://gist.github.com/raw/2625891/waitForKeyElements.js', function() {
doLog( 'Started Observer (waitForKeyElements).', 'i' );
// !! Specifically check for the correct page to prevent overhead !!
if ( Page.is( 'has-userlinks' ) ) {
waitForKeyElements( Observer.queryToLoad, FFC.loadAll() );
}
if ( Page.is( 'has-posts' ) ) {
waitForKeyElements( Observer.queryToLoad, QM.load );
waitForKeyElements( Observer.queryToLoad, emphasizeNestedRepliesParents );
}
if ( Page.is( 'messages' ) ) {
waitForKeyElements( Observer.queryToLoad, tweakMessagesPage );
}
});
}
}
};
/**
* Post related things.
*/
var Posting = {
// Are we busy waiting for the popup to appear?
waitingForPopup : false,
/**
* Initialise.
*/
init : function() {
// Remind to post to FB and Twitter in case forgotten to click checkbox.
$( '#create_post_form' ).submit(function( event ) {
return Posting.postFormSubmit( $( this ), event );
});
// Set focus to message entry field on page load.
if ( Page.is( 'home diary' ) ) {
$( '#text' ).focus();
}
// When using the "Create" or "Message" buttons, wait for the post input form.
$( 'body' ).on( 'click', '.create_post_popup, .message_pop_up', function() {
if ( ! Posting.waitingForPopup ) {
if ( $( this ).hasClass( 'create_post_popup' ) ) {
Posting.waitForPopup( 'post' );
} else if ( $( this ).hasClass( 'message_pop_up' ) ) {
Posting.waitForPopup( 'message' );
}
}
});
// Auto-focus title entry field when adding title.
$( 'body' ).on( 'click', '.create_post .options .add_title', function() {
var $postForm = $( this ).closest( '#create_post_form' );
var $postTitle = $postForm.find( '#title' );
// Focus title or text field, depending if the title is being added or removed.
if ( $postTitle.is( ':visible' ) ) {
setTimeout( function() { $postForm.find( '#text' ).focus(); }, 50 );
} else {
setTimeout( function() { $postTitle.focus(); }, 50 );
}
});
// Auto-focus message entry field when adding/removing image.
$( 'body' ).on( 'click', '.create_post .options .filebutton, .cancel_icon_createpost', function() {
var $postText = $( this ).closest( '#create_post_form' ).find( '#text' );
setTimeout( function() { $postText.focus(); }, 50 );
});
/**
* Open post by double clicking header (only on pages with posts).
*/
if ( ! Page.is( 'has-posts' ) ) {
return;
}
$( 'body' ).on( 'dblclick', '.post_header_name, .share_header', function( event ) {
var $post = $( this ).closest( '.post' );
var isShare = $post.find( '.share_header' ).length;
var isOriginal = ! $( this ).hasClass( 'share_header' );
$post.find( '#post_link_dropdown a' ).each(function() {
var linkText = $( this ).text().trim().toLowerCase();
if ( ( ! isShare && 'open' === linkText )
|| ( ! isOriginal && 'open' === linkText )
|| ( isOriginal && 'open original post' === linkText ) ) {
var url = $( this ).attr( 'href' );
// If the shift key is pressed, open in new window / tab.
if ( event.shiftKey ) {
window.open( url, '_blank' ).focus();
} else {
window.location = url;
}
return;
}
});
});
},
/**
* Check for the maximum number of hashtags and mentions.
* @param {string} message The message being posted.
* @return {boolean} True = submit, False = cancel, Null = not too many
*/
checkMaximumHashtagsMentions : function( message ) {
// Check if the setting is enabled.
if ( ! settings.checkMaxHM ) {
return null;
}
// Get number of hashtags and mentions in the message.
var nrOfHashtags = message.split( '#' ).length - 1;
doLog( nrOfHashtags + ' Hashtags found.' );
var nrOfMentions = message.split( '@' ).length - 1;
doLog( nrOfMentions + ' Mentions found.' );
// If the limits aren't exeeded, just go on to post.
if ( nrOfHashtags <= TSUConst.maxHashtags && nrOfMentions <= TSUConst.maxMentions ) {
return null;
}
// Set up warning message.
var warning = 'Limits may be exceeded, check your message!\nAre you sure you want to continue?\n';
if ( nrOfHashtags > TSUConst.maxHashtags ) {
warning += '\n' + nrOfHashtags + ' #hashtags found. (Max. ' + TSUConst.maxHashtags + ')';
doLog( 'Too many hashtags found! (' + nrOfHashtags + ')', 'w' );
}
if ( nrOfMentions > TSUConst.maxMentions ) {
warning += '\n' + nrOfMentions + ' @mentions found. (Max. ' + TSUConst.maxMentions + ')';
doLog( 'Too many mentions found! (' + nrOfMentions + ')', 'w' );
}
// Last chance to make sure about hashtags and mentions.
return confirm( warning );
},
/**
* Check if the social network sharing has been selected.
* @param {jQuery} $form Form jQuery object of the form being submitted.
* @return {boolean} True = submit, False = cancel, Null = all selected
*/
checkSocialNetworkSharing : function( $form ) {
// Check if the setting is enabled.
if ( ! settings.checkSocial ) {
return null;
}
var share_facebook = null;
var share_twitter = null;
// Get all visible (connected) checkboxes. If any are not checked, show warning.
$form.find( '.checkboxes_options_create_post input:visible' ).each(function() {
switch ( $( this ).attr( 'id' ) ) {
case 'facebook': share_facebook = $( this ).prop( 'checked' ); break;
case 'twitter': share_twitter = $( this ).prop( 'checked' ); break;
}
});
// If no social network accounts are connected, just go on to post.
if ( false !== share_facebook && false !== share_twitter ) {
return null;
}
var post_to = 'OK = Post to Tsu';
// Share to facebook?
if ( true === share_facebook ) {
post_to += ', Facebook';
}
// Share to twitter?
if ( true === share_twitter ) {
post_to += ', Twitter';
}
// Last chance to enable sharing to social networks...
return confirm( post_to + '\nCancel = Choose other social networks' );
},
/**
* Called on form submit.
* @param {jQuery} $form Form jQuery object of the form being submitted.
* @param {event} event The form submit event.
*/
postFormSubmit : function( $form, event ) {
// In case the post gets cancelled, make sure the message field is focused.
var message = $form.find( '#text' ).focus().val();
var title = $form.find( '#title' ).val();
var hasPic = ( '' !== $form.find( '#create_post_pic_preview' ).text() );
// Make sure something was entered (title, text or image.
// Check for the maximum number of hashtags and mentions,
// and if the Social network sharing warning has been approved.
if ( ( '' !== message || '' !== title || hasPic )
&& false !== Posting.checkMaximumHashtagsMentions( message )
&& false !== Posting.checkSocialNetworkSharing( $form ) ) {
doLog( 'Post!' );
return;
}
/**************************
* CANCEL FORM SUBMISSION! *
**************************/
doLog( 'DONT Post!' );
// Prevent form post.
event.preventDefault();
// Hide the loader wheel.
$form.find( '.loading' ).hide();
// Make sure to enable the post button again. Give it some time, as Tsu internal script sets it to disabled.
setTimeout(function(){
$form.find( '#create_post_button' ).removeAttr( 'disabled' );
}, 500 );
return false;
},
/**
* Wait for the fancybox popup to create a new post.
*/
waitForPopup : function( action ) {
Posting.waitingForPopup = true;
var formSelector;
var inputSelector;
switch ( action ) {
case 'post' :
formSelector = '.fancybox-wrap #create_post_form';
inputSelector = '#text';
break;
case 'message' :
formSelector = '.fancybox-wrap #new_message';
inputSelector = '#message_body';
break;
}
var $form = $( formSelector );
if ( $form.length ) {
$form.find( inputSelector ).focus();
// Apply checks to posts only!
if ( 'post' === action ) {
$form.submit(function( event ) {
return Posting.postFormSubmit( $( this ), event );
});
}
Posting.waitingForPopup = false;
return;
}
// Wait around for it longer...
setTimeout( function() { Posting.waitForPopup( action ); }, 500 );
}
};
/**
* User object containing info for Friends and Followers counter.
* @param {string|integer} userID Depending on the context, this is either the user id as a number or unique username / identifier.
* @param {string} userName The user's full name.
* @param {string} userUrl The url to the user profile page.
*/
function UserObject( userID, userName, userUrl ) {
// Keep track if this user object has already finished loading.
this.finished = false;
this.userID = userID;
this.userName = userName;
this.userUrl = userUrl;
// Add to all userObjects list.
Users.userObjects[ userID ] = this;
// Queue of user link spans to refresh once the user object has finished loading.
this.userLinkSpans = [];
doLog( '(' + userID + ':' + userName + ') New user loaded.' );
/**
* Set the friends info.
* @param {jQuery} $friendsLink The jQuery <a> object linking to the user's Friends page.
* @param {[string} friendsUrl The URL to the user's Friends page.
* @param {[string} friendsCount The user's number of friends.
*/
this.setFriendsInfo = function( $friendsLink, friendsUrl, friendsCount ) {
this.$friendsLink = $friendsLink;
this.friendsUrl = friendsUrl;
this.friendsCount = friendsCount;
};
/**
* Set the followers info.
* @param {jQuery} $followersLink The jQuery <a> object linking to the user's Followers page.
* @param {string} followersUrl The URL to the user's Followers page.
* @param {string} followersCount The user's number of Followers.
*/
this.setFollowersInfo = function( $followersLink, followersUrl, followersCount ) {
this.$followersLink = $followersLink;
this.followersUrl = followersUrl;
this.followersCount = followersCount;
};
/**
* Return a clone of the Friends page link.
* @return {jQuery} Friends page link.
*/
this.getFriendsLink = function() {
return this.$friendsLink.clone();
};
/**
* Return a clone of the Followers page link.
* @return {jQuery} Followers page link.
*/
this.getFollowersLink = function() {
return this.$followersLink.clone();
};
/**
* Set this user object as finished loading.
*/
this.setFinished = function() {
this.finished = true;
doLog( '(id:' + this.userID + ') Finished loading.' );
};
/**
* Is this user object already loaded?
* @return {Boolean}
*/
this.isFinished = function() {
return this.finished;
};
/**
* Add a user link span to the queue to be refreshed once the user object is loaded.
* @param {jQuery} $userLinkSpan The user link span object.
*/
this.queueUserLinkSpan = function( $userLinkSpan ) {
this.userLinkSpans.push( $userLinkSpan );
};
/**
* Refresh the passed $userLinkSpan with the user details.
* @param {jQuery} $userLinkSpan The <span> jQuery object to appent the details to.
* @param {integer} tries The number of tries that have already been used to refresh the details.
*/
this.refresh = function( $userLinkSpan, tries ) {
if ( undefined === tries || null === tries ) {
tries = 0;
}
// If the maximum tries has been exeeded, return.
if ( tries > FFC.maxTries ) {
// Just remove the failed link span, maybe it will work on the next run.
$userLinkSpan.remove();
// Remove all queued ones too to prevent infinite loader image.
for ( var i = this.userLinkSpans.length - 1; i >= 0; i-- ) {
this.userLinkSpans[ i ].remove();
}
this.userLinkSpans = [];
doLog( '(id:' + this.userID + ') Maximum tries exeeded!', 'w' );
return;
}
if ( this.isFinished() ) {
// Add the user details after the user link.
this.queueUserLinkSpan( $userLinkSpan );
// Update all listening user link spans.
for ( var i = this.userLinkSpans.length - 1; i >= 0; i-- ) {
this.userLinkSpans[ i ].empty().append( this.getFriendsLink(), this.getFollowersLink() );
}
// Empty the queue, as there is no need to reload already loaded ones.
this.userLinkSpans = [];
doLog( '(' + this.userID + ':' + this.userName + ') Friends and Followers set.' );
} else {
var t = this;
setTimeout(function() {
t.refresh( $userLinkSpan, tries + 1);
}, 1000);
}
};
}
/**
* Friends and Followers counts manager.
*/
var FFC = {
// Max number of tries to get friend and follower info (= nr of seconds).
maxTries : 60,
/**
* Load a user link.
* @param {jQuery} $userElement The element that contains the user link.
* @param {boolean} onHoverCard Is this element a hover card?
*/
loadUserLink : function( $userElement, onHoverCard ) {
// If this link has already been processed, skip it.
if ( $userElement.hasClass( 'ffc-processed' ) ) {
return;
}
// Set the "processed" flag to prevent loading the same link multiple times.
$userElement.addClass( 'ffc-processed' );
// Because the user link is in a nested entry.
var $userLink = $userElement.find( 'a:first' );
// If no link has been found, continue with the next one. Fail-safe.
if ( 0 === $userLink.length ) {
return;
}
// Add a new <span> element to the user link.
var $userLinkSpan = $( '<span/>', { html: '<img class="th-ffc-loader-wheel" src="/assets/loader.gif" alt="Loading..." />', class: 'th-ffc-span' } );
$userLink.after( $userLinkSpan );
// Special case for these pages, to make it look nicer and fitting.
if ( onHoverCard ) {
$userLinkSpan.before( '<br class="th-ffc-br" />' );
}
// Get the user info from the link.
var userName = $userLink.text().trim();
var userUrl = $userLink.attr( 'href' );
// Extract the userID from the url.
var userID = userUrl.split( '/' )[1];
// Check if the current user has already been loaded.
var userObject = Users.getUserObject( userID, true );
// Add this span to the list that needs updating when completed.
if ( userObject instanceof UserObject ) {
// If this user has already finished loading, just update the span, else add it to the queue.
if ( userObject.isFinished() ) {
userObject.refresh( $userLinkSpan, 0 );
} else {
userObject.queueUserLinkSpan( $userLinkSpan );
}
return;
}
// Create a new UserObject and load it's data.
userObject = new UserObject( userID, userName, userUrl );
// Load the numbers from the user profile page.
setTimeout( function() { $.get( userUrl, function( response ) {
// Get rid of all images first, no need to load those, then find the links.
var $numbers = $( response.replace( /<img[^>]*>/g, '' ) ).find( '.profile_details .numbers a' );
// If the user doesn't exist, just remove the span.
if ( 0 === $numbers.length ) {
$userLinkSpan.remove();
return;
}
// Set up the Friends link.
var $friends = $numbers.eq( 0 );
var friendsUrl = $friends.attr( 'href' );
var friendsCount = $friends.find( 'span' ).text();
var $friendsLink = $( '<a/>', {
href: friendsUrl,
html: friendsCount
});
// Set up the Followers link.
var $followers = $numbers.eq( 1 );
var followersUrl = $followers.attr( 'href' );
var followersCount = $followers.find( 'span' ).text();
var $followersLink = $( '<a/>', {
href: followersUrl,
html: followersCount
});
// Add titles to pages without posts and not on hover cards.
if ( ! onHoverCard && ! Page.is( 'has-posts' ) ) {
$friendsLink.attr( 'title', 'Friends' );
$followersLink.attr( 'title', 'Followers' );
}
// Add the Friends and Followers details, then refresh all userlink spans.
userObject.setFriendsInfo( $friendsLink, friendsUrl, friendsCount );
userObject.setFollowersInfo( $followersLink, followersUrl, followersCount );
userObject.refresh( $userLinkSpan, 0 );
})
.always(function() {
// Make sure to set the user as finished loading.
Users.finishedLoading( userID );
}); }, 100 );
},
/**
* Load the FF counts for a user's hover card.
* @param {jQuery} $userHoverCard Hover card selector.
*/
loadUserHoverCard : function( $userHoverCard ) {
var t = this;
// As long as the hover tooltip exists but the card inside it doesn't, loop and wait till it's loaded.
if ( $( '.tooltipster-user-profile' ).length && $userHoverCard.length === 0 ) {
setTimeout(function(){
t.loadUserHoverCard( $( $userHoverCard.selector, $userHoverCard.context ) );
}, 500);
return;
}
doLog( 'Start loading Friends and Followers (Hover Card).', 'i' );
FFC.loadUserLink( $userHoverCard, true );
},
/**
* Load Friends and Followers
* @param {boolean} clean Delete saved details and refetch all.
*/
loadAll : function( clean ) {
if ( Page.is( 'has-posts' ) ) {
return;
}
doLog( 'Start loading Friends and Followers.', 'i' );
// Find all users and process them.
var $newUserLinks = $( Observer.queryToLoadFF ).not( '.ffc-processed' );
doLog( 'New user links found: ' + $newUserLinks.length );
// Load all userlinks.
$newUserLinks.each(function() {
var $userElement = $( this );
// Is this link on a tooltip hover card?
var onHoverCard = ( $userElement.closest( '.tooltipster-base' ).length !== 0 );
FFC.loadUserLink( $userElement, onHoverCard );
});
}
};
/**
* Manage all users for the Friends and Followers counting.
*/
var Users = {
userObjects : {},
getUserObject : function( userID, setLoading ) {
if ( Users.userObjects.hasOwnProperty( userID ) ) {
doLog( '(' + userID + ':' + Users.userObjects[ userID ].userName + ') Already loaded.' );
return Users.userObjects[ userID ];
}
if ( setLoading ) {
doLog( '(id:' + userID + ') Set to loading.' );
Users.userObjects[ userID ] = true;
}
doLog( '(id:' + userID + ') Not loaded yet.' );
return false;
},
finishedLoading : function( userID ) {
if ( Users.userObjects.hasOwnProperty( userID ) ) {
Users.userObjects[ userID ].setFinished();
}
}
};
// Initialise after all variables are defined.
Page.init();
Observer.init();
Posting.init();
// Add the required CSS rules.
addCSS();
// Add the About (and Settings) window to the menu.
addAboutWindow();
// As the observer can't detect any changes on static pages, run functions now.
FFC.loadAll();
QM.load();
emphasizeNestedRepliesParents();
tweakMessagesPage();
// Load Notifications Reloaded?
if ( settings.notifReloaded > 0 ) {
notifications.urls.notifications = '/notifications/request/?count=' + settings.notifReloaded;
notifications.get().success(function() {
$.event.trigger( 'notificationsRender' );
});
}
// Add Notifications Reloaded to the notifications page.
if ( Page.is( 'notifications' ) ) {
var $ajaxNRRequest = null;
var $notificationsList = $( '#new_notifications_list' );
// Add the empty filter message
var $emptyFilterDiv = $( '<div>No notifications match the selected filter. Coose a different one.</div>' );
// Select input to filter kinds.
var $kindSelect = $( '<select/>', {
'id' : 'th-nr-nk-select',
title : 'Filter by the kind of notification'
});
// Select input to filter users.
var $userSelect = $( '<select/>', {
'id' : 'th-nr-nu-select',
title : 'Filter by user'
});
// List the available count options.
var selectCount = '';
[ 30, 50, 100, 200, 300, 400, 500 ].forEach(function( val ) {
selectCount += '<option value="' + val + '">' + val + '</option>';
});
/**
* Filter the current items according to the selected filters.
* @param {string} which Either "kind" or "user".
*/
function filterNotifications( which ) {
var $notificationItems = $notificationsList.find( '.notifications_item' ).show();
var kindVal = $kindSelect.val();
var userVal = $userSelect.val();
if ( undefined === which )
updateSelectFilters( $notificationItems );
// Filter kinds.
if ( '' !== $kindSelect.val() ) {
$notificationItems.not( '[data-kind="' + kindVal + '"]' ).hide();
}
// Filter users.
if ( '' !== $userSelect.val() ) {
$notificationItems.not( '[data-user-id="' + userVal + '"]' ).hide();
}
var $notificationItemsVisible = $notificationItems.filter( function() { return $( this ).find( '.new_notification:visible' ).length; } );
if ( $notificationItemsVisible.length ) {
$emptyFilterDiv.hide();
} else {
$emptyFilterDiv.show();
}
// The other select filter, not the current one.
var other = ( 'kind' === which ) ? 'user' : 'kind';
if ( '' === kindVal && '' === userVal ) {
updateSelectFilters( $notificationItems, other );
} else {
updateSelectFilters( $notificationItemsVisible, which );
}
}
/**
* Update the entries and count values of the filter select fields.
* @param {jQuery} $notificationItems List of notification items to take into account.
* @param {string} which The filter that is being set ("kind" or "user").
*/
function updateSelectFilters( $notificationItems, which ) {
// Remember the last selections.
var lastKindSelected = $kindSelect.val();
var lastUserSelected = $userSelect.val();
// The available items to populate the select fields.
var availableKinds = {};
var availableUsers = {};
$notificationItems.each(function() {
var $notificationItem = $( this );
// Remember all the available kinds and the number of occurrences.
if ( availableKinds.hasOwnProperty( $notificationItem.attr( 'data-kind' ) ) ) {
availableKinds[ $notificationItem.attr( 'data-kind' ) ]++;
} else {
availableKinds[ $notificationItem.attr( 'data-kind' ) ] = 1;
}
// Remember all the available users and the number of occurrences.
if ( availableUsers.hasOwnProperty( $notificationItem.attr( 'data-user-id' ) ) ) {
availableUsers[ $notificationItem.attr( 'data-user-id' ) ].count++;
} else {
availableUsers[ $notificationItem.attr( 'data-user-id' ) ] = {
username : $notificationItem.attr( 'data-user-name' ),
count : 1
};
}
});
// Update the kinds if the "User" filter has been changed.
if ( undefined === which || 'user' === which || '' === lastKindSelected ) {
// List the available kinds, adding the number of occurances.
$kindSelect.removeAttr( 'disabled' ).html( '<option value="">All Kinds</option>' );
for ( var key in availableKinds ) {
if ( TSUConst.kindsTexts.hasOwnProperty( key ) && availableKinds.hasOwnProperty( key ) ) {
$kindSelect.append( '<option value="' + key + '"' + selected( key, lastKindSelected ) + '>' + TSUConst.kindsTexts[ key ] + ' (' + availableKinds[ key ] + ')</option>' );
}
}
}
// Update the users if the "Kind" filter has been changed.
if ( undefined === which || 'kind' === which || '' === lastUserSelected ) {
// List the available users, adding the number of occurances.
$userSelect.removeAttr( 'disabled' ).html( '<option value="">All Users - (' + Object.keys( availableUsers ).length + ' Users)</option>' );
// Sort alphabetically.
var availableUsersSorted = [];
for ( var userID in availableUsers ) {
availableUsersSorted.push( [ userID, availableUsers[ userID ].username.toLowerCase() ] );
}
availableUsersSorted.sort( function( a, b ) { return ( a[1] > b[1] ) ? 1 : ( ( b[1] > a[1] ) ? -1 : 0 ); } );
availableUsersSorted.forEach(function( val ) {
var userID = val[0];
if ( availableUsers.hasOwnProperty( userID ) ) {
$userSelect.append( '<option value="' + userID + '"' + selected( userID, lastUserSelected ) + '>' + availableUsers[ userID ].username + ' (' + availableUsers[ userID ].count + ')</option>' );
}
});
}
}
/**
* Refresh the selected number of notifications.
*/
var reloadNotifications = function() {
// If a request is already busy, cancel it and start the new one.
if ( $ajaxNRRequest ) {
$ajaxNRRequest.abort();
}
doLog( 'Loading ' + $countSelect.val() + ' notifications.', 'i' );
// Show loader wheel.
$notificationsList.html( '<img src="/assets/loader.gif" alt="Loading..." />' );
// Disable select inputs.
$kindSelect.attr( 'disabled', 'disabled' );
$userSelect.attr( 'disabled', 'disabled' );
// Request the selected amount of notifications.
$ajaxNRRequest = $.getJSON( '/notifications/request/?count=' + $countSelect.val(), function( data ) {
// Clear the loader wheel.
$notificationsList.empty();
// Make sure we have access to the notifications.
if ( ! data.hasOwnProperty( 'notifications' ) ) {
// Some error occured.
$notificationsList.html( '<div>Error loading notifications, please try again later.</div>' );
return;
}
// No notifications.
if ( 0 === data.notifications.length ) {
$notificationsList.html( '<div>You don\'t have any notifications.</div>');
return;
}
// Append the notifications to the list. Function used is the one used by Tsu.
$( data.notifications ).each(function( i, item ) {
//var $notificationItem = $( window.notifications_fr._templates['new_comment_in_post'](
var $notificationItem = $( window.notifications_fr._templates.new_comment_in_post(
item.url,
item.user,
item.message,
item.created_at_int
))
.attr({
'data-kind' : item.kind,
'data-user-id' : item.user.id,
'data-user-name' : item.user.first_name + ' ' + item.user.last_name
});
$notificationsList.append( $notificationItem );
});
// Add the empty filter message.
$notificationsList.append( $emptyFilterDiv );
// Filter the notifications to make sure that the previously selected filter gets reapplied.
filterNotifications();
})
.fail(function() {
$notificationsList.html( '<div>Error loading notifications, please try again later.</div>' );
});
};
var $countSelect = $( '<select/>', {
'id' : 'th-nc-select',
html : selectCount
})
.change( reloadNotifications );
$( '<div/>', { 'id' : 'th-nr-div' } )
.append( $kindSelect.change( function() { filterNotifications( 'kind' ); } ) )
.append( $userSelect.change( function() { filterNotifications( 'user' ); } ) )
.append( $( '<label/>', { html : 'Show:', title : 'How many notifications to show' } ).append( $countSelect ) )
.append( $( '<i/>', { 'id' : 'th-nr-reload', title : 'Reload notifications' } ).click( reloadNotifications ) ) // Add reload button.
.appendTo( $( '#new_notifications_wrapper' ) );
// Reload all notifications and set data attributes.
reloadNotifications();
}
/**
* Convert timestamp to date and time, simplified date() function from PHP.
* @param {string} format Format of the date and time.
* @param {integer} timestamp UNIX timestamp.
* @return {string} The pretty date and time string.
*/
function phpDate( format, timestamp ) {
var d = new Date( timestamp * 1000 );
var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
var year = d.getFullYear();
var month = d.getMonth();
var day = d.getDate();
var hour = d.getHours();
var mins = d.getMinutes();
var secs = d.getSeconds();
// Check date() of PHP.
var mapObj = {
H : ( '0' + hour ).slice( -2 ),
i : ( '0' + mins ).slice( -2 ),
s : ( '0' + secs ).slice( -2 ),
Y : d.getFullYear(),
F : months[ month ],
m : ( '0' + month ).slice( -2 ),
d : ( '0' + day ).slice( -2 ),
j : day
};
var re = new RegExp( Object.keys( mapObj ).join( '|' ), 'gi' );
return format.replace( re, function( matched ){
return mapObj[ matched ];
});
}
/**
* Add Post Archive to Analytics page to find previous post easier and add ALL the details!
*/
if ( Page.is( 'analytics' ) ) {
/**
* Update the table zebra striping.
* @param {jQuery} $rowsVisible If the visible rows have already been found and passed, use those.
*/
function paZebra( $rowsVisible ) {
// If no visible rows have been passed, load them from the table.
$rowsVisible = $rowsVisible || $paTableBody.find( 'tr:visible' );
$rowsVisible.filter( ':even' ).addClass( 'th-pa-row-even' ).removeClass( 'th-pa-row-odd' );
$rowsVisible.filter( ':odd' ).addClass( 'th-pa-row-odd' ).removeClass( 'th-pa-row-even' );
}
/**
* Filter the posts by the selected types and update the table's zebra striping.
*/
function paFilter() {
$paFilterCheckboxes.each(function() {
var $rows = $paTableBody.find( '.th-pa-pt-' + $( this ).attr( 'data-type' ) );
if ( this.checked ) {
$rows.show();
} else {
$rows.hide();
}
$( this ).siblings( 'span' ).html( '(' + $rows.length + ')' );
});
var $rowsVisible = $paTableBody.find( 'tr:visible' );
if ( $rowsVisible.length > 0 ) {
// Update the zebra striping of the table.
paZebra( $rowsVisible );
$paFilterEmpty.hide();
} else {
$paFilterEmpty.show();
}
}
/**
* Get all the posts before the passed post ID.
* @param {integer} before The last loaded post ID.
*/
function paGetPosts( before ) {
// Disable the filter checkboxes while loading.
$paFilterCheckboxes.attr( 'disabled', 'disabled' );
var url = '/api/v1/posts/list/' + window.current_user.id + '?_=' + Date.now() + ( ( undefined !== before ) ? '&before=' + before : '' );
$.getJSON( url, function( data ) {
if ( ! data.hasOwnProperty( 'data' ) ) {
$paLoaderDiv.html( 'Error occured, please try again later.' );
return false;
}
// We have our list of posts, extract all the info and add the rows to the table.
data.data.forEach(function( post ) {
// Remember the last post ID.
$paLoadMorePosts.attr( 'data-before', post.id );
// Find out what type of post this is.
var postTypeText = 'Personal Post';
var postTypeClass = 'post';
if ( post.is_share ) {
postTypeText = 'Shared Post';
postTypeClass = 'share';
} else if ( post.user_id != window.current_user.id ) {
postTypeText = 'Wall Post by ' + post.user.full_name;
postTypeClass = 'wallpost';
}
// Put together the post links.
var postLink = '/' + post.user.username + '/' + post.id
var originalLink = '';
if ( post.is_share ) {
originalLink = '/' + post.original_user.username + '/' + post.shared_id;
}
var privacyIcon = '';
var selectBox =
'<ul class="privacy_' + post.id + ' black_dropdown_box" >' +
'<li class="' + ( ( 1 === post.privacy ) ? 'checked_privacy_option' : '' ) + '"><img src="/assets/check_mark.png" height="16" width="16" alt="Check mark" style="display: ' + ( ( 1 === post.privacy ) ? 'inline' : 'none' ) + ';"><a href="/posts/change_privacy/' + post.id + '/1/' + ( post.is_share ? 'share' : 'normal' ) + '" data-method="patch" data-remote="true">only friends</a></li>' +
'<li class="' + ( ( 0 === post.privacy ) ? 'checked_privacy_option' : '' ) + '"><img src="/assets/check_mark.png" height="16" width="16" alt="Check mark" style="display: ' + ( ( 0 === post.privacy ) ? 'inline' : 'none' ) + ';"><a href="/posts/change_privacy/' + post.id + '/0/' + ( post.is_share ? 'share' : 'normal' ) + '" data-method="patch" data-remote="true">public</a></li>' +
'</ul>';
// Depending on the post type, the privacy options are handled differently.
if ( 'post' === postTypeClass ) {
privacyIcon =
'<a href="#" id="privacy_icon">' +
( ( 0 === post.privacy ) ? '<span title="Public" class="privacy_icon_public"></span>' : '' ) +
( ( 1 === post.privacy ) ? '<span title="Only Friends" class="privacy_icon_private"></span>' : '' ) +
'</a>' + selectBox;
} else if ( 'share' === postTypeClass ) {
privacyIcon =
'<a href="#" id="privacy_icon">' +
( ( 0 === post.privacy ) ? '<span title="Public" class="privacy_icon_public"></span>' : '' ) +
( ( 1 === post.privacy ) ? '<span><img alt="Only Friends" title="Only Friends" src="/assets/friends_icon.png" width="16" height="16" /></span>' : '' ) +
'</a>' + selectBox;
} else if ( 'wallpost' === postTypeClass ) {
privacyIcon =
'<a href="#" id="privacy_icon" title="Can only be changed in Settings » Privacy » Post on your Diary">' +
( ( 0 === post.privacy ) ? '<span class="privacy_icon_public"></span>' : '' ) +
( ( 1 === post.privacy ) ? '<span class="privacy_icon_private"></span>' : '' ) +
'</a>'; // No select box, as this can't be changed per post. Only in Settings->Privacy for ALL wall posts.
}
// The post entry row for the table.
var $tr = $(
'<tr data-post-id="' + post.id + '" class="th-pa-pt-' + postTypeClass + '">' +
'<td>' +
( ( post.has_picture ) ? '<a class="th-pa-picture" rel="posts-archive-gallery" href="' + post.picture_url + '"><img alt="' + post.picture_url.split( '/' ).pop() + '" src="' + post.picture_url + '" /></a>' : '' ) +
'<div class="th-pa-post">' +
'<ul class="th-pa-meta">' +
'<li title="' + postTypeText + '"><i class="th-icon th-pa-pt"></i></li>' +
'<li class="th-pa-privacy privacy_box">' + privacyIcon + '</li>' +
'<li class="th-pa-date" data-date="' + post.created_at_int + '" title="' + phpDate( 'd. F Y - H:i:s', post.created_at_int ) + '">' + phpDate( 'd. F', post.created_at_int ) + '</li>' +
'<li class="th-pa-expand button th-pa-stealth" title="Expand text">+</li>' +
'<li class="th-pa-post-link th-pa-stealth"><a href="' + postLink + '" target="_blank" title="Open post">Open</a></li>' +
( ( post.is_share ) ? '<li class="th-pa-original-link th-pa-stealth"><a href="' + originalLink + '" target="_blank" title="Open original post">Open original</a></li>' : '' ) +
'</ul>' +
( ( '' !== post.title && null !== post.title ) ? '<div class="th-pa-title th-pa-ellipsis">' + post.title.trim() + '</div>' : '' ) +
( ( '' !== post.content && null !== post.content ) ? '<div class="th-pa-content th-pa-ellipsis">' + post.content.trim() + '</div>' : '' ) +
'</div>' +
'</td>' +
'<td>' + post.view_count + '</td>' +
'<td>' + post.like_count + '</td>' +
'<td>' + post.comment_count + '</td>' +
'<td>' + post.share_count + '</td>' +
'</tr>'
)
.appendTo( $paTableBody );
// Make the picture clickable to expand into a Fancybox.
$tr.find( '.th-pa-picture' ).fancybox( { padding : 0 } );
$tr.find( '.th-pa-expand' ).click(function() {
// Are we expanding or extracting.
if ( '+' === $( this ).text() ) {
$tr.find( '.th-pa-title, .th-pa-content' ).removeClass( 'th-pa-ellipsis' );
$( this ).text( '-' ).tooltipster( 'update', 'Collapse text' );
} else {
$tr.find( '.th-pa-title, .th-pa-content' ).addClass( 'th-pa-ellipsis' );
$( this ).text( '+' ).tooltipster( 'update', 'Expand text' );
}
});
});
// Initialise or update the tablesorter.
if ( undefined === before ) {
$paTable
.tablesorter({
// First column by date, others by text.
textExtraction : function( t ) {
return ( 1 === $( t ).find( '.th-pa-date' ).length ) ? $( t ).find( '.th-pa-date' ).attr( 'data-date' ) : $( t ).text()
}
})
.bind( 'sortEnd', function() {
paZebra();
});
} else {
$paTable.trigger( 'update' );
}
// Update zebra striping.
paFilter();
// Are there more posts?
if ( data.data.length < 10 ) {
$paLoaderDiv.html( 'No more posts to load.' );
} else {
$paLoaderWheel.hide();
$paLoadMorePosts.show();
}
})
.always(function() {
// Enable the filter checkboxes.
$paFilterCheckboxes.removeAttr( 'disabled' );
});
}
// Posts Archive table.
var $paTable = $( '<table/>', {
'id' : 'th-pa-table'
});
// Table header.
var $paTableHeader = $( '<thead/>', {
html :
'<tr>' +
'<th title="Sort by Date">Posts Archive (by Tsu Helper)</th>' +
'<th title="Sort by Views"><span class="icon view_icon"></span></th>' +
'<th title="Sort by Likes"><span class="icon like_icon"></span></th>' +
'<th title="Sort by Comments"><span class="icon comment_icon"></span></th>' +
'<th title="Sort by Shares"><span class="icon share_icon"></span></th>' +
'</tr>'
})
.appendTo( $paTable );
// Add a filter to choose which type of posts to display.
var $paFilter = $( '<div/>', {
'id' : 'th-pa-filter',
html :
'Filter Posts: ' +
'<ul>' +
'<li><label><input id="th-pa-cb-post" type="checkbox" checked="checked" data-type="post" />Personal Posts <span></span></label></li>' +
'<li><label><input id="th-pa-cb-share" type="checkbox" checked="checked" data-type="share" />Shared Posts <span></span></label></li>' +
'<li><label><input id="th-pa-cb-wallpost" type="checkbox" checked="checked" data-type="wallpost" />Wall Posts by other Users <span></span></label></li>' +
'</ul>'
});
// Call the filter when a checkbox value gets changed.
var $paFilterCheckboxes = $paFilter.find( 'input[type=checkbox]' ).attr( 'disabled', 'disabled' ).change( function() { paFilter(); } );
// Message to display if no posts match the chosen filter.
var $paFilterEmpty = $( '<div/>', {
'id' : 'th-pa-filter-empty',
html : 'No posts match the selected filter.'
})
.hide();
// Table body.
var $paTableBody = $( '<tbody/>' )
.appendTo( $paTable );
// The row of the table to display the loading wheel and the "Load More Posts" button.
var $paLoaderDiv = $( '<div/>', { 'id' : 'th-pa-loader-div' } );
// Show only the loader wheel to start with.
var $paLoaderWheel = $( '<span><img src="/assets/loader.gif" alt="Loading..." />Loading more posts...</span>' )
.appendTo( $paLoaderDiv );
// Button to "Load More Posts".
var $paLoadMorePosts = $( '<span class="button" style="float:none;">Load More Posts</span>' )
.hide()
.click(function() {
$paLoaderWheel.show();
$paLoadMorePosts.hide();
paGetPosts( $paLoadMorePosts.attr( 'data-before' ) );
})
.appendTo( $paLoaderDiv );
// Table wrapper.
$( '<div/>', {
'id' : 'th-pa-wrapper',
html : $paTable
})
.prepend( $paFilter )
.append( $paFilterEmpty )
.append( $paLoaderDiv )
.insertBefore( $( '.dashboard_post_statistic' ) );
// Get the first lot of posts.
paGetPosts();
}
/**
* Add a specific class to all nested reply parent elements to emphasize them.
*/
function emphasizeNestedRepliesParents() {
// Make sure the setting is enabled and we're on the right page.
if ( ! settings.emphasizeNRP || ! Page.is( 'has-posts' ) ) {
return;
}
doLog( 'Emphasizing Nested Replies Parents.', 'i' );
$( '.post_comment .load_more_post_comment_replies' ).not( '.th-nrp' ).each(function(){
if ( /\d+/.exec( $( this ).text() ) > 0 ) {
$( this ).addClass( 'th-nrp' );
}
});
}
/**
* Autofocus text input and add line breaks to messages.
*/
function tweakMessagesPage() {
// Make sure we're on the right page.
if ( ! Page.is( 'messages' ) ) {
return;
}
doLog( 'Tweaking messages page.', 'i' );
// Focus the recipient field if this is a new message.
if ( document.URL.endsWith( '/new' ) ) {
$( '.new_message #message_to_textarea' ).focus();
} else {
$( '.new_message #message_body' ).focus();
}
// Add line breaks to all messages.
$( '.messages_content .message_box' ).not( '.tsu-helper-tweaked' ).each(function(){
var $text = $( this ).find( '.message-text' );
$text.html( $text.html().trim().replace( /(?:\r\n|\r|\n)/g, '<br />' ) );
$( this ).addClass( 'tsu-helper-tweaked' );
});
}
/**
* Make a log entry if debug mode is active.
* @param {string} logMessage Message to write to the log console.
* @param {string} level Level to log ([l]og,[i]nfo,[w]arning,[e]rror).
* @param {boolean} alsoAlert Also echo the message in an alert box.
*/
function doLog( logMessage, level, alsoAlert ) {
if ( ! publicDebug && 104738 !== window.current_user.id ) {
return;
}
var logLevels = { l : 0, i : 1, w : 2, e : 3 };
// Default to "log" if nothing is provided.
level = level || 'l';
if ( 'disabled' !== settings.debugLevel && logLevels[ settings.debugLevel ] <= logLevels[ level ] ) {
switch( level ) {
case 'l' : console.log( logMessage ); break;
case 'i' : console.info( logMessage ); break;
case 'w' : console.warn( logMessage ); break;
case 'e' : console.error( logMessage ); break;
}
if ( alsoAlert ) {
alert( logMessage );
}
}
}
/**
* Add the required CSS rules.
*/
function addCSS() {
doLog( 'Added CSS.', 'i' );
// Remember to take care of setting-specific CSS!
var settingSpecificCSS = '';
// Hide Ads.
if ( settings.hideAds ) {
settingSpecificCSS +=
'.homepage_advertisement, .rectangle_advertisement, .skyscraper_advertisement { position: absolute !important; left: -999999999px !important; }';
}
// Nested replies parents.
if ( settings.emphasizeNRP ) {
settingSpecificCSS +=
'.th-nrp { text-decoration: underline; color: #777 !important; }';
}
// Quick Mention links for comments.
if ( settings.quickMention ) {
settingSpecificCSS +=
'.th-qm-comment, .th-qm-reply { z-index: 1; font-weight: bold; font-size: 0.8em; display: block; position: absolute; background: #1abc9c; color: #fff; border-radius: 3px; padding: 2px; }' +
'.th-qm-comment { margin-left: 11px; }' +
'.th-qm-active-input { border-color: rgba(0,0,0,.4) !important; }' +
'.post_comment { position: relative; }';
}
// Add the styles to the head.
$( '<style>' ).html(
settingSpecificCSS +
// Menu item.
'#th-menuitem-about a:before { display: none !important; }' +
'#th-menuitem-about a { background-color: #1ea588; color: #fff !important; width: 100% !important; padding: 8px !important; box-sizing: border-box; text-align: center; }' +
'#th-menuitem-about a:hover { background-color: #1ea588 !important; }' +
// FFC.
'.th-ffc-span .th-ffc-loader-wheel { margin-left: 5px; height: 12px; }' +
'.th-ffc-span a { font-size: smaller; margin-left: 5px; border-radius: 3px; background-color: #1abc9c; color: #fff !important; padding: 1px 3px; font-weight: bold; }' +
// About & Settings windows.
'#th-aw, #th-sw { width: 400px; height: auto; }' +
'#th-aw *, #th-sw * { box-sizing: border-box; }' +
'#th-aw h1, #th-sw h1 { margin: 5px; }' +
'.th-buttons-div { padding: 5px; text-align: center; display: inline-block; border: 1px solid #d7d8d9; width: 100%; line-height: 23px; background-color: #fff; border-radius: 2px; margin-top: 10px; }' +
// About window.
'.th-update { background-color: #f1b054 !important; color: #fff !important; }' +
'#th-aw > div { display: block; margin: 5px 0; }' +
'#th-aw .card { padding: 5px; min-width: 100%; border: 1px solid #d7d8d9; border-top-left-radius: 30px; border-bottom-left-radius: 30px; }' +
'#th-aw .card .button { width: 123px; }' +
'#th-aw-update-button { margin: 5px; }' +
'#th-aw-settings-button { float: right; height: 32px; width: 32px; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAJBklEQVRYR5VXe2xbZxU/175+v+JnHMexY+fJwqpKU5HYgCEeg05oG4KBVvFHK7G0K9vatUmbPra6SdemlddHnFJUsTGJvwBpMBVNCMEQBSRUZUBXytq84zjxM7Hjt69fnO8+4tskm9QrXd/X953zO+f7nd/5TMFDHj6fj5abTSVXu0vSmEpBajUJ2Ux6/9DLr157GJPUwwwmY0/4/W67xTzz/K4XpOK5kVAIPvzzh78+9GL/Dx/G5qcCOBsIPCOjpS/korE9GHVRMDp88cKXm5sdf+np6pRCHUACFNSoOmQyGbg/M3tvoH/v58QAfG+99bhGoxzKReLfRzvMRnBbAhgdv/KsQqn+ravdTc1OTyfW1jJfOXP06Cc+v99C05KzTmfbj93ONpyLCBAAIIBqpQa3/vURU2bKP1q6f/93169fL48GAkNyhfxMq8spDS+Fp1OLob6NIDYBwMifU6lV7337m9+givkCFGs1+Mffblbq9epHFCV9rKunl9YqleizzrknFngcIKVhORKuLy8tlaBWnzSaTI9+8YnHqTLamVsMQnAhuAnEAwDOXQt8TylX/eZb6DydTEEul8McS8BstUCuUIRWhx1WojFI4jdhIvHNHRRI8CUZq1BrIJ5IgF6L10iU/dqk10MwGiYgZmb+fbsvEAiUuFn8QdIrU8hj33n6aSqfSUM2lxeFRnBIoFar8lOEkMVDhHd4rSMYKXIDsyeuFL1WC3enpmBlJTE2uPelAw8AIA/j7777Dkaxu8PrpVhoaIjLL+e3Xq1BJptFwmWBwoESzL+ElkKFKUOlWgWVUgVarRrUKjU7ZeMS5fJ5mJyczKXSWfe548dXNgEgbs4Fxv7abLN9yWFvoYh/IUWpVAqWlpfBbreD2WQSRdbIAgERiUQgjRXR6fWAXK5Yp0cVs3H3f3fL+Xzxa76Bgb8LBjaR8Mj58zqtXLa6fdvnaVoqYxGsrq5CIrEKXm870Ei0xsLxZoRMsdYoyCGAhaUQdHo4EISosXiCBPCH468e2ClGvwnAG37/TqvFfMPjdqPQ1KCQL0IQRaarswN5wGsPVgDrCi1zK1/DKxFGsuaoDPg9m89BaGkZurs6cZwE6sifO5/cK8aZWdOlQ5cKn5oB//Wf3e/q7OqW1DkCTc3MgdPRAiqVkiMCuhLohjUPJYZhvxEucAvPxUSgRbBipFIpWCxmdmY4Fq+lM+mDA/37AusARi5fPCyXy/ZWa/Xmeq2u0mg0dLvLRZEoCoUCxBIr4Ha1oWF0jMSjanVgKmUIBherhXI5jgsyX6lU+oxGo9bhcGCsPA68QXswPTsL3R2drFgV8iWYDwWxmKoVrKoMYlygLr/z9qS3w9tls9owxRSsYY1jU2HjjEbjyGgV6PU6jmk8K+9PTVXypWL/8KHBXxB3/f39Mtcjj/zKZDJ+197czFGELBOCWAwtQ5uzlUsOnmaLlc2YVCaHj2//B6jRq2MTDrvjMY2aKx1WTYh7XIFFXPsWNCiTIRn5I4+lhJz474kDBx9df4k3u30+pdegT/X19irE7xNIYIvJzLsn9vHk5SG0vFyjzo5dueWw23doNNrGIL78w9Eo2G02tuaFok6mkhCLRn9+8rXDL4odkfszly/FOjweKy3DheFFANcc9Do9lxFyiCSUBfAmAsAoEQDJAK8/fMIyqIZazAxhu2CgUCzC/OLipO/Q4R4xgP0+n9ai1yX6urv5DHAOc2hDg9K87px9jT9oMxRGAMOXL92yWSw79Dod60ikfUDEg0iwUGzkmwRZPTM3V83nc0MjR4b8xFy/z6d26HS/t1nMXzUam1grQo8olkqgVHCYxLaJTJPGRfn8F4aqQO2HatWKziRqtYrGdishVSBTKtj1z2dzmIBG28ExMDU9XctkcxmpRBJBY25HS4sCK4GqVCuE8BxfyRQMoM72BArKZQYw7RWGYWpor4QluvCAEL3i8+n1SsU9T3t7i0IuRz5KwNnuhlAwCDXsA1ibGBk3pcmgB4+zDfcBVUggL+IrK1DG8lwPHcepNEpUQiWkk0n2NRKyHo0nxlbn5gbJfkFMifXlHBwZOWA1Gy82N9tJJUOT0QRqjQaWFxc5LgjHhobIGmMj5z4QAWpzI/iFICa3wmvCTDFXznRfPPnmomBmkxS/cuyYFVO83NPTQ5NuR4629nZgcC2j4TD7TLJAssE/NNLN0QukKNkuTzsk4jHIpjPsMNKgIrHoxNmh4zsaUWxuK3Bi9Nx7pqam53A309ATvHN7vWxU83OzmPYKRyk2WBI2uXLPaqwmNzahcHgJd8opHmMdarhU88EgU2DyX7j4xsjtLTNwdOT0Sxab7WpnRwe1ihLcoB033Iqa4Ma0prMZNrp8rsCCkSNfNFhFVouFbUvzKL8F3IZtPHRNBrjz8Z3MQize8ku/H7dbogwc9vkstFQSfeLJJyVki82Wkjg6NlhMPhLTaDYCAmU3IKRKmBIDa+k0ttwoFB6oGGGpuOwQezkElkyuXhkeOHLwAQDk4cJPx0+nUmvHnW1OxMJ3N0wtqVlsIEDTqHDiWRtTJKp0UhGshAtjEAPZ1OC5oGAq23F3zK7PJhIOjpz+iYyWXW51OGgiOiUkXzgSKaMoMTrc4DcZDFI5CotYVAQjWNyofFlYS60VCwxTxz2g0mq1YvFgk0Pnq8nkXCEW344b0vSWHBBeHh4+tU8hlQV0BgONu6F0qcTsDE9OTrT29u6Sy+gRg9HoRKJywfHlSIRqFhUSteNGuVz2L92790/Ptm3XMJV7NFotnVpbmytucL5lBgQQR4aH9+HOdtdaJvfstdHRpPD+tVMnnzLoDB9YrbbGXzNMAaobRMKRufOvv+4Vk2/g1KkfULRksFhNfD3ga0T+mRnYRF/Ri5ePHes16HW3WxwOuRA+ib5QyOPeMXlj9MTJZz5r/sZvGzlAoiIdm1wJ44STbAjISXs8Ht3ze3ZPtLlctEAwwu4VLNvQ/MKFt8fHySaFCAWRWvFVuCd/LkhzIFd+A8c5JWAEx+QqnBgpkH04aWkEhHz0auDmVlHe/OOfDn3w/vsTvHHyz4c4IWzP8vfkmZwEDAvg/5gaEzsDHsw6AAAAAElFTkSuQmCC"); }' +
'.th-aw-donate-buttons { margin: inherit; border-top-left-radius: 20px; border-bottom-left-radius: 20px; }' +
'.th-aw-donate-paypal { float: left; }' +
'.th-aw-donate-paypal img { vertical-align: middle; }' +
'.th-aw-get-in-touch li { display: inline-block; margin-right: 10px; }' +
'.th-aw-info li { margin: 2px 0; }' +
// 16px icons.
'.th-icon { display: inline-block; width: 16px; height: 16px; vertical-align: text-bottom; }' +
'.th-icon-bug { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADXklEQVQ4T02TYUwTZxjHn3vp3bXXu94hWNIW1EApU6ajshTciHFhumGMLlmyZNvnZXYmW/aFZPswY7JkGOMWE7fol31a4ofFZOyDzkkii5kEBKyEEsEKG2thBUvbu9L2rtf39lyRhUt+uefu3t//fe+e9xjYcQwEg7y1T/nCMulPd0anE9uP+vtfCbEM+wFdMy79PjOzudNhdlzYNTna+3KIE9jr1Kx2UAu8hJDndQTmTcP4+N6fc09wDEWsba8W0NW1T4nF/jKwdEa6O74WBGdUcLqAkDqoVk0olktQLBUvP5xKfINjyj09wbrx8YRqu3YA0/9G10W9bO71mLnVAwp8NlVsBL/fC7JHgkw2D+paGiJiRp9YZ65qnOJz8Y7S3dHHH9krsQNYWZabj7RKnw+9xkaVYITcHPmDbChtQKoOMEkFAloCTp88RTdm79HBscqP8XT5cir1fAndih0gIIHrx91fDQycfM97+ASXXYrTzMIk+Xd2ERqDXvCH36RSSztZnx4xbt2+9fPZkc0L6KSQoh3gQVp/OyPdePXEOyFOFAkR6oEofliciEFrTxhoNgm0mANDK9DJu78svD2svY/OIqIyx/oODhVK+utDe9OR8JE+ztLSKPuA8CIsJ4uwp0UAWi4AVdeBiI0QG7tvDC41TYhu5+zo/ZmovYJdSPvoGfl2Z3dXvZmKA3F5gEgNsFrwgE9UcfY80M0sOPz7IT71OHtsOD+AzlNkww6QkbZf35JvHu7u2MOsLRCg1VqbUzkXBJTSVsuxpZY3RKen5pdP38m/i3eeIXk7wC0IbOh82HPjVDjQLpdTxKqUa05ac0GTtBXAsE7I8QE6HFtd+vaJ/mEmo87i7U07gOvr7fxud0U9dLYFIvt9FmcVMjUpU3RCg7AVZrl3w1yyYnyfcjzQnJ7lxN/x6MrKVhdIm39X4NnKhvxDr3It5BN7mnjNwYMOmsGDyOlgAA9pXTLnVwpj58Zz0ebmhlwymVlFl9Z2IsIiyiHZcfCTTulKk4d7iScmcQDDUIZapSpL1/KVhWtz6qeP8uYMjs0h9tavydtnHgsZk/wXuuUvfS7HUWcdU1+1LH29VJ08H8sNqhX454Ws22+1M2C75rBwI/bmkuzvg5iIhuSRwouZ//8b/wNDgFsg+yoSRgAAAABJRU5ErkJggg=="); }' +
'.th-icon-idea { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACv0lEQVQ4T5WSXUiTURzGn/O++zB1HxTOqav8apZfjBgFflyotewmKBQhL7qxxFAIKiiSopI+vEkwpGTQTXpT0mWUmFRqorlEK/Nzqb3TbeKcujm3ve/bUVJc2kUHDgfO8z+////hPAQ7LNv9Q6fBkgtUOvJH7gEvNsZeH3r1dznZejFdo48jEvaGMru8ItxQDEYeCdG/DH6Rw8pIOzw9zU95/8rdvdUj3Ma7EMBkbcaTqKK68rD4LKozEIVVCnBB9DpBFjgE/AIcr++Z91/uPb8NMH7nwFmlsbhJfawSLKsCYcIhBJfABxwQvBzI/DgYhxVurwTu7uelSTdHm9cgmxMM30r8GHvuYY5cawDDRlJAGJ1gBbzfDt47CXH+B1j7JALxx2F7dqUj5fZEbgjgW3XCkq6sMlKmTAdhlRQgX7cgBJzgPT8hzn0HO2tFMO0Efpnrl9NqrIoQwMC1fQsxpSaVRK6AJEIPQtYAPgircxCXp6kFK90cVvU5mGl64858MKUOAViuxrVpC435TMQEpLIkCqAWRB7wuUCW7CBuJwJ7whEU0zH7tq/1cC1nCgF0X9IWRcRoXmiyA2A8LrDBMICngKAPxO+HIGMQ1Gjh6JTCO+s8c/TRzHomQr6xqyq6fndGdKU6eQbSOc+mKEgJgqpwLIzFYH7Q/jir3l61Yw7aL0ZFSlhSG21QV6h0Ntqd5mCthVQGj1MHW5erOSiI5XkNzuUdAWuX78tUCRKVYkKTuYvmYHG9jmEj4Pzqx6rXk5vX4OrYmt4QCxtCXV2dmKhPRZbRCOvoELr6+jE48AVms3lb/T8BBfkF0GgUmJ1zo/NDByyW/wTkZOfiYKoew6Nj+NzxCb39lp0naG1tVfE8n0IIMdCdQm0k63S6Uy0vW9DW9g4+nxcnCwsXi0tKlBzHNVJ9UBTFKXpOmUym/t+tniogNDUKLAAAAABJRU5ErkJggg=="); }' +
'.th-icon-heart { margin-right: 5px; background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrUlEQVQ4T31T3UsUURQ/987dnR33Y9x1LT92TQSL1upFKQxLrIU1QpKioggKFKG3/o1eo6dQiHrrQUppQVAqoowFMwtiSXzR3dZsPxx3nZ11Zu7tztj6QdKBC+fj/n73d87hIuD2dVjuFIANc/e6FTOAWcrY2Kmx4ku7PuQdxAgNcbcLE6ebmltJxNBox6jyBM3f87U7JNfnw903RU/zMQcwBlph1cx/m65sppOPeSh4w5H7/hMXRFGux1gQkK6WaPrts3Ill7qFvtyVHx46M/BAdnucZn4FgJqAPUFA9W2wOj9dZoxCU2dMomtLQEtZXjcA+xqgIspG6s3zOJq740u29V47CqkFZKz/4hcoILEGhEAYWF0LtVqA3DKmnJxVVLtBweMH0toJix9eF1Hiti/b3nWuzlxeAF21LmwbctZwJQHbp6U8sK3dGhFFIKEOWFxI5NCnG77Z1uMdp9HvJVzZrOwQAOIuJtsxl21Ntmqi2wWG/4i58iM5g94PyiPBUPOjAFJENa+ASffc3MXseIKAoSZQC1ndzWf9cxi9ABAar/gLjaE6SdRyRN0oW2M40DAGcPskKItBPZNae3p+QhmxhML05do+gvGrpga3JG4ppLiu8W3uV4IQAq+fgx2yvpopaqZuRi9ObSRsApskKkeRQxhvapQkl64QJbdLgjACOeCCMvHrmUxJY7oRi84UZ+1h79Ua7/P0Cg7nZMgiMQpkPavZ5dqgBQ7o6fSmRpkRu/QX/A+BlZjgJAQck+FmSZLMgr0GlYNTKVUzqREbeLf9ctX2KagmJ3u8PQiTeLjF47JyK8tctsHBH/eDD1RQJRk/6+0mmExZscFfvnoAeIcgHo9HBEGI8EnbhxdO8hMB/g9sQ3x/AN/5ZhK8PmeaZqK/vz/xXwV7+/yf/wc6MhxIaZWcBAAAAABJRU5ErkJggg=="); }' +
'.th-icon-heartp { margin-right: 5px; background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC0klEQVQ4T5VTXUiTURh+zr5vP7rNDV2KTl1Oc6YY9IOQqIVZLAjBKAUvBOmmQBCli+gmKKgMsqTAq7ow8mJEhMHM0goTMkkwIRn+DqeRuuXPdH/fX+f7NMG0iw4czvue532e94dzCP5ztboQ11yN8B8akY3W/oQialyRCKlVAAkD1G9vKlt9IbsP+k0XJIoTkCKGURt4gRsDxPbmsrXHpOW90aHV6D15GRWwmLIVdjC0CO/CIAJrs20EEpOUkNVgSymCIc4CFWER5YIYnemicUvnyb2+pIeOzJONZkMqwpyf0kVoGCPitYnwzH6AJEk4mHkKoVgAMSFIfRFa1gyBFzAy3fWO3O1NnjvmqLKux+ZpEBWQBDAqLeLV+6BVG5WOIjRjmFuCIEblAqHTmGGOy8bw+CuQ2z1p0SOOM5qV8DRCkTWlBYBA5DV4+TSgCNRctmyStzCdVg+zzo6Rib4YueVOHy2wHy8M8z+xHqIlQlBIXAzofh5S7Mp6/RZZlmZg1BuhJhZ4vF8HyI3XtgarZf8jk0mLwIofdMJ407lJ/HudrY0Hy6phMSfDv7yOBf9cPbnoAlOgzeHtGbl03kEEfgXR6wruKVBRY4QlMQFiLB7TvolnN6um6pR3cN114DTLMm/tmTZIqiAW/RuIRngMdkcUoZLKODAMg5RkA0TOgBmfFzwvFN+pnvysCMjrmsvhZAjbbbdlgLCr8M1u4EvPhoIVn9MjLVUPiUvA1KwPgsSXtlSPD8jYtoDsXO0sKFermL6cLCttZxlzc5sVpKfrAN6MSe88JFEsban9rpB3CSgiHYfKVayqL9dupZWsK0Eip8fEzA/6eKTS+3Xftsl7CsiXTR2HTzCEfMzLsSoCnkmaWRJ2kf8pIAONT46WMGrySbYFTiptuzS8I/OOFtxudz6dcj4hRNkULKQ7X/4Xm1lU8jFG/8UQxYcFQRhyOp1D8uVvoY8fXsjKUkIAAAAASUVORK5CYII="); }' +
'.th-icon-help { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACtklEQVQ4T6WTTUhUURTH//dd33uOM8/GQafRZhaSNTmmLWqRIEI7i5blIgqiyBoNkgSjohiK6DuMaoYEKQg3btqYLV0UUUILMy2pheH356i9+XjvzZvbnTcqfiw7cLlwDud3/ufccwn+08jm/FDXoJQvyA0gOAaGagJCGWEfwcjb5XQyEqqv0NfnbAA86vrdQKnwsqBQgV2xQZRlEEKgJ5KIqwkszP1F2mSNLfU7I6uQNcCDrl9BWRbDxT439JQA3WDg2VwEvxiDJBJIOWlMjc4gqRmNrfW7LIgFCIUGpbyArPlKi7EcI0hz3353FB6HaRVaSAgYnFegGiKUPIbxkUnEZjQl1FShWoC7ncMXXW7nM4gKNMNEymQ47NfwZVTCPAceDRjQdR29Iw7IIgVJqZidWWy9fmL3Qwtwu3P4Q7HXU6PySinTBFe8wfZ5uSKfiVefKXKoAMVOMPFnYuDGSX9VtoU3PzWP1yepMd2qvtmO7GUQBAHd3xgoJXDkiZgeH9NDp/bIFuDm6x9aUYmXA7QN1SXKUFumw+20obs/jeWkNVcOkDE7OabfOl2eBVzrGPpUuN1THU/y6iw7uIwdLDVQ4pLwrp9BS604iQC7jWJuemrgzpnybAtX2ocubXPltzFBQUpPrAFq/YRLpugdWs0GqGgDZSoWo4tX75+ruGcBjvNnLN1BtIJCDwy+A6Ye3zKHjEPgybLIEJ2fRtSMOdvPH1haW6TLkYGgKIphh7OIz4FyJUnUlBlQcoH330VeORdUMKEuzcEw9ObHFyqfZqAbVrn5+UCQUCFsdygQJTuP0qwSPhdDjyEeU5FOp1raGquerErc8pkaX/Q6KHMF+R84ywn+FcIwY6zDJAuRcNMhdX1/FqCnpyfAhxXgSdbhrkp+Mvd6G+KQPh7/appmX11dXV8m+A+i8hIg7mH0KgAAAABJRU5ErkJggg=="); }' +
'.th-icon-manual { margin-right: 10px; float: left; display: inline-block; width: 32px; height: 32px; vertical-align: text-bottom; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAE/klEQVRYR8WVW0wcVRjH/3vjtjt7mdlFSmkLLtJYWKTF2rpgwXJRY6oxaLVYbR80KYmJocYaTRNjo1Fqi4t9wAcSo9b65qu+GDVqSKppK1qUgLSlQOkChYVlgb36nZm9zHLbYW3sSXYzmXPmfL/z//7fd1S4w0PF4peWluYZzcZXBYvwIC8IhmP1lsrbwcWXVCF/1zNijNWGOOlwOHo4jnPwvGWM54W+prs9NbcDwG4vQtkLrtQA5eXlEYNB72lrO2myWq0wGAziLzs7G5mZmWmzXD7bqhxAb9APdLg6ikmJOwPATtzcfABVVdWIqfC/KsAAeIGHhbeG3qg3a9LWfcmH6/AAB0GwwGTNxZlDpUAk8t8YVCoMDw3B8WKHMhNyHClg4UUA18ESREKhOMAyFPHF2oBqjQajozdQfuhjZQBiCngBZpsN7c/aEQoGVwwixV4jOM2xWa1WhzG3G/cdPqMQgNwv8FIKTu8vigIkshBZISh7FwqFxUXhpPkINNoM3CSAnS93KgPgDBwsZEKzYMOp/YUIBgJJHpDOTP/Rw7N4/kAQs75FzC0sIhAMkzAJZTRaLSYmp9B0/JwygFgVmEmBD5u2ICACyKSOP0oPwXAEUzM+3LDWIrPImRQ8Ri6mQpc1m+xmVVfdvaajsXcindQJmQckE55s2oSA3y/7LnbsBFKQpB8Z98C/5wQcdtuSFKxcQN1/TkCrUQdzcnRdjaXmFrYqAUBVIJAJTZSCD54qIIBFSXSK/culgWWGDBDA+PQcHEc+Q0lRLvyh1GXb238LOVlaqNTwUcZe2VchfBoH4KKNyCjk4v0n8+FfXJA5UHqU/C2NQDCEoTEPwvXvoXCzDQu0Y6oxeGVaXJKVoYn4g+GwPkcn1ShLAbsDxD5AZfjuvjz4FyQAFrK7Z3DZ3kEK6CYFyo58jmJSYD0Asc1y9LrZ5BSIALk48XguFufn40ET5pYpECAF3B5E6ttgVwgg5pwiqukvW6tC//UZGQCVIbsLTFYb3nmMJPX5kk8dbTDne6+J75kJx6d8pMAXuMd+l6jAd2c7U2VBnK872IIsrRr/XPckAFgfYFVgJIC3H+ExLweQ1XdMA1b3w+5pRBpPoaQ4T1EK5HQMYODatFwB6TZkfeB4gwU+rzexfiUASsHwxAzQeBpbSzaIAN8rVODhqAIDV1YBeKvOhLkYwJIWHFeAAEYIINLYjm1b89NSoG9wSp4CA7Vi6gOUgjdrDfAygFUuHQbBynBknJpc40co27YR86TAD18q80Dt8y1kQjX+6r+V7AELecBMAMdq9PDO0uZrAYidkKXABUdZgQiwnsEAevsmk6tAIA8YqRO+vicb3hnanPWBVSBYJxydmEW4oQMV5ZswHwjjx3PKFKhpJgV0avT8PSFTgDUidh1TJ3ytOhOzHglgtREDCBHA9orNIsB6BgP4vXc82QOsCoxUBUedDMCjAMCLUIMLO7ZvEQF+UqjAQ1EFLl12xwEuUh+oiPWBVmdGSgDxNpwkgDoXKisL01Lgwh834wBPGDjua8HCa4x0F7Tu1mEmhQIxgOBeF3beXwQfKfDzV8o8UH2gBTmUgt96xiQANpxO515S4DCXt+Hp5wrc2anyyQDGqBVrHu3E7gckgPUMBvDrRRmA7ONcejbQT69kw0++vdq9a8dGfToA5y+MJhRQEmylNZ3fXG0nGV9K53tqaF3/Aot1QCKE5DaSAAAAAElFTkSuQmCC"); }' +
// Heartbeat.
'.th-about-love:hover .th-icon-heart { -webkit-animation: heartbeat 1s linear infinite; -moz-animation: heartbeat 1s linear infinite; animation: heartbeat 1s linear infinite; }' +
'@keyframes "heartbeat" { 0% { -webkit-transform: scale(1); -moz-transform: scale(1); -o-transform: scale(1); transform: scale(1); } 80% { -webkit-transform: scale(0.9); -moz-transform: scale(0.9); -o-transform: scale(0.9); transform: scale(0.9); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); -o-transform: scale(1); transform: scale(1); } }' +
'@-webkit-keyframes "heartbeat" { 0% { -webkit-transform: scale(1); transform: scale(1); } 80% { -webkit-transform: scale(0.9); transform: scale(0.9); } 100% { -webkit-transform: scale(1); transform: scale(1); } }' +
'@-moz-keyframes "heartbeat" { 0% { -moz-transform: scale(1); transform: scale(1); } 80% { -moz-transform: scale(0.9); transform: scale(0.9); } 100% { -moz-transform: scale(1); transform: scale(1); } }' +
'@-o-keyframes "heartbeat" { 0% { -o-transform: scale(1); transform: scale(1); } 80% { -o-transform: scale(0.9); transform: scale(0.9); } 100% { -o-transform: scale(1); transform: scale(1); } }' +
// Settings window.
'#th-sw label, #th-sw input, #th-sw select { display: inline-block; cursor: pointer; }' +
'#th-sw form > div { margin: 5px 0; }' +
'#th-sw-back-button { float: left !important; }' +
'.th-sw-help { margin-left: 4px; cursor: help; }' +
// Show custom number of notifications.
'#new_notifications_wrapper { position: relative; }' +
'#th-nr-div { position: absolute; top: 0; right: 15px; }' +
'#th-nr-div select { cursor: pointer; margin: 0 5px; }' +
'#th-nr-nk-select, #th-nr-nu-select { width: 80px; }' +
'#th-nr-reload { display: inline-block; height: 16px; width: 16px; vertical-align: text-bottom; cursor: pointer; margin: 0 5px; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC2klEQVQ4T32SS08TURiG37l0ept2aGnxAqIoprXVyNqNW1YaF3YhwcR4SVRYiFFJRDHES8UYjBeMMUYj0RhIZCXxFxhDwBAUsKlKJAJFaellpu1MZ+Y4rbEBL5zdOfnOc773Ow+FVVa4/7PA0aQ7rcunL4WC4r9KqdUAV/ujXgfHflcU9ZMKvelcyDf8Z/1fgOv9H30U2HYKZI/JxLq9HgfsVhbRL4uaDj08grHOgVBI+w1aAbj+ItICir7l2+JhPG4ekgLE0xoYGqiwU4gvJjEzlxouKPrBC83+SBFSBlx9Fj2+scbZ6650QNEZSHkdOYUgJ5PSY7yVRoWNILWUwdx8avDd4Nj+gYGQVgJ09n2oNzGmiYC/mhNlBpmsiqwoQpVlsBY7GLMVRM1Dl7OQ87nOjgO+rhURuvoid2pr3C0aa0daVCDGFzRdJ2GKkFeM2fKG4SxQsqmETtB0qdn/evkgf3XwNBLdULuuPpOnkBMzyGfTHV0H/VfOPRgRbDYhSXRtVFWw//JR//Q/f+HikynZW13DFTNL8RiyWmHdjUPBWBFg5vibmaVEa0/brtx/Peh4PCm71lRzqg5IiQXkMgbgZDC2miMrZtD+aGrcWendQTMmQBGRTKQ6wkcDV5YD2h9OdlM0daZ8RjB97ci2zaUZnH04cdnGO8+bbQ44LcDXmTlDFBJGQe3TaJYxPDi8dZOrzV/vAUVRiE4nMPVp8W73sWBrCXDm3sRawpKow+XlBacVa12MMUgR3+bTpQu16wXwDh5JSUdGzGNmJpbT9MLWnuMNs2WRTt1/v48zW17abDw8lXZUVbAQeAbE8CgpaviRVBFfkpCTJMOFfFPPie3PV5hY3LT2ju9mQT024tQ5eBt43lyKnJFkiGLWkCs9awhy5PbJnWUXyh0MDQ0FGIYJFAjdEFkU9iZldkta5qxFgGAuoMouo65C+miitbdGrFFN04YbGxuHfwJN4z4gjV4RIAAAAABJRU5ErkJggg=="); }' +
// Notifications Reloaded.
'#new_notifications_popup .notifications, #new_notifications_popup .messages, #new_notifications_popup .friend_requests { max-height: 160px; width: 100%; overflow: auto; }' +
'#new_notifications_popup .notifications .notifications_item, #new_notifications_popup .messages .notifications_item { width: 100%; }' +
// Posts Archive.
'#th-pa-wrapper * { box-sizing: border-box; }' +
'#th-pa-wrapper { float: left; border: 0px solid rgba(0,0,0,0.1); border-collapse: collapse; width: 100%; background: white; margin: 10px 0; background-color: #f6f7f8; }' +
'#th-pa-wrapper ul { margin: 0; }' +
'#th-pa-filter-empty { padding: 10px; font-weight: bold; background-color: #eee; }' +
'#th-pa-filter { padding: 4px 10px; font-weight: bold; }' +
'#th-pa-filter ul { display: inline-block; }' +
'#th-pa-filter label { cursor: pointer; font-weight: normal; }' +
'#th-pa-table { border: 0; border-collapse: collapse; width: 100%; line-height: 20px; }' +
'#th-pa-table thead { font-size: 1.5em; background-color: #fff; text-align: left; border-bottom: 1px solid rgba(0,0,0,0.05); }' +
'#th-pa-table th { padding: 10px; }' +
'#th-pa-table th:not(:first-child), #th-pa-table td:not(:first-child) { width: 40px; text-align: center; border-left: 1px solid rgba(0,0,0,0.05); }' +
'.th-pa-row-even { background-color: #eee; }' +
'.th-pa-row-odd { background-color: #fff; }' +
'#th-pa-loader-div { padding: 15px; font-weight: bold; }' +
'#th-pa-loader-div img { vertical-align: middle; margin-right: 5px; }' +
'#th-pa-table thead .icon { margin: 0; width: 24px; height: 24px; }' +
'#th-pa-table .view_icon { background-position: -202px -7px; }' +
'#th-pa-table .share_icon { background-position: -232px -7px; }' +
'#th-pa-table .like_icon { background-position: -263px -7px; }' +
'#th-pa-table .comment_icon { background-position: -292px -7px; }' +
'.th-pa-privacy .privacy_icon_private { background: url("/assets/friends_icon.png") no-repeat !important; background-size: 15px !important; }' +
'.th-pa-privacy #privacy_icon span { width: 15px; height: 15px; }' +
'.th-pa-privacy #privacy_icon span img { width: 15px; height: 15px; }' +
'.th-pa-pt-post .th-pa-pt { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACkUlEQVQ4T3XSW0hTcRwH8O9/N5vb2XTeNufCG0q2lKWYCoGQURQiPSzpISiIQnzoQkbgS9FDINGjUb30kqQUBEIaURR7qNzyNmuoZdPMXd3cObtfPJ0lyg7T39Phd37/D1/4/Qgy6r0t8BBgL2X2tr9ZliUikXCIoYP9nU2l3u0+yRx+Z/PT+v1KapNls4yZX15o1Xms2xd8mgig71RLAZ0e4gHj3310lVZBJVLZwI+fLhxrKMXKRpx1ekJPAkz4+tk2XYQHvLGu0+WlCiqa3MxKsGT34HSjFtEkCzuHOFzM42AoeoMHjE576coyJWUZG+YB+uNG/F7eAtIV4ZBlf5xd5RAe8NrioavL83ZNYOeAzJJLJYjGCcMDXn110zWV+dTkOD9BXYcR6z5m5336kVBAsEHHozvAA5PysoTUPkqyCwJZjh4a5QWoFed32+j/nlQkgG3Rt5Vg4EPBoEZV21OuboZMqkIo4oPdOYHAnyJQG8ZdEcPJbszNrzPk/lt1n66kbqBS24hQzI1EKgyxMBdCNg8rnknIREboCq9kIVKxADM2D0PujemW2vSdFeGEgwPWkGRj2IyLYTGF0XWmHeZpEzT+W1lAA5dgas7NkLujVWxrfTu+mCcgz49DqQIsnxIoLMnF0bYj+Dz7ER0GKwgR8pBcLoFl1sWQ/pc1S02HDlesOuZhNvmxT0ogVxIYWoogl1RhxrqIisjV7AQnumGecjLk9osDfZpizYC6RAKHy4EFaxCGViUUsmL8dURQQJ1DddnFLCCdYOLb2tYWbj6vHyxS5fdoiimIc1JIxIRwuhnEgs2JQllvdK9dBvzekZ07uPassYcb7OXu42Ayllr0rsWGh+7YRvY8BCDE/XP9AzObEEcy9WOXAAAAAElFTkSuQmCC"); }' +
'.th-pa-pt-share .th-pa-pt { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACaElEQVQ4T4XSX0hTURwH8O+5u9e8btetP1JuLUrBSIuRFUQvlpWQFiihyIJ6sYcei3qqhx6lB4nqxVw9BEX0EPQHKyKE/hCUIpZuRKl3Nsttbrpzt3nd3D3daZPdZnXgci+/c36f++P8fgR565Uv1gWwjvxY7psxRnjedE+h8YvHdtunc3GSf/ilb4Zu32SVNMYKjKHRaTg22FgoGu9Jx3Chce9amj1kAJ6PRGmlo1RKZwoB77cgDrrsmJhNsalw4mZMSZ5t2+ecMwC9nyN0c7lZUlMLgEkwVDEmh9G0ywF1gUHWkZ9BpTueUM8ZgCeDYVpxq1YihMN8ew+0yrplZNy/BGTXnI74Z1IsoCMG4PG7MWXL/QaLvc6Nyb47WDh+FVr1kcUkWQfyl0UsgpoiChlwl3YxQhZvnvGihRfNpKrhBJLxOAJvHyLT3Am2swWRqLKcn/2riSOYpSmVvG+1UteZKxJLJXVBQybyHZlJL0iJDWmrE/LrR9DcN0C2HTBUIPIcfF+jCnnTbKM1hxulTFjO1gCmJqAloiCCCMG5A3PmcshBCr7DUwAMf4kopK/JRqtrt0rpwIix9xwPZnVgIpQGTl2DqeaPCgQOQ76wQl4cKu0Cxy1Nn6DfgcCTqrJ5zDMTArOrQI6eR1HD6YLBEnVgcDikGLpw+6lX2djTaKmoWIPx0QiE1ksoqj+50mSjRAf6PwWNgKd3lJZfr5cIx6HYfRnF+9tXTM4Gs8DHwSkj0P3MT/e4yqRkIql3Qfprcg74MPDDCHTe7fdYV69r+2dm3mZsZvqB4Q70PbP+rP/9/p+T0A8EfwHTFvU4JDA5ZgAAAABJRU5ErkJggg=="); }' +
'.th-pa-pt-wallpost .th-pa-pt { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACeElEQVQ4T4WTXUiTURzGn/djc7pPW2vTqZgf3Wh+ZMgM0sI7LaSLjEC60ouii5K8KC9UiOimCLqIMoJIo7S7QIIQEqILNcXUVqx01nTfe91edWvOnd7X5dt0ix44nMOf5/z4f5xDIUmj1uBdgLQnx3bOhBCKZZnnfGit+/TRXN9OnEo2v7VyofICrTpOSApj5rsPZpOOeAJr/ZtBdDVZ9CHRtAvwZj4QKjJlqWNgUgCfv7nRWJmLH6tR4vKuPwryG1dbj+WHJcDQ2TK536AN/+KWaVaTj9quF2DVBgm0YPeiucaMSIzALkCcbv7h2nqkUwI86ai+RRj5daMpB06HC0ZLC8ynOiXA4lICICosQJa4KHEIEAkwcLl2odpy8qB2nxYrPx2wr3hRdHFAAtgFQLJUmXJEohQvAV52WnoLD1X06A1GLC8tIlrYAF3dBemOP8BLZ/ESQ1NYDUUjEuDZpSMlSo3GptLowK9yMLQ9gMJQnG6i27FMlobVFkhkcHNUb1Zw7J268ivn3K/GUHC+BbP2Dygu7UOGLFH3XomAua9+nup9ndeuyJD3l+bX4HBeC/iwE5lyDb44R2FzTKHE1AOzvjUVIKMxY/XyVPdw6VSjpbmaYePYiPqxFd8U6pMJED3iMRoTswuorxpOC5ie8/DUtcHKWFNDPRPc8Ahd5UDIFiiKgUKeDZ3SiJF3YzhzYjwFkCVkMPnJzVMd96qeKtVsG2iKTldrjq4Rx2v70gImpl3bTVQJyygs9Y37I+8ryg4oI7F4otNKFcSxivteiRmMf1z5+w5Ew+3Bycfa7P2pHfvHMIOcb2jXZxJ8yj/ZiPv/tC4Y3L8B/vXnOFesbhoAAAAASUVORK5CYII="); }' +
'.th-pa-picture { float: right; width: 70px; height: 70px; padding: 5px; text-align: center; }' +
'.th-pa-picture img { max-width: 60px; max-height: 60px; }' +
'.th-pa-post { float: left; width: 480px; padding: 5px; min-height: 70px; }' +
'.th-pa-title { font-weight: bold; }' +
'.th-pa-title, .th-pa-content { white-space: pre-line; }' +
'.th-pa-ellipsis { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }' +
'.th-pa-meta { opacity: 0.6; }' +
'#th-pa-table tr:hover .th-pa-meta { opacity: 1; }' +
'.th-pa-meta li, #th-pa-filter li { display: inline-block; margin-right: 10px; }' +
'.th-pa-expand { float: none; padding: 0px; width: 13px; height: 13px; vertical-align: text-top; }' +
'.th-pa-post-link, .th-pa-original-link { float: right; }' +
'.th-pa-stealth { display: none !important; }' +
'#th-pa-table tr:hover .th-pa-stealth { display: inline-block !important; }'
).appendTo( 'head' );
}
/**
* Add the about window which shows the version and changelog.
* It also displays donate buttons and an update button if a newer version is available.
*/
function addAboutWindow() {
doLog( 'Added about window.', 'i' );
// About window.
var $aboutWindow = $( '<div/>', {
'id' : 'th-aw',
html :
'<h1>About Tsu Helper</h1>' +
'<div class="th-about-love"><i class="th-icon th-icon-heart"></i>Made with love and care.</div>' +
'<div><ul class="th-aw-info">' +
'<li>Version <strong>' + Updater.localVersion + '</strong> (<a href="https://j.mp/tsu-helper-changelog" target="_blank">changelog</a>)<br />' +
'<li>©2014-2015 Armando Lüscher (<a href="/noplanman">@noplanman</a>)<br />' +
'<li><em>Disclaimer</em>: Tsu Helper is in no way affiliated with Tsu LLC.' +
'<li>Use it at your own risk.' +
'</ul></div>' +
'<div><i class="th-icon-manual"></i>For more details about this script, to see how it works,<br />and an overview of all the features and how to use them,<br /> take a look at the extensive manual <a href="https://j.mp/tsu-helper-readme" target="_blank">here</a>.</div>' +
'<div><ul class="th-aw-get-in-touch">' +
'<li>Found a <i class="th-icon th-icon-bug" title="Bug"></i>' +
'<li>Have a great <i class="th-icon th-icon-idea" title="Idea"></i>' +
'<li>Just want to say hi?' +
'<li><a class="message_pop_up fancybox.ajax" href="/messages/new/noplanman">Let me know!</a>' +
'</ul></div>' +
'<div>If you like this script and would like to support my work, please consider a small donation. It is very much appreciated <i class="th-icon th-icon-heartp"></i>' +
'<div class="th-buttons-div th-aw-donate-buttons">' +
'<a class="th-aw-donate-paypal" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CRQ829DME6CNW" target="_blank"><img alt="Donate via PayPal" title="Donate via PayPal" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" /></a>' +
'<span>« PayPal <i> - or - </i> Tsu »</span>' +
'<a class="th-aw-donate-tsu button message_pop_up fancybox.ajax donation" href="/users/profiles/donation/104738" title="Donate via Tsu">Donate</a>' +
'</div>' +
'</div>' +
'<div id="th-about-followme">Follow me and stay up to date!</div>' +
'<div>Iconset <a href="https://www.iconfinder.com/iconsets/essen" target="_blank">Essen</a> by <a href="http://pc.de" target="_blank">PC.de</a></div>'
});
// Get my card and add it to the about window.
$.get( '/users/profile_summary/104738', function( card ) {
$aboutWindow.find( '#th-about-followme' ).after( card );
});
// Settings window.
var $settingsWindow = $( '<div/>', {
'id' : 'th-sw',
html : '<h1>Tsu Helper Settings</h1>'
});
// Settings which are only a checkbox.
var checkboxSettings = '';
[
{ name : 'hideAds', txt : 'Hide Ads', help : 'Show or Hide all the Ads.' },
{ name : 'quickMention', txt : 'Enable Quick Mentions', help : 'Add Quick Mention links to comments and replies.' },
{ name : 'emphasizeNRP', txt : 'Emphasize Nested Replies', help : 'Emphasize the parent of nested comment replies, to make them more visible.' },
{ name : 'checkSocial', txt : 'Check Social Networks', help : 'Check if your new post is being shared to your connected Social Network accounts.' },
{ name : 'checkMaxHM', txt : 'Check Max. Hashtags & Mentions', help : 'Check if the maximum number of Hashtags & Mentions has been reached before posting.' }
].forEach(function( item ) {
checkboxSettings += '<div><label><input type="checkbox" name="' + item.name + '"' + checked( settings[ item.name ] ) + ' />' + item.txt + '</label><i class="th-icon th-icon-help th-sw-help" title="' + item.help + '"></i></div>';
});
// The debug level dropdown.
var debugLevelSettings = ( publicDebug || 104738 === window.current_user.id ) ?
'<div><label>Debug level: ' +
'<select name="debugLevel">' +
'<option value="disabled"' + selected( 'disabled', settings.debugLevel ) + '>Disabled</option>' +
'<option value="l"' + selected( 'l', settings.debugLevel ) + '>Log</option>' +
'<option value="i"' + selected( 'i', settings.debugLevel ) + '>Info</option>' +
'<option value="w"' + selected( 'w', settings.debugLevel ) + '>Warn</option>' +
'<option value="e"' + selected( 'e', settings.debugLevel ) + '>Error</option>' +
'</select>' +
'</label></div>' : '';
// List the available count options.
var selectNotifReloaded = '<select name="notifReloaded"><option value="0">Disabled</option>';
[ 5, 10, 15, 20, 25, 30 ].forEach(function( val ) {
selectNotifReloaded += '<option value="' + val + '">' + val + '</option>';
});
selectNotifReloaded += '</select>';
var $settingsForm = $( '<form/>', {
'id' : 'th-settings-form',
html :
checkboxSettings +=
// Notifications Reloaded
'<div><label>Notifications Reloaded count: ' +
selectNotifReloaded +
'</label><i class="th-icon th-icon-help th-sw-help" title="How many notifications to show in the notification popup."></i></div>'
+ debugLevelSettings
})
.appendTo( $settingsWindow );
// Defaults button on Settings window.
var $defaultsButton = $( '<a/>', {
'id' : 'th-sw-defaults-button',
class : 'button red',
title : 'Reset to default values',
html : 'Defaults',
click : function() {
Settings.setDefaults( $settingsForm );
}
})
.appendTo( $settingsWindow.find( 'h1' ) );
// The state in which the Settings are closed (back or save).
var settingsCloseState = null;
// Save button on Settings window.
var $saveButton = $( '<a/>', {
'id' : 'th-sw-save-button',
class : 'button',
title : 'Save Settings',
html : 'Save',
click : function() {
if ( confirm( 'Refresh page now for changes to take effect?' ) ) {
Settings.save( $settingsForm );
settingsCloseState = 'save';
$.fancybox.close();
}
}
});
// Back button on Settings window.
var $backButton = $( '<a/>', {
'id' : 'th-sw-back-button',
class : 'button grey',
title : 'Go Back without saving',
html : '« Back',
click : function() {
// Close this window.
settingsCloseState = 'back';
$.fancybox.close();
}
});
// Buttons on Settings window.
$( '<div/>', {
class : 'th-buttons-div',
html : '<span><a href="https://j.mp/tsu-helper-settings" target="_blank">Detailed Help</a></span>'
})
.prepend( $backButton )
.append( $saveButton )
.appendTo( $settingsWindow );
// Settings button on About window.
$( '<a/>', {
'id' : 'th-aw-settings-button',
title : 'Change Settings',
html : '',
click : function() {
// Open settings window in a fancybox.
Settings.populateForm( $settingsForm );
$.fancybox( $settingsWindow, {
closeBtn : false,
modal : true,
beforeClose : function() {
// If the Back button was pressed, reopen the About window.
if ( 'back' === settingsCloseState ) {
setTimeout(function() {
$.fancybox( $aboutWindow );
}, 10);
return false;
}
},
afterClose : function() {
// If the Save button was pressed, reload the page.
if ( 'save' === settingsCloseState ) {
location.reload();
return;
}
}
});
}
})
.appendTo( $aboutWindow.find( 'h1' ) );
// Check if there is a newer version available.
if ( Updater.hasUpdate() ) {
$( '<a/>', {
'id' : 'th-aw-update-button',
class : 'button th-update',
title : 'Update Tsu Helper to the newest version (' + Updater.remoteVersion + ')',
href : Updater.scriptURL,
html : 'New Version!',
click : function() {
if ( ! confirm( 'Upgrade to the newest version (' + Updater.remoteVersion + ')?\n\n(refresh this page after the script has been updated)' ) ) {
return false;
}
}
})
.attr( 'target', '_blank' ) // Open in new window / tab.
.appendTo( $aboutWindow.find( 'h1' ) );
}
// Link in the menu that opens the about window.
var $aboutWindowLink = $( '<a/>', {
title : 'About noplanman\'s Tsu Helper',
html : 'About Tsu Helper',
click : function() {
// Close the menu.
$( '#navBarHead .sub_nav' ).hide();
// Open about window in a fancybox.
$.fancybox( $aboutWindow );
}
});
// Check if there is a newer version available.
if ( Updater.hasUpdate() ) {
// Change the background color of the name tab on the top right.
$( '#navBarHead .tab.name' ).addClass( 'th-update' );
$aboutWindowLink.addClass( 'th-update' );
}
// Add "About" menu item.
$( '<li/>', { 'id' : 'th-menuitem-about', html : $aboutWindowLink } )
.appendTo( '#navBarHead .sub_nav' );
}
});