Qvitter/js/misc-functions.js
2017-04-21 00:48:41 +02:00

2895 lines
102 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
· ·
· ·
· Q V I T T E R ·
· ·
· ·
· <o) ·
· /_//// ·
· (____/ ·
· (o< ·
· o> \\\\_\ ·
· \\) \____) ·
· ·
· ·
· @licstart The following is the entire license notice for the ·
· JavaScript code in this page. ·
· ·
· Copyright (C) 2015 Hannes Mannerheim and other contributors ·
· ·
· ·
· This program is free software: you can redistribute it and/or modify ·
· it under the terms of the GNU Affero General Public License as ·
· published by the Free Software Foundation, either version 3 of the ·
· License, or (at your option) any later version. ·
· ·
· This program is distributed in the hope that it will be useful, ·
· but WITHOUT ANY WARRANTY; without even the implied warranty of ·
· MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ·
· GNU Affero General Public License for more details. ·
· ·
· You should have received a copy of the GNU Affero General Public License ·
· along with this program. If not, see <http://www.gnu.org/licenses/>. ·
· ·
· @licend The above is the entire license notice ·
· for the JavaScript code in this page. ·
· ·
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
/* ·
·
· Trigger click on <input type="file"> elements
·
· @param inputFile: jQuery element to trigger click on
·
· · · · · · · · · */
function triggerClickOnInputFile(inputFile) {
if(typeof bowser != 'undefined') {
var bowserIntVersion = parseInt(bowser.version,10);
if(typeof bowser.chrome != 'undefined' && bowser.chrome === true && bowserIntVersion < 53
|| typeof bowser.opera != 'undefined' && bowser.opera === true && bowserIntVersion < 39
|| typeof bowser.safari != 'undefined' && bowser.safari === true && bowserIntVersion < 9) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click", true, true);
inputFile[0].dispatchEvent(evt);
console.log('triggering click on on input file element with the old trigger hack for older browsers...');
}
else {
inputFile.trigger('click');
console.log('regular click triggered on input file element');
}
}
else {
inputFile.trigger('click');
console.log('no bowser object found: regular click triggered on input file element');
}
console.log(bowser);
}
/* ·
·
· Store in localStorage object cache
·
· @param name: the name of this type of object
· @param unique_id: some unique_id the key in localStorage will be name-unique_id
· @param object: the object to store
·
· · · · · · · · · */
function localStorageObjectCache_STORE(name, unique_id, object) {
if(localStorageIsEnabled() === false) {
return false;
}
name = localStorageMaybeAppendIdToKey(name);
if(object === false || object === null || object.length < 1) {
// false or an empty object means we remove this entry
if(typeof localStorage[name + '-' + unique_id] != 'undefined' && localStorage[name + '-' + unique_id] !== null) {
delete localStorage[name + '-' + unique_id];
}
return false;
}
var dataToSave = {};
dataToSave.modified = Date.now();
dataToSave.cdata = LZString.compressToUTF16(JSON.stringify(object));
try {
localStorage.setItem(name + '-' + unique_id, JSON.stringify(dataToSave));
}
catch (e) {
if (e.name == 'QUOTA_EXCEEDED_ERR' || e.name == 'NS_ERROR_DOM_QUOTA_REACHED' || e.name == 'QuotaExceededError' || e.name == 'W3CException_DOM_QUOTA_EXCEEDED_ERR') {
removeOldestLocalStorageEntries(function(){
localStorageObjectCache_STORE(name, unique_id, object);
});
}
else {
console.log('could not store in localStorage, unknown error');
}
}
}
/* ·
·
· Remove the 100 oldest cached items
·
· · · · · · · · · */
function removeOldestLocalStorageEntries(callback) {
// grab the expiry and store the modified-key into an object
var modified = Object.keys(localStorage).reduce(function(collection,key){
var currentModified = JSON.parse(localStorage.getItem(key)).modified;
collection[currentModified] = key;
return collection;
},{});
delete modified['undefined']; // we don't want those
// get the modified dates into an array
var modifiedDates = Object.keys(modified);
modifiedDates.sort();
var i = 0;
$.each(modifiedDates,function(k,v){
delete localStorage[modified[v]];
i++;
if(i>=100) {
return false;
}
});
console.log('removed ' + i + ' old localstorage items');
if(i>0) {
callback();
return true;
}
else {
return false;
}
}
/* ·
·
· Get from localStorage object cache
·
· @param name: the name of this type of object
· @param unique_id: some unique_id the key in localStorage will be name-unique_id
·
· · · · · · · · · */
function localStorageObjectCache_GET(name, unique_id) {
if(localStorageIsEnabled() === false) {
return false;
}
name = localStorageMaybeAppendIdToKey(name);
if(typeof localStorage[name + '-' + unique_id] == 'undefined' || localStorage[name + '-' + unique_id] === null) {
return false;
}
try {
var parsedObject = JSON.parse(localStorage[name + '-' + unique_id]);
}
catch(e) {
return false;
}
if(typeof parsedObject.modified == 'undefined' || parsedObject.modified === null) {
// invalid or old localstorage object found, check the whole localstorage!
checkLocalStorage();
return false;
}
else {
try {
var decompressedAndParsed = JSON.parse(LZString.decompressFromUTF16(parsedObject.cdata));
return decompressedAndParsed;
}
catch(e) {
return false;
}
}
}
// to the following data types we add the logged in user's user id,
// since they contain user specific data (0 for logged out)
// selectedLanguage is handled differently, since we want to be able to
// access the logged out user's data if we're logged in
function localStorageMaybeAppendIdToKey(name) {
if(jQuery.inArray(name, ['browsingHistory', 'conversation', 'queetBoxInput', 'streamState']) !== -1) {
if(window.loggedIn) {
return name + '-' + window.loggedIn.id;
}
else {
return name + '-0' ;
}
}
else {
return name;
}
}
function checkLocalStorage() {
if(localStorageIsEnabled() === false) {
console.log('localstorage disabled');
return false;
}
console.log('checking localStorage for invalid entries');
var dateNow = Date.now()
var corrected = 0;
var deleted = 0;
var compressed = 0;
$.each(localStorage, function(k,entry){
if(typeof entry == 'string') {
// check that entry is valid json
try {
var entryParsed = JSON.parse(entry);
}
catch(e) {
delete localStorage[k];
deleted++;
return true;
}
// check that it is a valid/currently used data type
var validDataTypes = [
'browsingHistory',
'conversation',
'favsAndRequeets',
'languageData',
'fullQueetHtml',
'selectedLanguage',
'queetBoxInput',
'streamState',
'languageErrorMessageDiscarded'
];
var thisDataType = k.substring(0,k.indexOf('-'));
if($.inArray(thisDataType, validDataTypes) == -1 || k.indexOf('-') == -1) {
delete localStorage[k];
deleted++;
return true;
}
// check that it has a modified entry, if not: add one
if(typeof entryParsed.modified == 'undefined' || entryParsed.modified === null) {
var newEntry = {};
newEntry.modified = dateNow - corrected; // we want as unique dates as possible
newEntry.cdata = entryParsed;
try {
localStorage.setItem(k, JSON.stringify(newEntry));
}
catch (e) {
if (e.name == 'QUOTA_EXCEEDED_ERR' || e.name == 'NS_ERROR_DOM_QUOTA_REACHED' || e.name == 'QuotaExceededError' || e.name == 'W3CException_DOM_QUOTA_EXCEEDED_ERR') {
removeOldestLocalStorageEntries(function(){
localStorage.setItem(k, JSON.stringify(newEntry));
});
}
}
entryParsed = newEntry;
corrected++;
}
// compress uncompressed data
if(typeof entryParsed.data != 'undefined') {
// but not if it's not containing any data (some bug may have saved an empty, false or null value)
if(entryParsed.data === false || entryParsed.data === null || entryParsed.data.length == 0) {
delete localStorage[k];
deleted++;
return true;
}
var dataCompressed = LZString.compressToUTF16(JSON.stringify(entryParsed.data));
var newCompressedEntry = {};
newCompressedEntry.modified = entryParsed.modified;
newCompressedEntry.cdata = dataCompressed;
localStorage.setItem(k, JSON.stringify(newCompressedEntry));
compressed++;
}
}
});
console.log(corrected + ' entries corrected, ' + deleted + ' entries deleted, ' + compressed + ' entries compressed');
}
/* ·
·
· Checks if localstorage is availible
·
· We can't just do if(typeof localStorage.selectedLanguage != 'undefined')
· because firefox with cookies disabled then freaks out and stops executing js completely
·
· · · · · · · · · */
function localStorageIsEnabled() {
var mod = 'test';
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
}
catch(e) {
if (e.name == 'QUOTA_EXCEEDED_ERR' || e.name == 'NS_ERROR_DOM_QUOTA_REACHED' || e.name == 'QuotaExceededError' || e.name == 'W3CException_DOM_QUOTA_EXCEEDED_ERR') {
// if localstorage is empty but returns a full error, we assume it's disabled (in an ugly way)
if(localStorage.length === 0) {
return false;
}
var successfulRemoval = removeOldestLocalStorageEntries(function(){
return localStorageIsEnabled();
});
if(successfulRemoval === false) {
return false;
}
}
else {
return false;
}
}
}
/* ·
·
· Block/unblock user and do necessary stuff
·
· · · · · · · · · */
function blockUser(arg, callback) {
$('body').click(); // a click somewhere hides any open menus
// arguments is sent as an object, for easier use with a menu's function-row
var userId = arg.userId;
var blockButton_jQueryElement = arg.blockButton_jQueryElement;
display_spinner();
APIBlockOrUnblockUser('block', userId, function(data) {
remove_spinner();
// activate the button, if we were passed one
if(typeof blockButton_jQueryElement != 'undefined') {
blockButton_jQueryElement.removeClass('disabled');
}
if(data && data.statusnet_blocking === true) {
// success
markUserAsBlockedInDOM(userId, data.following);
if(typeof callback == 'function') {
callback(blockButton_jQueryElement);
}
}
else {
// failed!
showErrorMessage(window.sL.failedBlockingUser);
}
});
}
function unblockUser(arg, callback) {
$('body').click(); // a click somewhere hides any open menus
// arguments is sent as an object, for easier use with a menu's function-row
var userId = arg.userId;
var blockButton_jQueryElement = arg.blockButton_jQueryElement;
display_spinner();
APIBlockOrUnblockUser('unblock', userId, function(data) {
remove_spinner();
// activate the button, if we were passed one
if(typeof blockButton_jQueryElement != 'undefined') {
blockButton_jQueryElement.removeClass('disabled');
}
if(data && data.statusnet_blocking === false) {
// success
markUserAsUnblockedInDOM(userId, data.following);
if(typeof callback == 'function') {
callback(blockButton_jQueryElement);
}
}
else {
// failed!
showErrorMessage(window.sL.failedUnblockingUser);
}
});
}
function markUserAsBlockedInDOM(userId, following) {
// display buttons accordingly
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').addClass('blocking');
if(following) {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').addClass('following');
}
else {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').removeClass('following');
}
// hide notices from the blocked user
$.each($('.stream-item[data-quitter-id-in-stream][data-user-id="' + userId + '"]'),function(){
$(this).addClass('profile-blocked-by-me');
$(this).children('.queet').attr('data-tooltip',window.sL.thisIsANoticeFromABlockedUser);
});
// add to the window.allBlocking array
if (userIsBlocked(userId) === false) {
window.allBlocking.push(userId);
}
}
function markUserAsUnblockedInDOM(userId, following) {
// display buttons accordingly
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').removeClass('blocking');
if(following) {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').addClass('following');
}
else {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').removeClass('following');
}
// hide the user from lists of blocked users
if(window.currentStreamObject.name == 'user blocks' && window.currentStreamObject.nickname == window.loggedIn.screen_name) {
$.each($('.stream-item.user[data-user-id="' + userId + '"]'),function(){
slideUpAndRemoveStreamItem($(this));
});
}
// unhide notices from the blocked user
$.each($('.stream-item[data-quitter-id-in-stream][data-user-id="' + userId + '"]'),function(){
$(this).removeClass('profile-blocked-by-me');
$(this).children('.queet').removeAttr('data-tooltip');
});
// remove from the window.allBlocking array
var existingBlockIndex = window.allBlocking.indexOf(userId);
if (existingBlockIndex > -1) {
window.allBlocking.splice(existingBlockIndex, 1);
}
}
/* ·
·
· Is this user id blocked?
·
· · · · · · · · · */
function userIsBlocked(userId) {
var existingBlock = window.allBlocking.indexOf(userId);
if (existingBlock > -1) {
return true;
}
else {
return false;
}
}
/* ·
·
· Marks all notices from blocked users in an jQuery object as blocked
·
· · · · · · · · · */
function markAllNoticesFromBlockedUsersAsBlockedInJQueryObject(obj) {
$.each(window.allBlocking,function(){
obj.find('.stream-item[data-user-id="' + this + '"]').addClass('profile-blocked-by-me');
obj.find('.stream-item[data-user-id="' + this + '"]').children('.queet').attr('data-tooltip',window.sL.thisIsANoticeFromABlockedUser);
});
}
/* ·
·
· Marks all notices from muted users in an jQuery object as muted
·
· · · · · · · · · */
function markAllNoticesFromMutedUsersAsMutedInJQueryObject(obj) {
$.each(obj.find('.stream-item'),function(){
if(isUserMuted($(this).attr('data-user-id'))) {
$(this).addClass('user-muted');
$(this).children('.queet').attr('data-tooltip',window.sL.thisIsANoticeFromAMutedUser);
}
else {
$(this).children('.queet').removeAttr('data-tooltip');
$(this).removeClass('user-muted');
}
});
}
/* ·
·
· Marks all profile cards from muted users as muted in DOM
·
· · · · · · · · · */
function markAllProfileCardsFromMutedUsersAsMutedInDOM() {
$.each($('body').find('.profile-header-inner'),function(){
if(isUserMuted($(this).attr('data-user-id'))) {
$(this).parent('.profile-card').addClass('user-muted');
}
else {
$(this).parent('.profile-card').removeClass('user-muted');
}
});
}
/* ·
·
· Function invoked after mute and unmute
·
· · · · · · · · · */
function hideOrShowNoticesAfterMuteOrUnmute() {
markAllNoticesFromMutedUsersAsMutedInJQueryObject($('#feed-body'));
markAllProfileCardsFromMutedUsersAsMutedInDOM();
}
/* ·
·
· Sandbox/unsandbox user and do necessary stuff
·
· · · · · · · · · */
function sandboxCreateOrDestroy(arg, callback) {
$('body').click(); // a click somewhere hides any open menus
display_spinner();
APISandboxCreateOrDestroy(arg.createOrDestroy, arg.userId, function(data) {
remove_spinner();
if(!data) {
// failed!
showErrorMessage(window.sL.ERRORfailedSandboxingUser);
}
});
}
/* ·
·
· Sandbox/unsandbox user and do necessary stuff
·
· · · · · · · · · */
function silenceCreateOrDestroy(arg, callback) {
$('body').click(); // a click somewhere hides any open menus
display_spinner();
APISilenceCreateOrDestroy(arg.createOrDestroy, arg.userId, function(data) {
remove_spinner();
if(!data) {
// failed!
showErrorMessage(window.sL.ERRORfailedSilencingUser);
}
});
}
/* ·
·
· Get the logged in user's menu array
·
· · · · · · · · · */
function loggedInUsersMenuArray() {
return [
{
type: 'function',
functionName: 'goToEditProfile',
label: window.sL.editMyProfile
},
{
type: 'link',
href: window.siteInstanceURL + 'settings/profile',
label: window.sL.settings
},
{
type:'divider'
},
{
type: 'link',
href: window.siteInstanceURL + window.loggedIn.screen_name + '/mutes',
label: window.sL.userMuted
},
{
type: 'link',
href: window.siteInstanceURL + window.loggedIn.screen_name + '/blocks',
label: window.sL.userBlocked
}];
}
/* ·
·
· Append moderator user actions to menu array
·
· @param menuArray: array used to build menus in getMenu()
· @param userID: the user id of the user to act on
· @param userScreenName: the screen_name/nickname/username of the user to act on
· @param sandboxed: is the user sandboxed?
· @param silenced: is the user silenced?
·
· · · · · · · · · */
function appendModeratorUserActionsToMenuArray(menuArray,userID,userScreenName,sandboxed,silenced) {
// not if it's me
if(window.loggedIn.id == userID) {
return menuArray;
}
if(window.loggedIn !== false && window.loggedIn.rights.sandbox === true) {
menuArray.push({type:'divider'});
if(sandboxed === true) {
menuArray.push({
type: 'function',
functionName: 'sandboxCreateOrDestroy',
functionArguments: {
userId: userID,
createOrDestroy: 'destroy'
},
label: window.sL.unSandboxThisUser.replace('{nickname}','@' + userScreenName)
});
}
else {
menuArray.push({
type: 'function',
functionName: 'sandboxCreateOrDestroy',
functionArguments: {
userId: userID,
createOrDestroy: 'create'
},
label: window.sL.sandboxThisUser.replace('{nickname}','@' + userScreenName)
});
}
}
if(window.loggedIn !== false && window.loggedIn.rights.silence === true) {
if(silenced === true) {
menuArray.push({
type: 'function',
functionName: 'silenceCreateOrDestroy',
functionArguments: {
userId: userID,
createOrDestroy: 'destroy'
},
label: window.sL.unSilenceThisUser.replace('{nickname}','@' + userScreenName)
});
}
else {
menuArray.push({
type: 'function',
functionName: 'silenceCreateOrDestroy',
functionArguments: {
userId: userID,
createOrDestroy: 'create'
},
label: window.sL.silenceThisUser.replace('{nickname}','@' + userScreenName)
});
}
}
return menuArray;
}
/* ·
·
· Updates the times for all queets loaded to DOM
·
· · · · · · · · · */
function updateAllQueetsTimes() {
$('[data-created-at]').each(function(){
// if the element with the data-created-at doesn't have an a-child, we change the html of the element
if($(this).children('a').length==0){
$(this).html(parseTwitterDate($(this).attr('data-created-at')));
}
// otherwise the change the child's html
else {
$(this).children('a').html(parseTwitterDate($(this).attr('data-created-at')));
}
});
}
/* ·
·
· Is this a local URL?
·
· · · · · · · · · */
function isLocalURL(url) {
if(url.substring(0,window.siteInstanceURL.length) == window.siteInstanceURL // site url
|| url.substring(0,window.siteAttachmentURLBase.length) == window.siteAttachmentURLBase // attachment url
|| url.substring(0,window.siteAvatarURLBase.length) == window.siteAvatarURLBase // avatar url
) {
return true;
}
return false;
}
/* ·
·
· Check for hidden items and show the new queets bar if there are any
·
· · · · · · · · · */
function maybeShowTheNewQueetsBar() {
var newQueetsNum = $('#feed-body').find('.stream-item.hidden:not(.always-hidden):not(.hidden-repeat)').length;
// subtract the number of hidden notices from muted users if this isn't the notifications stream,
// or if this is the notifications stream but the user has opted out of seeing notifications from muted users
var mutedHiddenNum = 0;
if(window.currentStreamObject.name == 'notifications') {
if($('#feed-body').hasClass('hide-notifications-from-muted-users')) {
mutedHiddenNum = $('#feed-body').find('.stream-item.hidden.user-muted:not(.always-hidden):not(.hidden-repeat)').length;
}
}
else {
var mutedHiddenNum = $('#feed-body').find('.stream-item.hidden.user-muted:not(.always-hidden):not(.hidden-repeat)').length;
}
newQueetsNum = newQueetsNum - mutedHiddenNum;
if(newQueetsNum > 0) {
$('#new-queets-bar').parent().removeClass('hidden');
// bar label
if(newQueetsNum == 1) { var q_txt = window.sL.newQueet; }
else { var q_txt = window.sL.newQueets; }
if(window.currentStreamObject.name == 'notifications') {
if(newQueetsNum == 1) { var q_txt = window.sL.newNotification; }
else { var q_txt = window.sL.newNotifications; }
}
$('#new-queets-bar').html(q_txt.replace('{new-notice-count}',newQueetsNum));
}
}
/* ·
·
· Align tooltips to the hovered element
·
· · · · · · · · · */
function alignTooltipTohoveredElement(tooltipElement,tooltipCaret,hovered) {
var tooltipWidth = tooltipElement.outerWidth();
var tooltipHeight = tooltipElement.outerHeight();
var windowWidth = $(window).width();
var windowScrollPosY = $(window).scrollTop();
var targetPosX = hovered.offset().left;
var targetPosY = hovered.offset().top;
var targetHeight = hovered.outerHeight();
var targetWidth = hovered.outerWidth();
// too little space on top of element, show tooltip at bottom
if((targetPosY-windowScrollPosY-tooltipHeight-10) < 0) {
var tooltipCaretPosX = targetPosX+targetWidth/2-5;
var tooltipCaretPosY = targetPosY+targetHeight+2;
// caret always directly under element
tooltipCaret.css('left',tooltipCaretPosX + 'px');
tooltipCaret.css('top',tooltipCaretPosY + 'px');
tooltipCaret.addClass('top');
// tooltip itself might bleed over the window edges, and need moving
var tooltipPosX = targetPosX+targetWidth/2-tooltipWidth/2;
var tooltipPosY = targetPosY+targetHeight+7;
if((tooltipPosX+tooltipWidth)>windowWidth) {
tooltipPosX = windowWidth-tooltipWidth-5;
}
if(tooltipPosX < 5) {
tooltipPosX = 5;
}
tooltipElement.css('left',tooltipPosX + 'px');
tooltipElement.css('top',tooltipPosY + 'px');
}
// tooltip at top
else {
var tooltipCaretPosX = targetPosX+targetWidth/2-5;
var tooltipCaretPosY = targetPosY-8;
// caret always directly on top of element
tooltipCaret.css('left',tooltipCaretPosX + 'px');
tooltipCaret.css('top',tooltipCaretPosY + 'px');
tooltipCaret.addClass('bottom');
// tooltip itself might bleed over the window edges, and need moving
var tooltipPosX = targetPosX+targetWidth/2-tooltipWidth/2;
var tooltipPosY = targetPosY-7-tooltipHeight;
if((tooltipPosX+tooltipWidth)>windowWidth) {
tooltipPosX = windowWidth-tooltipWidth-5;
}
if(tooltipPosX < 5) {
tooltipPosX = 5;
}
tooltipElement.css('left',tooltipPosX + 'px');
tooltipElement.css('top',tooltipPosY + 'px');
}
}
/* ·
·
· Cache the unicode compatible regexps for the syntax highlighting
·
· · · · · · · · · */
function cacheSyntaxHighlighting() {
window.syntaxHighlightingRegexps = Object();
var allDomains = '(abb|abbott|abogado|ac|academy|accenture|accountant|accountants|active|actor|ad|ads|adult|ae|aero|af|afl|ag|agency|ai|aig|airforce|al|allfinanz|alsace|am|amsterdam|an|android|ao|apartments|aq|aquarelle|ar|archi|army|arpa|as|asia|associates|at|attorney|au|auction|audio|auto|autos|aw|ax|axa|az|ba|band|bank|bar|barclaycard|barclays|bargains|bauhaus|bayern|bb|bbc|bbva|bd|be|beer|berlin|best|bf|bg|bh|bi|bible|bid|bike|bingo|bio|biz|bj|bl|black|blackfriday|bloomberg|blue|bm|bmw|bn|bnpparibas|bo|boats|bond|boo|boutique|bq|br|bridgestone|broker|brother|brussels|bs|bt|budapest|build|builders|business|buzz|bv|bw|by|bz|bzh|ca|cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cbn|cc|cd|center|ceo|cern|cf|cfa|cfd|cg|ch|channel|chat|cheap|chloe|christmas|chrome|church|ci|cisco|citic|city|ck|cl|claims|cleaning|click|clinic|clothing|club|cm|cn|co|coach|codes|coffee|college|cologne|com|community|company|computer|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses|cr|credit|creditcard|cricket|crs|cruises|cu|cuisinella|cv|cw|cx|cy|cymru|cyou|cz|dabur|dad|dance|date|dating|datsun|day|dclk|de|deals|degree|delivery|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dj|dk|dm|dnp|do|docs|dog|doha|domains|doosan|download|durban|dvag|dz|earth|eat|ec|edu|education|ee|eg|eh|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|er|erni|es|esq|estate|et|eu|eurovision|eus|events|everbank|exchange|expert|exposed|express|fail|faith|fan|fans|farm|fashion|feedback|fi|film|finance|financial|firmdale|fish|fishing|fit|fitness|fj|fk|flights|florist|flowers|flsmidth|fly|fm|fo|foo|football|forex|forsale|foundation|fr|frl|frogans|fund|furniture|futbol|fyi|ga|gal|gallery|garden|gb|gbiz|gd|gdn|ge|gent|gf|gg|ggee|gh|gi|gift|gifts|gives|gl|glass|gle|global|globo|gm|gmail|gmo|gmx|gn|gold|goldpoint|golf|goo|goog|google|gop|gov|gp|gq|gr|graphics|gratis|green|gripe|gs|gt|gu|guge|guide|guitars|guru|gw|gy|hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hk|hm|hn|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|house|how|hr|ht|hu|ibm|icbc|icu|id|ie|ifm|il|im|immo|immobilien|in|industries|infiniti|info|ing|ink|institute|insure|int|international|investments|io|iq|ir|irish|is|it|iwc|java|jcb|je|jetzt|jewelry|jll|jm|jo|jobs|joburg|jp|juegos|kaufen|kddi|ke|kg|kh|ki|kim|kitchen|kiwi|km|kn|koeln|komatsu|kp|kr|krd|kred|kw|ky|kyoto|kz|la|lacaixa|land|lat|latrobe|lawyer|lb|lc|lds|lease|leclerc|legal|lgbt|li|liaison|lidl|life|lighting|limited|limo|link|lk|loan|loans|lol|london|lotte|lotto|love|lr|ls|lt|ltda|lu|lupin|luxe|luxury|lv|ly|ma|madrid|maif|maison|management|mango|market|marketing|markets|marriott|mba|mc|md|me|media|meet|melbourne|meme|memorial|men|menu|mf|mg|mh|miami|mil|mini|mk|ml|mm|mma|mn|mo|mobi|moda|moe|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|mp|mq|mr|ms|mt|mtn|mtpc|mu|museum|mv|mw|mx|my|mz|na|nadex|nagoya|name|navy|nc|ne|nec|net|network|neustar|new|news|nexus|nf|ng|ngo|nhk|ni|nico|ninja|nissan|nl|no|np|nr|nra|nrw|ntt|nu|nyc|nz|okinawa|om|one|ong|onl|online|ooo|org|organic|osaka|otsuka|ovh|pa|page|panerai|paris|partners|parts|party|pe|pf|pg|ph|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pink|pizza|pk|pl|place|plumbing|plus|pm|pn|pohl|poker|porn|post|pr|praxi|press|pro|prod|productions|prof|properties|property|ps|pt|pub|pw|py|qa|qpon|quebec|racing|re|realtor|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rio|rip|ro|rocks|rodeo|rs|rsvp|ru|ruhr|run|rw|ryukyu|sa|saarland|sale|samsung|sandvik|sandvikcoromant|sap|sarl|saxo|sb|sc|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scot|sd|se|seat|sener|services|sew|sex|sexy|sg|sh|shiksha|shoes|show|shriram|si|singles|site|sj|sk|ski|sky|sl|sm|sn|sncf|so|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting|sr|ss|st|study|style|su|sucks|supplies|supply|support|surf|surgery|suzuki|sv|swiss|sx|sy|sydney|systems|sz|taipei|tatar|tattoo|tax|taxi|tc|td|team|tech|technology|tel|temasek|tennis|tf|tg|th|thd|theater|tickets|tienda|tips|tires|tirol|tj|tk|tl|tm|tn|to|today|tokyo|tools|top|toray|toshiba|tours|town|toys|tp|tr|trade|trading|training|travel|trust|tt|tui|tv|tw|tz|ua|ug|uk|um|university|uno|uol|us|uy|uz|va|vacations|vc|ve|vegas|ventures|versicherung|vet|vg|vi|viajes|video|villas|vision|vlaanderen|vn|vodka|vote|voting|voto|voyage|vu|wales|walter|wang|watch|webcam|website|wed|wedding|weir|wf|whoswho|wien|wiki|williamhill|win|wme|work|works|world|ws|wtc|wtf|xbox|xerox|xin|测试|परीक्षा|佛山|慈善|集团|在线|한국|ভারত|八卦|موقع|বাংলা|公益|公司|移动|我爱你|москва|испытание|қаз|онлайн|сайт|срб|бел|时尚|테스트|淡马锡|орг|삼성|சிங்கப்பூர்|商标|商店|商城|дети|мкд|טעסט|工行|中文网|中信|中国|中國|娱乐|谷歌|భారత్|ලංකා|測試|ભારત|भारत|آزمایشی|பரிட்சை|网店|संगठन|餐厅|网络|укр|香港|δοκιμή|飞利浦|إختبار|台湾|台灣|手机|мон|الجزائر|عمان|ایران|امارات|بازار|پاکستان|الاردن|بھارت|المغرب|السعودية|سودان|عراق|مليسيا|澳門|政府|شبكة|გე|机构|组织机构|健康|ไทย|سورية|рус|рф|تونس|みんな|グーグル|ελ|世界|ਭਾਰਤ|网址|游戏|vermögensberater|vermögensberatung|企业|信息|مصر|قطر|广东|இலங்கை|இந்தியா|հայ|新加坡|فلسطين|テスト|政务|xxx|xyz|yachts|yandex|ye|yodobashi|yoga|yokohama|youtube|yt|za|zip|zm|zone|zuerich|zw|oracle|xn--1qqw23a|xn--30rr7y|xn--3bst00m|xn--3ds443g|xn--3e0b707e|xn--45brj9c|xn--45q11c|xn--4gbrim|xn--55qw42g|xn--55qx5d|xn--6frz82g|xn--6qq986b3xl|xn--80adxhks|xn--80ao21a|xn--80asehdb|xn--80aswg|xn--90a3ac|xn--90ais|xn--9et52u|xn--b4w605ferd|xn--c1avg|xn--cg4bki|xn--clchc0ea0b2g2a9gcd|xn--czr694b|xn--czrs0t|xn--czru2d|xn--d1acj3b|xn--d1alf|xn--estv75g|xn--fiq228c5hs|xn--fiq64b|xn--fiqs8s|xn--fiqz9s|xn--fjq720a|xn--flw351e|xn--fpcrj9c3d|xn--fzc2c9e2c|xn--gecrj9c|xn--h2brj9c|xn--hxt814e|xn--i1b6b1a6a2e|xn--imr513n|xn--io0a7i|xn--j1amh|xn--j6w193g|xn--kcrx77d1x4a|xn--kprw13d|xn--kpry57d|xn--kput3i|xn--l1acc|xn--lgbbat1ad8j|xn--mgb9awbf|xn--mgba3a4f16a|xn--mgbaam7a8h|xn--mgbab2bd|xn--mgbayh7gpa|xn--mgbbh1a71e|xn--mgbc0a9azcg|xn--mgberp4a5d4ar|xn--mgbpl2fh|xn--mgbx4cd0ab|xn--mxtq1m|xn--ngbc5azd|xn--node|xn--nqv7f|xn--nqv7fs00ema|xn--nyqy26a|xn--o3cw4h|xn--ogbpf8fl|xn--p1acf|xn--p1ai|xn--pgbs0dh|xn--q9jyb4c|xn--qcka1pmc|xn--rhqv96g|xn--s9brj9c|xn--ses554g|xn--unup4y|xn--vermgensberater-ctb|xn--vermgensberatung-pwb|xn--vhquv|xn--vuq861b|xn--wgbh1c|xn--wgbl6a|xn--xhq521b|xn--xkc2al3hye2a|xn--xkc2dl3a5ee0h|xn--y9a3aq|xn--yfro4i67o|xn--ygbi2ammx|xn--zfr164b)';
window.syntaxHighlightingRegexps.externalMention = XRegExp.cache('(^|\\s|\\.|<br>|&nbsp;|\\()(@)[a-zA-Z0-9]+(@)[\\p{L}\\p{N}\\-\\.]+(\\.)(' + allDomains + ')($|\\s|\\.|\\,|\\:|\\-|\\<|\\!|\\?|\\&|\\)|\\\')');
window.syntaxHighlightingRegexps.mention = /(^|\s|\.|<br>|&nbsp;|\()(@)[a-zA-Z0-9]+($|\s|\.|\,|\:|\-|\<|\!|\?|\&|\)|\')/;
window.syntaxHighlightingRegexps.tag = XRegExp.cache('(^|\\s|\\.|<br>|&nbsp;|\\()(\\#)[\\p{L}\\p{N}\\-\\.]+($|\\s|\\,|\\:|\\<|\\!|\\?|\\&|\\)|\\\')');
window.syntaxHighlightingRegexps.url = XRegExp.cache('(^|\\s|\\.|<br>|&nbsp;|\\()(http\\:\\/\\/|https\:\\/\\/)([\\p{L}\\p{N}\\-\\.]+)?(\\.)(' + allDomains + ')(\\/[\\p{L}\\p{N}\\%\\!\\*\\\'\\(\\)\\;\\:\\@\\&\\=\\+\\$\\,\\/\\?\\#\\[\\]\\-\\_\\.\\~]+)?(\\/)?($|\\s|\\,|\\:|\\-|\\<|\\!|\\?|\\&|\\)|\\\')');
window.syntaxHighlightingRegexps.urlWithoutProtocol = XRegExp.cache('(^|\\s|\\.|<br>|&nbsp;|\\()[\\p{L}\\p{N}\\-\\.]+(\\.)(' + allDomains + ')(\\/[\\p{L}\\p{N}\\%\\!\\*\\\'\\(\\)\\;\\:\\@\\&\\=\\+\\$\\,\\/\\?\\#\\[\\]\\-\\_\\.\\~]+)?(\\/)?($|\\s|\\.|\\,|\\:|\\-|\\<|\\!|\\?|\\&|\\)|\\\')');
window.syntaxHighlightingRegexps.email = XRegExp.cache('(^|\\s|\\.|<br>|&nbsp;|\\()([a-zA-Z0-9\\!\\#\\$\\%\\&\\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~\\.]+)?(@)[\\p{L}\\p{N}\\-\\.]+(\\.)(' + allDomains + ')($|\\s|\\.|\\,|\\:|\\-|\\<|\\!|\\?|\\&|\\)|\\\')');
cacheSyntaxHighlightingGroups();
}
/* ·
·
· Cache syntax highlighting for groups
·
· · · · · · · · · */
function cacheSyntaxHighlightingGroups() {
if(window.groupNicknamesAndLocalAliases.length > 0) {
var allGroupNicknamesAndLocalAliases = '(' + window.groupNicknamesAndLocalAliases.join('|') + ')';
window.syntaxHighlightingRegexps.group = XRegExp.cache('(^|\\s|\\.|<br>|&nbsp;|\\()(\\!)' + allGroupNicknamesAndLocalAliases + '($|\\s|\\.|\\,|\\:|\\-|\\<|\\!|\\?|\\&|\\)|\\\')');
}
}
/* ·
·
· User array cache (called array because it's an array in php)
·
· Stored in window.userArrayCache with unique key like instance_url/nickname
· with protocol (http:// or https://) trimmed off, e.g. "quitter.se/hannes2peer"
·
· · · · · · · · · */
window.userArrayCache = new Object();
window.convertUriToUserArrayCacheKey = new Object();
window.convertStatusnetProfileUrlToUserArrayCacheKey = new Object();
window.convertLocalIdToUserArrayCacheKey = new Object();
function userArrayCacheStore(data) {
if(typeof data == 'undefined') {
return false;
}
// if we are passed a data object with both local and external data, use external data as key
if(typeof data.local != 'undefined'
&& typeof data.local.statusnet_profile_url != 'undefined'
&& typeof data.external != 'undefined'
&& typeof data.external.statusnet_profile_url != 'undefined') {
var instanceUrlWithoutProtocol = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(data.external.statusnet_profile_url, data.external.screen_name);
var key = instanceUrlWithoutProtocol + '/' + data.external.screen_name;
var dataToStore = data;
}
// we can also get either local...
else if(typeof data.local != 'undefined' && typeof data.local.statusnet_profile_url != 'undefined' ) {
var instanceUrlWithoutProtocol = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(data.local.statusnet_profile_url, data.local.screen_name);
var key = instanceUrlWithoutProtocol + '/' + data.local.screen_name;
data.external = false;
var dataToStore = data;
}
// ...or external...
else if(typeof data.external != 'undefined' && typeof data.external.statusnet_profile_url != 'undefined' ) {
var instanceUrlWithoutProtocol = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(data.external.statusnet_profile_url, data.external.screen_name);
var key = instanceUrlWithoutProtocol + '/' + data.external.screen_name;
data.local = false;
var dataToStore = data;
}
// ...or an unspecified data object, in which case we check the avatar urls to see if it's local or external
else if (typeof data.statusnet_profile_url != 'undefined') {
var instanceUrlWithoutProtocol = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(data.statusnet_profile_url, data.screen_name);
var key = instanceUrlWithoutProtocol + '/' + data.screen_name;
var localOrExternal = detectLocalOrExternalUserObject(data);
// local
if(localOrExternal == 'local'){
var dataToStore = {local:data,external:false};
}
// external
else {
var dataToStore = {external:data,local:false};
}
}
else {
return false;
}
// store
if(typeof window.userArrayCache[key] == 'undefined') {
window.userArrayCache[key] = dataToStore;
window.userArrayCache[key].modified = Date.now();
// easy conversion between URI and statusnet_profile_url and the key we're using in window.userArrayCache
window.convertLocalIdToUserArrayCacheKey[parseInt(dataToStore.local.id, 10)] = key;
window.convertUriToUserArrayCacheKey[dataToStore.local.ostatus_uri] = key;
window.convertStatusnetProfileUrlToUserArrayCacheKey[dataToStore.local.statusnet_profile_url] = key;
}
else {
if(dataToStore.local) {
// keep old status if newer data doesn't have any
if(typeof dataToStore.local.status == 'undefined' && typeof window.userArrayCache[key].local.status != 'undefined') {
dataToStore.local.status = window.userArrayCache[key].local.status;
}
window.userArrayCache[key].local = dataToStore.local;
// easy conversion between URI and statusnet_profile_url and the key we're using in window.userArrayCache
window.convertLocalIdToUserArrayCacheKey[dataToStore.local.id] = key;
window.convertUriToUserArrayCacheKey[dataToStore.local.ostatus_uri] = key;
window.convertStatusnetProfileUrlToUserArrayCacheKey[dataToStore.local.statusnet_profile_url] = key;
}
if(dataToStore.external) {
window.userArrayCache[key].external = dataToStore.external;
// easy conversion between URI and the key we're using in window.userArrayCache
window.convertUriToUserArrayCacheKey[dataToStore.external.ostatus_uri] = key;
window.convertStatusnetProfileUrlToUserArrayCacheKey[dataToStore.external.statusnet_profile_url] = key;
}
// store the time when this record was modified
if(dataToStore.local || dataToStore.external) {
window.userArrayCache[key].modified = Date.now();
}
}
}
function userArrayCacheGetByLocalNickname(localNickname) {
if(localNickname.substring(0,1) == '@') {
localNickname = localNickname.substring(1);
}
if(typeof window.userArrayCache[window.siteRootDomain + '/' + localNickname] != 'undefined') {
return window.userArrayCache[window.siteRootDomain + '/' + localNickname];
}
else {
return false;
}
}
function userArrayCacheGetByProfileUrlAndNickname(profileUrl, nickname) {
var possibleLocalId = false;
if(nickname.substring(0,1) == '@') {
nickname = nickname.substring(1);
}
if(profileUrl.indexOf(window.siteInstanceURL + 'user/') == 0) {
possibleLocalId = parseInt(profileUrl.substring(window.siteInstanceURL.length+5),10);
}
// the url might match a known profile uri
if(typeof window.convertUriToUserArrayCacheKey[profileUrl] != 'undefined') {
if(typeof window.userArrayCache[window.convertUriToUserArrayCacheKey[profileUrl]] != 'undefined') {
return window.userArrayCache[window.convertUriToUserArrayCacheKey[profileUrl]];
}
}
// or the href attribute might match a known statusnet_profile_url
else if(typeof window.convertStatusnetProfileUrlToUserArrayCacheKey[profileUrl] != 'undefined') {
if(typeof window.userArrayCache[window.convertStatusnetProfileUrlToUserArrayCacheKey[profileUrl]] != 'undefined') {
return window.userArrayCache[window.convertStatusnetProfileUrlToUserArrayCacheKey[profileUrl]];
}
}
// or the local id might match a known id
else if(typeof window.convertLocalIdToUserArrayCacheKey[possibleLocalId] != 'undefined') {
if(typeof window.userArrayCache[window.convertLocalIdToUserArrayCacheKey[possibleLocalId]] != 'undefined') {
return window.userArrayCache[window.convertLocalIdToUserArrayCacheKey[possibleLocalId]];
}
}
// or we try to guess the instance url, and see if we have a match in our cache
else if(typeof window.userArrayCache[guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(profileUrl, nickname) + '/' + nickname] != 'undefined') {
return window.userArrayCache[guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(profileUrl, nickname) + '/' + nickname];
}
// we couldn't find any cached user array
else {
return false;
}
}
function userArrayCacheGetUserNicknameById(id) {
var possibleUserURI = window.siteInstanceURL + 'user/' + id;
var key = window.convertUriToUserArrayCacheKey[possibleUserURI];
if(typeof key != 'undefined') {
if(typeof window.userArrayCache[key] != 'undefined') {
return window.userArrayCache[key].local.screen_name;
}
}
return false;
}
/* ·
·
· Detect if the supplied user object is from the local server or external
·
· · · · · · · · · */
function detectLocalOrExternalUserObject(userObject) {
if(isLocalURL(userObject.profile_image_url) ) {
return 'local';
} else {
return 'external';
}
}
/* ·
·
· Guess instance's base installation url without protocol from a profile url
·
· · · · · · · · · */
function guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(profileUrl, nickname) {
// remove protocol
var guessedInstanceUrl = removeProtocolFromUrl(profileUrl)
// user/id-style profile urls
if(guessedInstanceUrl.indexOf('/user/') > -1 &&
$.isNumeric(guessedInstanceUrl.substring(guessedInstanceUrl.lastIndexOf('/user/')+6))) {
guessedInstanceUrl = guessedInstanceUrl.substring(0,guessedInstanceUrl.lastIndexOf('/user/'));
}
// nickname-style profile urls
else if(guessedInstanceUrl.substring(guessedInstanceUrl.lastIndexOf('/')+1) == nickname) {
guessedInstanceUrl = guessedInstanceUrl.substring(0,guessedInstanceUrl.lastIndexOf('/'));
}
// remove trailing "index.php" if the instance doesn't use mod_rewrite
if(guessedInstanceUrl.substring(guessedInstanceUrl.lastIndexOf('/')) == '/index.php') {
guessedInstanceUrl = guessedInstanceUrl.substring(0,guessedInstanceUrl.lastIndexOf('/'));
}
// there was a bug once that made some instances have multiple /:s in their url,
// so make sure there's no trailing /:s
while (guessedInstanceUrl.slice(-1) == '/') {
guessedInstanceUrl = guessedInstanceUrl.slice(0,-1);
}
// fix new mastodon style profile urls
if(guessedInstanceUrl.indexOf('/@') > -1) {
guessedInstanceUrl = guessedInstanceUrl.substring(0, guessedInstanceUrl.indexOf('/@'));
}
return guessedInstanceUrl;
}
/* ·
·
· Remove the protocol (e.g. "http://") from an URL
·
· · · · · · · · · */
function removeProtocolFromUrl(url) {
if(typeof url == 'undefined'
|| url === null
|| url === false
|| url == '') {
return '';
}
if(url.indexOf('://') == -1) {
return url;
}
return url.substring(url.indexOf('://')+3);
}
/* ·
·
· Get host from URL
·
· · · · · · · · · */
function getHost(url) {
var a = document.createElement('a');
a.href = url;
return a.hostname;
}
/* ·
·
· Is this url a link to my profile?
·
· · · · · · · · · */
function thisIsALinkToMyProfile(url) {
if(typeof url == 'undefined') {
return false;
}
if(!window.loggedIn) {
return false;
}
if(url.slice(-1) == '/') { // remove trailing '/'
url = url.slice(0,-1);
}
var urlWithoutProtocol = removeProtocolFromUrl(url);
if(removeProtocolFromUrl(window.loggedIn.statusnet_profile_url) == urlWithoutProtocol) {
return true;
}
var userIdUrlWithoutProtocol = removeProtocolFromUrl(window.siteInstanceURL) + 'user/' + window.loggedIn.id;
if(userIdUrlWithoutProtocol == urlWithoutProtocol) {
return true;
}
return false;
}
/* ·
·
· Iterates recursively through an API response in search for user data to cache
· If we find a "statusnet_profile_url" key we assume the parent is a user array/object
·
· · · · · · · · · · · · · */
function searchForUserDataToCache(obj) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
searchForUserDataToCache(obj[property]);
}
else if(typeof obj[property] == 'string' && property == 'statusnet_profile_url') {
userArrayCacheStore(obj);
}
}
}
}
/* ·
·
· Updates user data loaded into the stream with the latest data from the user array cache
· This function should therefor always be invoked _after_ searchForUserDataToCache()
·
· · · · · · · · · · · · · */
function updateUserDataInStream() {
var timeNow = Date.now();
$.each(window.userArrayCache,function(k,userArray){
// if the cache record was updated the latest second, we assume this is brand new info that we haven't
// updated the stream with
if(typeof userArray.local != 'undefined'
&& userArray.local !== false
&& typeof userArray.modified != 'undefined'
&& (timeNow-userArray.modified)<1000) {
// add/remove silenced class to stream items and profile cards
if(userArray.local.is_silenced === true) {
$('.stream-item[data-user-id=' + userArray.local.id + ']').addClass('silenced');
$('.profile-card .profile-header-inner[data-user-id=' + userArray.local.id + ']').addClass('silenced');
$('.user-menu-cog[data-user-id=' + userArray.local.id + ']').addClass('silenced');
}
else {
$('.stream-item[data-user-id=' + userArray.local.id + ']').removeClass('silenced')
$('.profile-card .profile-header-inner[data-user-id=' + userArray.local.id + ']').removeClass('silenced');
$('.user-menu-cog[data-user-id=' + userArray.local.id + ']').removeClass('silenced');
}
// add/remove sandboxed class to stream items and profile cards
if(userArray.local.is_sandboxed === true) {
$('.stream-item[data-user-id=' + userArray.local.id + ']').addClass('sandboxed');
$('.profile-card .profile-header-inner[data-user-id=' + userArray.local.id + ']').addClass('sandboxed');
$('.user-menu-cog[data-user-id=' + userArray.local.id + ']').addClass('sandboxed');
}
else {
$('.stream-item[data-user-id=' + userArray.local.id + ']').removeClass('sandboxed')
$('.profile-card .profile-header-inner[data-user-id=' + userArray.local.id + ']').removeClass('sandboxed');
$('.user-menu-cog[data-user-id=' + userArray.local.id + ']').removeClass('sandboxed');
}
// profile size avatars (notices, users)
$.each($('img.avatar.profile-size[data-user-id="' + userArray.local.id + '"]'),function(){
if($(this).attr('src') != userArray.local.profile_image_url_profile_size) {
$(this).attr('src',userArray.local.profile_image_url_profile_size);
}
});
// standard size avatars (notifications)
$.each($('img.avatar.standard-size[data-user-id="' + userArray.local.id + '"]'),function(){
if($(this).attr('src') != userArray.local.profile_image_url) {
$(this).attr('src',userArray.local.profile_image_url);
}
});
// full names
$.each($('strong.name[data-user-id="' + userArray.local.id + '"],\
.fullname[data-user-id="' + userArray.local.id + '"]'),function(){
if($(this).html() != userArray.local.name) {
$(this).html(userArray.local.name);
}
});
// user/screen names
$.each($('.screen-name[data-user-id="' + userArray.local.id + '"]'),function(){
if($(this).html().substring(1) != userArray.local.screen_name) {
$(this).html('@' + userArray.local.screen_name);
}
});
// profile urls
// try to find the last account group with this id, if the statusnet_profile_url seems to
// be changed we replace it wherever we can find it, even in list urls etc that starts with statusnet_profile_url
if(userArray.local.is_local === true && $('a.account-group[data-user-id="' + userArray.local.id + '"]').last().attr('href') != userArray.local.statusnet_profile_url) {
var oldStatusnetProfileURL = $('a.account-group[data-user-id="' + userArray.local.id + '"]').last().attr('href');
// all links with the exact statusnet_profile_url
$.each($('[href="' + oldStatusnetProfileURL + '"]'),function(){
$(this).attr('href',userArray.local.statusnet_profile_url);
});
// links starting with statusnet_profile_url
$.each($('[href*="' + oldStatusnetProfileURL + '/"]'),function(){
$(this).attr('href',$(this).attr('href').replace(oldStatusnetProfileURL + '/',userArray.local.statusnet_profile_url + '/'));
});
}
// cover photos
$.each($('.profile-header-inner[data-user-id="' + userArray.local.id + '"]'),function(){
if($(this).css('background-image') != 'url("' + userArray.local.cover_photo + '")' && userArray.local.cover_photo != false) {
$(this).css('background-image','url("' + userArray.local.cover_photo + '")');
}
});
// the window.following object might need updating also
if(typeof window.following != 'undefined' && typeof window.following[userArray.local.id] != 'undefined') {
if(window.following[userArray.local.id].avatar != userArray.local.profile_image_url) {
window.following[userArray.local.id].avatar = userArray.local.profile_image_url;
}
if(window.following[userArray.local.id].name != userArray.local.name) {
window.following[userArray.local.id].name = userArray.local.name;
}
if(window.following[userArray.local.id].username != userArray.local.screen_name) {
window.following[userArray.local.id].username = userArray.local.screen_name;
}
}
}
});
}
/* ·
·
· Iterates recursively through an API response in search for updated notice data
· If we find a "repeated" key we assume the parent is a notice object (chosen arbitrary)
·
· · · · · · · · · · · · · */
window.knownDeletedNotices = new Object();
function searchForUpdatedNoticeData(obj) {
var streamItemsUpdated = false;
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
searchForUpdatedNoticeData(obj[property]);
}
else if(typeof obj[property] == 'boolean' && property == 'repeated') {
var streamItemFoundInFeed = $('.stream-item[data-conversation-id][data-quitter-id="' + obj.id + '"]'); // data-conversation-id identifies it as a notice, not a user or something
// if this is a special qvitter-delete-notice activity notice it means we try to hide
// the deleted notice from our stream
// the uri is in the obj.text var, between the double curly brackets
if(typeof obj.qvitter_delete_notice != 'undefined' && obj.qvitter_delete_notice == true) {
var uriToHide = obj.text.substring(obj.text.indexOf('{{')+2,obj.text.indexOf('}}'));
window.knownDeletedNotices[uriToHide] = true;
var streamItemToHide = $('.stream-item[data-uri="' + uriToHide + '"]');
slideUpAndRemoveStreamItem(streamItemToHide);
streamItemsUpdated = true;
}
// if this is not a delete notice it means the notice exists and is not deleted,
// correct any notices that are marked as unrepeated, they might have
// been marked like that by mistake (i.e. a bug...)
else if(streamItemFoundInFeed.hasClass('unrepeated')) {
streamItemFoundInFeed.removeClass('unrepeated always-hidden');
streamItemsUpdated = true;
}
// ordinary notices
else if(streamItemFoundInFeed.length>0) {
var queetFoundInFeed = streamItemFoundInFeed.children('.queet');
var queetID = streamItemFoundInFeed.attr('data-quitter-id');
// sometimes activity notices don't get the is_activity flag set to true
// maybe because they were in the process of being saved when
// we first got them
if(obj.is_post_verb === false) {
streamItemFoundInFeed.addClass('activity always-hidden');
streamItemsUpdated = true;
}
// update the avatar row if the queet is expanded and the numbers are not the same
if(streamItemFoundInFeed.hasClass('expanded')) {
var oldFavNum = parseInt(queetFoundInFeed.find('.action-fav-num').text(),10);
var oldRQNum = parseInt(queetFoundInFeed.find('.action-rq-num').text(),10);
if(oldFavNum != obj.fave_num || oldRQNum != obj.repeat_num) {
getFavsAndRequeetsForQueet(streamItemFoundInFeed, queetID);
}
}
// attachments might have been added/changed/have had time to be processed
if(queetFoundInFeed.children('script.attachment-json').text() != JSON.stringify(obj.attachments)) {
if(queetFoundInFeed.children('script.attachment-json').length == 0) {
queetFoundInFeed.prepend('<script class="attachment-json" type="application/json">' + JSON.stringify(obj.attachments) + '</script>');
}
else {
queetFoundInFeed.children('script.attachment-json').text(JSON.stringify(obj.attachments));
}
var attachmentsHTMLBuild = buildAttachmentHTML(obj.attachments);
var thumbsIsHidden = false;
if(queetFoundInFeed.find('.queet-thumbs').hasClass('hide-thumbs')) {
var thumbsIsHidden = true;
}
queetFoundInFeed.find('.queet-thumbs').remove();
queetFoundInFeed.find('.oembed-data').remove();
placeQuotedNoticesInQueetText(attachmentsHTMLBuild.quotedNotices,queetFoundInFeed.find('.queet-text'));
// we might want to hide urls (rendered as attachments) in the queet text
$.each(queetFoundInFeed.find('.queet-text').find('a'),function(){
if(attachmentsHTMLBuild.urlsToHide.indexOf($(this).text()) > -1) {
$(this).removeAttr('style'); // temporary fix
$(this).addClass('hidden-embedded-link-in-queet-text');
}
});
queetFoundInFeed.find('.queet-text').after(attachmentsHTMLBuild.html);
if(thumbsIsHidden) {
queetFoundInFeed.find('.queet-thumbs').addClass('hide-thumbs');
}
streamItemsUpdated = true;
}
// attentions might have been added to a notice
if(queetFoundInFeed.children('script.attentions-json').text() != JSON.stringify(obj.attentions)) {
if(queetFoundInFeed.children('script.attentions-json').length == 0) {
queetFoundInFeed.prepend('<script class="attentions-json" type="application/json">' + JSON.stringify(obj.attentions) + '</script>');
}
else {
queetFoundInFeed.children('script.attentions-json').text(JSON.stringify(obj.attentions));
}
}
// set favorite data
queetFoundInFeed.find('.action-fav-num').attr('data-fav-num',obj.fave_num);
queetFoundInFeed.find('.action-fav-num').html(obj.fave_num);
if(obj.favorited) {
streamItemFoundInFeed.addClass('favorited');
queetFoundInFeed.find('.action-fav-container').children('.with-icn').addClass('done');
queetFoundInFeed.find('.action-fav-container').find('.icon.sm-fav').attr('data-tooltip',window.sL.favoritedVerb);
streamItemsUpdated = true;
}
else {
streamItemFoundInFeed.removeClass('favorited');
queetFoundInFeed.find('.action-fav-container').children('.with-icn').removeClass('done');
queetFoundInFeed.find('.action-fav-container').find('.icon.sm-fav').attr('data-tooltip',window.sL.favoriteVerb);
streamItemsUpdated = true;
}
// set repeat data
queetFoundInFeed.find('.action-rq-num').attr('data-rq-num',obj.repeat_num);
queetFoundInFeed.find('.action-rq-num').html(obj.repeat_num);
if(obj.repeated) {
streamItemFoundInFeed.addClass('requeeted');
queetFoundInFeed.find('.action-rt-container').children('.with-icn').addClass('done');
queetFoundInFeed.find('.action-rt-container').find('.icon.sm-rt').attr('data-tooltip',window.sL.requeetedVerb);
streamItemFoundInFeed.attr('data-requeeted-by-me-id',obj.repeated_id);
streamItemsUpdated = true;
}
else {
streamItemFoundInFeed.removeClass('requeeted');
queetFoundInFeed.find('.action-rt-container').children('.with-icn').removeClass('done');
queetFoundInFeed.find('.action-rt-container').find('.icon.sm-rt').attr('data-tooltip',window.sL.requeetVerb);
streamItemFoundInFeed.removeAttr('data-requeeted-by-me-id');
streamItemsUpdated = true;
}
}
}
}
}
if(streamItemsUpdated) {
// TODO, create a queue that runs with setInterval instead, say every 5 s,
// that way we can run rememberStreamStateInLocalStorage() in the background,
// and don't slow down stream change etc
// rememberStreamStateInLocalStorage();
}
}
/* ·
·
· Removes a deleted stream item from the feed gracefully, if not already hidden
·
· · · · · · · · · */
function slideUpAndRemoveStreamItem(streamItem,callback) {
if(streamItem.length>0 && !streamItem.hasClass('always-hidden')) {
streamItem.animate({opacity:'0.2'},1000,'linear',function(){
$(this).css('height',$(this).height() + 'px');
$(this).animate({height:'0px'},500,'linear',function(){
$(this).addClass('deleted always-hidden');
rememberStreamStateInLocalStorage();
if(typeof callback == 'function') {
callback();
}
});
});
}
}
/* ·
·
· Store the current stream's state (html) in localStorage (if we're logged in)
·
· · · · · · · · · */
function rememberStreamStateInLocalStorage() {
if(typeof window.currentStreamObject != 'undefined') {
// don't store expanded content, only store profile card and the top 20 visible stream-items
var firstTwentyVisibleHTML = '';
var i = 0;
$.each($('#feed-body').children('.stream-item'),function(k,streamItem){
firstTwentyVisibleHTML += $(streamItem).outerHTML();
if(!$(streamItem).hasClass('always-hidden')) {
i++;
}
if(i>20) {
return false;
}
});
var feed = $('<div/>').append(firstTwentyVisibleHTML);
// we add some of these things again when the notices are fetched from the cache
cleanStreamItemsFromClassesAndConversationElements(feed);
var feedHtml = feed.html();
var profileCardHtml = $('#feed').siblings('.profile-card').outerHTML();
var streamData = {
card: profileCardHtml,
feed: feedHtml
};
localStorageObjectCache_STORE('streamState',window.currentStreamObject.path, streamData);
}
}
/* ·
·
· Clean stream items from classes and conversation elements,
· to use e.g. for caching and including in popup footers
·
· @param streamItems: jQuery object with stream items as children
·
· · · · · · · · · */
function cleanStreamItemsFromClassesAndConversationElements(streamItems) {
streamItems.children('.stream-item').removeClass('profile-blocked-by-me');
streamItems.children('.stream-item').children('.queet').removeAttr('data-tooltip'); // can contain tooltip about blocked user
streamItems.find('.temp-post').remove();
streamItems.children('.stream-item').removeClass('not-seen');
streamItems.children('.stream-item').removeClass('hidden-repeat'); // this means we need hide repeats when adding cached notices to feed later
streamItems.children('.stream-item').removeClass('selected-by-keyboard');
streamItems.find('.dropdown-menu').remove();
streamItems.find('.stream-item').removeClass('expanded').removeClass('next-expanded').removeClass('hidden').removeClass('collapsing').addClass('visible');
streamItems.children('.stream-item').each(function() {
cleanUpAfterCollapseQueet($(this));
});
}
/* ·
·
· Hide all instances (repeats) of a notice but the first/oldest one
·
· @param streamItems: jQuery object with stream items as children
·
· · · · · · · · · */
function hideAllButOldestInstanceOfStreamItem(streamItemContainer) {
streamItemContainer.children('.stream-item').each(function(){
// if this stream item have siblings _after_ it, with the same id, hide it!
if($(this).nextAll('.stream-item[data-quitter-id="' + $(this).attr('data-quitter-id') + '"]').length > 0) {
$(this).addClass('hidden-repeat');
}
});
return streamItemContainer;
}
/* ·
·
· Gets the full unshortened HTML for a queet
·
· · · · · · · · · */
function getFullUnshortenedHtmlForQueet(streamItem, cacheOnly) {
if(typeof cacheOnly == 'undefined') {
var cacheOnly = false;
}
var queet = streamItem.children('.queet');
var queetId = streamItem.attr('data-quitter-id');
var attachmentMore = queet.find('span.attachment.more');
// only if actually shortened
if(attachmentMore.length>0
&& queet.children('script.attachment-json').length > 0
&& queet.children('script.attachment-json').text() != 'undefined') {
// first try localstorage cache
var cacheData = localStorageObjectCache_GET('fullQueetHtml',queetId);
if(cacheData) {
queet.find('.queet-text').html(cacheData);
queet.outerHTML(detectRTL(queet.outerHTML()));
}
// then try static html file attachment, that we should have in the attachment-json script element
else if(cacheOnly === false){
var attachmentId = attachmentMore.attr('data-attachment-id');
$.each(JSON.parse(queet.children('script.attachment-json').text()), function(k,attachment) {
if(attachment.id == attachmentId) {
$.get(attachment.url,function(data){
if(data) {
// get body and store in localStorage
var bodyHtml = $('<html/>').html(data).find('body').html();
localStorageObjectCache_STORE('fullQueetHtml',queetId,bodyHtml);
queet.find('.queet-text').html($.trim(bodyHtml));
queet.outerHTML(detectRTL(queet.outerHTML()));
}
});
return false;
}
});
}
}
}
/* ·
·
· Appends a user to the array containing the mentions suggestions to show when typing a notice
·
· · · · · · · · · */
function appendUserToMentionsSuggestionsArray(user) {
if(typeof window.following[user.id] == 'undefined') {
// in the window.following array, we use "false" as url if it's a user from this instance
if(user.is_local) {
var url = false;
}
else {
var url = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(user.statusnet_profile_url,user.screen_name);
}
var userToAdd = {
avatar: user.profile_image_url,
id: user.id,
name: user.name,
url: url,
username: user.screen_name
};
window.following[user.id] = userToAdd;
}
}
/* ·
·
· Is a profile pref in the qvitter namespace enabled?
·
· · · · · · · · · */
function isQvitterProfilePrefEnabled(topic) {
if(typeof window.qvitterProfilePrefs != 'undefined' && typeof window.qvitterProfilePrefs[topic] != 'undefined'
&& window.qvitterProfilePrefs[topic] !== null
&& window.qvitterProfilePrefs[topic] != ''
&& window.qvitterProfilePrefs[topic] !== false
&& window.qvitterProfilePrefs[topic] != 0
&& window.qvitterProfilePrefs[topic] != '0') {
return true;
}
return false;
}
/* ·
·
· Is this user muted?
·
· · · · · · · · · */
function isUserMuted(userID) {
if(isQvitterProfilePrefEnabled('mute:' + userID)) {
return true;
}
else {
return false;
}
}
/* ·
·
· Display unread notifications
·
· · · · · · · · · */
function displayOrHideUnreadNotifications(notifications) {
var data = $.parseJSON(notifications);
var totNotif = 0;
if(data !== null && typeof data != 'undefined') {
$.each(data,function(k,v){
totNotif = totNotif + parseInt(v,10);
});
}
if(window.currentStreamObject.name == 'notifications') {
var hiddenNotifications = $('#feed-body').find('.stream-item.hidden:not(.always-hidden)').length;
if(hiddenNotifications>0) {
totNotif = totNotif + hiddenNotifications;
}
}
if(totNotif>0) {
$('#unseen-notifications').html(totNotif);
document.title = '(' + totNotif + ') ' + window.siteTitle; // update html page title
$('#unseen-notifications').show();
}
else {
$('#unseen-notifications').hide();
document.title = window.siteTitle;
}
}
/* ·
·
· Removes HTML special chars recursively from strings in objects
· with exceptions: "statusnet_html" found in notices, which we assume
· gnusocial already stripped from xss, and the "source" which should be
· html rendered by gnusocial itself and not open for attacks
·
· @param obj: the object to search and replace in
·
· · · · · · · · · · · · · */
function iterateRecursiveReplaceHtmlSpecialChars(obj) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterateRecursiveReplaceHtmlSpecialChars(obj[property]);
}
else if(typeof obj[property] == 'string'
&& property != 'statusnet_html'
&& property != 'oembedHTML' // we trust this to be cleaned server side
&& property != 'source') {
obj[property] = replaceHtmlSpecialChars(obj[property]);
}
}
}
return obj;
}
function replaceHtmlSpecialChars(text) {
// don't do anything if the text is undefined
if(typeof text == 'undefined') {
return text;
}
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
/* ·
·
· Checks if register form is valid
·
· @returns true or false
·
· · · · · · · · · */
function validateRegisterForm(o) {
var nickname = o.find('#signup-user-nickname-step2');
var fullname = o.find('#signup-user-name-step2');
var email = o.find('#signup-user-email-step2');
var homepage = o.find('#signup-user-homepage-step2');
var bio = o.find('#signup-user-bio-step2');
var loc = o.find('#signup-user-location-step2');
var password1 = o.find('#signup-user-password1-step2');
var password2 = o.find('#signup-user-password2-step2');
var passwords = o.find('#signup-user-password1-step2,#signup-user-password2-step2');
var allFieldsValid = true;
if(nickname.val().length>1 && /^[a-zA-Z0-9]+$/.test(nickname.val())) {
nickname.removeClass('invalid'); } else { nickname.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(fullname.val().length < 255) {
fullname.removeClass('invalid'); } else { fullname.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(validEmail(email.val())) {
email.removeClass('invalid'); } else { email.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if($.trim(homepage.val()).length==0 || /^(ftp|http|https):\/\/[^ "]+$/.test(homepage.val())) {
homepage.removeClass('invalid'); } else { homepage.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(bio.val().length < 140) {
bio.removeClass('invalid'); } else { bio.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(loc.val().length < 255) {
loc.removeClass('invalid'); } else { loc.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(password1.val().length>5 && password2.val().length>5 && password1.val() == password2.val()) {
passwords.removeClass('invalid'); } else { passwords.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
return allFieldsValid;
}
function validEmail(email) {
if(/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(email)) {
return true;
}
else {
return false;
}
}
/* ·
·
· Checks if edit profile form is valid
·
· @returns true or false
·
· · · · · · · · · */
function validateEditProfileForm(o) {
var fullname = o.find('input.fullname');
var homepage = o.find('input.url');
var bio = o.find('textarea.bio');
var loc = o.find('input.location');
var allFieldsValid = true;
if(fullname.val().length < 255) {
fullname.removeClass('invalid'); } else { fullname.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if($.trim(homepage.val()).length==0 || /^(ftp|http|https):\/\/[^ "]+$/.test(homepage.val())) {
homepage.removeClass('invalid'); } else { homepage.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(bio.val().length < 140) {
bio.removeClass('invalid'); } else { bio.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
if(loc.val().length < 255) {
loc.removeClass('invalid'); } else { loc.addClass('invalid'); if(allFieldsValid)allFieldsValid=false; }
return allFieldsValid;
}
/* ·
·
· Validate a hex color and add # if missing
·
· @returns hex color with # or false
·
· · · · · · · · · */
function isValidHexColor(maybeValidHexColor) {
if(maybeValidHexColor.substring(0,1) != '#') {
maybeValidHexColor = '#' + maybeValidHexColor;
}
var validHexColor = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(maybeValidHexColor);
if(validHexColor) {
validHexColor = maybeValidHexColor;
}
return validHexColor;
}
/* ·
·
· Change profile design
·
· @param obj: user object that should contain one, two or all of backgroundimage, backgroundcolor and linkcolor
· false or empty string unsets the parameter to default
·
· · · · · · · · · */
function changeDesign(obj) {
// if we're logged out and this is the front page, we use the default design
if(!window.loggedIn &&
(window.currentStreamObject.name == 'public timeline' || window.currentStreamObject.name == 'public and external timeline')) {
obj.backgroundimage = window.fullUrlToThisQvitterApp + window.siteBackground;
obj.backgroundcolor = window.defaultBackgroundColor;
obj.linkcolor = window.defaultLinkColor;
}
// if no object is defined, abort
if(typeof obj == 'undefined') {
return false;
}
// remember the design for this stream
if(typeof window.oldStreamsDesigns[window.currentStreamObject.nickname] == 'undefined') {
window.oldStreamsDesigns[window.currentStreamObject.nickname] = new Object();
}
// change design elements
if(typeof obj.backgroundimage != 'undefined') {
if(obj.backgroundimage === false || obj.backgroundimage == '') {
$('body').css('background-image','url(\'\')');
}
else if(obj.backgroundimage.length > 4) {
$('body').css('background-image','url(\'' + obj.backgroundimage + '\')');
}
window.oldStreamsDesigns[window.currentStreamObject.nickname].backgroundimage = obj.backgroundimage;
}
if(typeof obj.backgroundcolor != 'undefined') {
if(obj.backgroundcolor === false || obj.backgroundcolor == '') {
obj.backgroundcolor = window.defaultBackgroundColor;
}
changeBackgroundColor(obj.backgroundcolor);
window.oldStreamsDesigns[window.currentStreamObject.nickname].backgroundcolor = obj.backgroundcolor;
}
if(typeof obj.linkcolor != 'undefined') {
if(obj.linkcolor === false || obj.linkcolor == '') {
obj.linkcolor = window.defaultLinkColor;
}
changeLinkColor(obj.linkcolor);
window.oldStreamsDesigns[window.currentStreamObject.nickname].linkcolor = obj.linkcolor;
}
}
// create object to remember designs on page load
window.oldStreamsDesigns = new Object();
/* ·
·
· Change background color
·
· @param newLinkColor: hex value with or without #
·
· · · · · · · · · */
function changeBackgroundColor(newBackgroundColor) {
// check hex value
var validHexColor = isValidHexColor(newBackgroundColor);
if(!validHexColor) {
console.log('invalid hex value for backgroundcolor: ' + newBackgroundColor);
return false;
}
$('body').css('background-color',validHexColor);
}
/* ·
·
· Change link color
·
· @param newLinkColor: hex value with or without #
·
· · · · · · · · · */
function changeLinkColor(newLinkColor) {
// check hex value
var validHexColor = isValidHexColor(newLinkColor);
if(!validHexColor) {
console.log('invalid hex value for linkcolor: ' + newLinkColor);
return false;
}
var lighterColor08 = blendRGBColors(hex2rgb(validHexColor),'rgb(255,255,255)',0.8);
var lighterColor06 = blendRGBColors(hex2rgb(validHexColor),'rgb(255,255,255)',0.6)
var headStyle = $('#dynamic-styles').children('style');
var headStyleText = headStyle.text();
headStyleText = replaceFromStringEndToStringStart(headStyleText,'/*COLORSTART*/','/*COLOREND*/',validHexColor);
headStyleText = replaceFromStringEndToStringStart(headStyleText,'/*BACKGROUNDCOLORSTART*/','/*BACKGROUNDCOLOREND*/',validHexColor);
headStyleText = replaceFromStringEndToStringStart(headStyleText,'/*BORDERCOLORSTART*/','/*BORDERCOLOREND*/',validHexColor);
headStyleText = replaceFromStringEndToStringStart(headStyleText,'/*LIGHTERBACKGROUNDCOLORSTART*/','/*LIGHTERBACKGROUNDCOLOREND*/',lighterColor08);
headStyleText = replaceFromStringEndToStringStart(headStyleText,'/*LIGHTERBORDERCOLORSTART*/','/*LIGHTERBORDERCOLOREND*/',lighterColor06);
headStyleText = replaceFromStringEndToStringStart(headStyleText,'/*LIGHTERBORDERBOTTOMCOLORSTART*/','/*LIGHTERBORDERBOTTOMCOLOREND*/',lighterColor08);
headStyle.text(headStyleText);
}
function replaceFromStringEndToStringStart(string,fromStringEnd,toStringStart,withString) {
return string.substring(0,string.indexOf(fromStringEnd)+fromStringEnd.length) + withString + string.substring(string.indexOf(toStringStart));
}
function blendRGBColors(c0, c1, p) {
var f=c0.split(","),t=c1.split(","),R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);
return "rgb("+(Math.round((parseInt(t[0].slice(4))-R)*p)+R)+","+(Math.round((parseInt(t[1])-G)*p)+G)+","+(Math.round((parseInt(t[2])-B)*p)+B)+")";
}
function hex2rgb(hexStr){
// note: hexStr should be #rrggbb
var hex = parseInt(hexStr.substring(1), 16);
var r = (hex & 0xff0000) >> 16;
var g = (hex & 0x00ff00) >> 8;
var b = hex & 0x0000ff;
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
/* ·
·
· Right-to-left language detection <o
· (//
· @param s: the stream-item to detect rtl in
·
· @return a stream-item that might have rtl-class added
·
· · · · · · · · · */
function detectRTL(s) {
var $streamItem = $('<div>').append(s);
var $queetText = $('<div>').append($streamItem.find('.queet-text').html()); // create an jquery object
var $a = $queetText.find('a'); $a.remove(); // remove links
var $vcard = $queetText.find('.vcard'); $vcard.remove(); // remove users, groups
var $hcard = $queetText.find('.h-card'); $hcard.remove(); // remove users, groups
var $tag = $queetText.find('.tag'); $tag.remove(); // remove tags
if($queetText.find('.rtl').length>0) { $queetText.html($queetText.find('.rtl').html()); } // remove rtl container if there is one
// remove chars we're not interested in
$queetText.html($queetText.html().replace(/\@/gi,'').replace(/\#/gi,'').replace(/\!/gi,'').replace(/\(/gi,'').replace(/\)/gi,'').replace(/\:D/gi,'').replace(/D\:/gi,'').replace(/\:/gi,'').replace(/\-/gi,'').replace(/\s/gi, ''));
// count ltr and rtl chars
var ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
rtlDirCheck = new RegExp('^[^'+ltrChars+']*['+rtlChars+']'),
RTLnum = 0,
LTRnum = 0,
RTLorLTR = $queetText.html();
for (var i = 0, len = RTLorLTR.length; i < len; i++) {
if(rtlDirCheck.test(RTLorLTR[i])) { RTLnum++; }
else { LTRnum++; }
}
// if there are more rtl chars than ltr
// or if no chars (that we are interested, but body is set to rtl)
if(RTLnum > LTRnum
|| ($queetText.html().length==0 && $('body').hasClass('rtl'))) {
$streamItem.children('.stream-item').children('.queet').addClass('rtl');
}
else {
// for ltr languages we move @, ! and # to inside (only because it looks better)
prependCharIfItDoesntAlreadyExist($streamItem.find('.queet-text').find('.h-card.mention'),'@');
prependCharIfItDoesntAlreadyExist($streamItem.find('.queet-text').find('.h-card.group'),'!');
prependCharIfItDoesntAlreadyExist($streamItem.find('.queet-text').find('.vcard .fn.nickname:not(.group)'),'@'); // very old style
prependCharIfItDoesntAlreadyExist($streamItem.find('.queet-text').find('.vcard .nickname.mention:not(.fn)'),'@'); // old style
prependCharIfItDoesntAlreadyExist($streamItem.find('.queet-text').find('.vcard .nickname.group'),'!'); // old style
prependCharIfItDoesntAlreadyExist($streamItem.find('.queet-text').find('a[rel="tag"]'),'#');
}
// we remove @, ! and #, they are added as pseudo elements, or have been moved to the inside
return $streamItem.html().replace(/@<a/gi,'<a').replace(/!<a/gi,'<a').replace(/@<span class="vcard">/gi,'<span class="vcard">').replace(/!<span class="vcard">/gi,'<span class="vcard">').replace(/#<span class="tag">/gi,'<span class="tag">');
}
function prependCharIfItDoesntAlreadyExist(jQueryObject,char) {
if(jQueryObject.text().substring(0,1) != char) {
jQueryObject.prepend(char);
}
}
/* ·
·
· Takes twitter style dates and converts them
·
· @param tdate: date in the form of e.g. 'Mon Aug 05 16:30:22 +0200 2013'
·
· @return user friendly dates ..M_
· W
· Needs global language object window.sL to be populated
·
· · · · · · · · · · · · · */
function parseTwitterDate(tdate) {
var month_names = new Array ();
month_names[month_names.length] = window.sL.shortmonthsJanuary;
month_names[month_names.length] = window.sL.shortmonthsFebruary
month_names[month_names.length] = window.sL.shortmonthsMars
month_names[month_names.length] = window.sL.shortmonthsApril
month_names[month_names.length] = window.sL.shortmonthsMay
month_names[month_names.length] = window.sL.shortmonthsJune
month_names[month_names.length] = window.sL.shortmonthsJuly
month_names[month_names.length] = window.sL.shortmonthsAugust
month_names[month_names.length] = window.sL.shortmonthsSeptember
month_names[month_names.length] = window.sL.shortmonthsOctober
month_names[month_names.length] = window.sL.shortmonthsNovember
month_names[month_names.length] = window.sL.shortmonthsDecember
var system_date = parseDate(tdate);
var user_date = new Date();
var diff = Math.floor((user_date - system_date) / 1000);
if (diff <= 10) {return window.sL.now;}
if (diff < 60) {return window.sL.shortDateFormatSeconds.replace('{seconds}',Math.round(diff/10)*10);}
if (diff <= 3540) {return window.sL.shortDateFormatMinutes.replace('{minutes}',Math.round(diff / 60));}
if (diff <= 86400) {return window.sL.shortDateFormatHours.replace('{hours}',Math.round(diff / 3600));}
if (diff <= 31536000) {return window.sL.shortDateFormatDate.replace('{day}',system_date.getDate()).replace('{month}',month_names[system_date.getMonth()]);}
if (diff > 31536000) {return window.sL.shortDateFormatDateAndY.replace('{day}',system_date.getDate()).replace('{month}',month_names[system_date.getMonth()]).replace('{year}',system_date.getFullYear());}
return system_date;
}
function parseTwitterLongDate(tdate) {
var month_names = new Array ();
month_names[month_names.length] = window.sL.longmonthsJanuary;
month_names[month_names.length] = window.sL.longmonthsFebruary
month_names[month_names.length] = window.sL.longmonthsMars
month_names[month_names.length] = window.sL.longmonthsApril
month_names[month_names.length] = window.sL.longmonthsMay
month_names[month_names.length] = window.sL.longmonthsJune
month_names[month_names.length] = window.sL.longmonthsJuly
month_names[month_names.length] = window.sL.longmonthsAugust
month_names[month_names.length] = window.sL.longmonthsSeptember
month_names[month_names.length] = window.sL.longmonthsOctober
month_names[month_names.length] = window.sL.longmonthsNovember
month_names[month_names.length] = window.sL.longmonthsDecember
var system_date = parseDate(tdate);
var hours = system_date.getHours();
var minutes = ('0'+system_date.getMinutes()).slice(-2);
var ampm = hours >= 12 ? 'pm' : 'am';
var time24hours = hours + ':' + minutes;
var time12hours = hours % 12;
time12hours = time12hours ? time12hours : 12; // the hour '0' should be '12'
if(ampm == 'am') { time12hours = window.sL.time12am.replace('{time}',time12hours + ':' + minutes);}
else { time12hours = window.sL.time12pm.replace('{time}',time12hours + ':' + minutes); }
return window.sL.longDateFormat.replace('{time24}',time24hours).replace('{hours}',hours).replace('{minutes}',minutes).replace('{time12}',time12hours).replace('{day}',system_date.getDate()).replace('{month}',month_names[system_date.getMonth()]).replace('{year}',system_date.getFullYear());
}
function timestampToTwitterDate(timestamp) {
var a = new Date(timestamp*1000);
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
var day = days[a.getUTCDay()];
var year = a.getUTCFullYear();
var month = months[a.getUTCMonth()];
var date = (a.getUTCDate()<10?'0':'')+a.getUTCDate();
var hour = (a.getUTCHours()<10?'0':'')+a.getUTCHours();
var min = (a.getUTCMinutes()<10?'0':'')+a.getUTCMinutes();
var sec = (a.getUTCSeconds()<10?'0':'')+a.getUTCSeconds();
return day+' '+month+' '+date+' '+hour+':'+min+':'+sec+' +0000 '+year;
}
function parseDate(str) {
if(typeof str != 'undefined') {
var v=str.split(' ');
return new Date(Date.parse(v[1]+" "+v[2]+", "+v[5]+" "+v[3]+" "+v[4]));
}
}
/* ·
·
· If we want to make sure we have empty arrays, not empty objects
·
· · · · · · · · · · */
function convertEmptyObjectToEmptyArray(data) {
// empty object? return empty array instead...
if($.isEmptyObject(data)) {
return [];
}
// leave data unchanged if we don't recognize it
else {
return data;
}
}
/* ·
·
· Functions to show and remove the spinner
·
· · · · · · · · · · · · */
function display_spinner(parent) {
if($('.loader').length<1) {
if(typeof parent == 'undefined') {
$('.global-nav').removeClass('show-logo');
var parent = 'body';
}
$(parent).prepend('\
<div class="loader">\
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\
width="40px" height="40px" viewBox="0 0 40 40" enable-background="new 0 0 40 40" xml:space="preserve">\
<path opacity="0.2" enable-background="new " d="M20.201,8.503c-6.413,0-11.612,5.199-11.612,11.612s5.199,11.611,11.612,11.611\
c6.412,0,11.611-5.198,11.611-11.611S26.613,8.503,20.201,8.503z M20.201,29.153c-4.992,0-9.039-4.046-9.039-9.038\
s4.047-9.039,9.039-9.039c4.991,0,9.038,4.047,9.038,9.039S25.192,29.153,20.201,29.153z"/>\
<path d="M24.717,12.293l1.285-2.227c-1.708-0.988-3.686-1.563-5.801-1.563l0,0v2.573l0,0C21.848,11.076,23.386,11.524,24.717,12.293 z">\
<animateTransform attributeType="xml"\
attributeName="transform"\
type="rotate"\
from="0 20 20"\
to="360 20 20"\
dur="1s"\
repeatCount="indefinite"/>\
</path>\
</svg>\
</div>\
');
}
}
function remove_spinner() {
$('.loader').remove();
$('.global-nav').addClass('show-logo');
}
/* ·
·
· Converts ...-attachment-links to spans
·
· (Attachments are loaded when queets expand)
·
· · · · · · · · · · · · · · · · · */
function convertAttachmentMoreHref() {
$('a.attachment.more').each(function() {
if(typeof $(this).attr('href') != 'undefined') {
var attachment_href = $(this).attr('href');
var attachment_id = attachment_href.substr((~-attachment_href.lastIndexOf("/") >>> 0) + 2);
if(attachment_id.length>0) {
$(this).replaceWith($('<span class="attachment more" data-attachment-id="' + attachment_id + '">…</span>'));
}
}
});
}
/* ·
·
· Saves the user's bookmarks to the server
·
· · · · · · · · · · · · · */
function saveAllBookmarks() {
var i=0;
var bookmarkContainer = new Object();
$.each($('#bookmark-container .stream-selection'), function(key,obj) {
bookmarkContainer[i] = new Object();
bookmarkContainer[i].dataStreamHref = $(obj).attr('href');
bookmarkContainer[i].dataStreamHeader = $(obj).text();
i++;
});
postUpdateBookmarks(bookmarkContainer);
$('#bookmark-container').sortable({delay: 100});
$('#bookmark-container').disableSelection();
}
/* ·
·
· Append all bookmarks to the bookmark container
·
· · · · · · · · · · · · · */
function appendAllBookmarks(bookmarkContainer) {
if(typeof bookmarkContainer != 'undefined' && bookmarkContainer) {
$('#bookmark-container').html('');
var bookmarkContainerParsed = JSON.parse(bookmarkContainer);
$.each(bookmarkContainerParsed, function(key,obj) {
$('#bookmark-container').append('<a class="stream-selection" href="' + obj.dataStreamHref + '">' + obj.dataStreamHeader + '<i class="chev-right" data-tooltip="' + window.sL.tooltipRemoveBookmark + '"></i></a>');
});
}
$('#bookmark-container').sortable({delay: 100});
$('#bookmark-container').disableSelection();
}
/* ·
·
· Change the header of an item in the history container by href attribute
·
· · · · · · · · · · · · · */
function updateHistoryContainerItemByHref(href, streamHeader) {
$('#history-container .stream-selection[href="' + href + '"]').html(streamHeader + '<i class="chev-right" data-tooltip="' + window.sL.tooltipBookmarkStream + '"></i>');
updateHistoryLocalStorage();
}
/* ·
·
· Remove items in the history container by href attribute
·
· · · · · · · · · · · · · */
function removeHistoryContainerItemByHref(href) {
$('#history-container .stream-selection[href="' + href + '"]').remove();
updateHistoryLocalStorage();
}
/* ·
·
· Updates the browsing history local storage
·
· · · · · · · · · · · · · */
function updateHistoryLocalStorage() {
if(localStorageIsEnabled()) {
var i=0;
var historyContainer = new Object();
$.each($('#history-container .stream-selection'), function(key,obj) {
historyContainer[i] = new Object();
historyContainer[i].dataStreamHref = $(obj).attr('href');
historyContainer[i].dataStreamHeader = $(obj).text();
i++;
});
localStorageObjectCache_STORE('browsingHistory', window.loggedIn.screen_name,historyContainer);
if($('#history-container .stream-selection').length==0) {
$('#history-container').css('display','none');
}
else {
$('#history-container').css('display','block');
}
}
}
/* ·
·
· Loads history from local storage to menu
·
· · · · · · · · · · · · · */
function loadHistoryFromLocalStorage() {
if(localStorageIsEnabled()) {
var cacheData = localStorageObjectCache_GET('browsingHistory', window.loggedIn.screen_name);
if(cacheData) {
$('#history-container').css('display','block');
$('#history-container').html('');
$.each(cacheData, function(key,obj) {
var streamHeader = replaceHtmlSpecialChars(obj.dataStreamHeader); // because we're pulling the header with jQuery.text() before saving in localstorage, which unescapes our escaped html
$('#history-container').append('<a class="stream-selection" href="' + obj.dataStreamHref + '">' + streamHeader + '<i class="chev-right" data-tooltip="' + window.sL.tooltipBookmarkStream + '"></i></a>');
});
}
updateHistoryLocalStorage();
}
}
/* ·
·
· Does stream need a ? or a &
·
· · · · · · · · · · · · · */
function qOrAmp(stream) {
if(stream.substr(-5) == '.json') {
return '?';
}
else {
return '&';
}
}
/* ·
·
· Count chars in queet box
·
· @param src: the queetbox's value
· @param trgt: the counter
· @param btn: the button
·
· · · · · · · · · · · · · */
function countCharsInQueetBox(src,trgt,btn) {
// count linebreaks by converting them to spaces
var $src_txt = $('<div/>').append(src.html().replace(/<br>/g,' '));
$src_txt = $('<div/>').append($.trim($src_txt.text().replace(/\n/g,'').replace(/^\s+|\s+$/g, '')));
var numchars = ($src_txt.text()).length;
// check for long urls and disable/enable url shorten button if present
var longurls = 0;
$.each(src.siblings('.syntax-middle').find('span.url'),function(key,obj){
if($.trim($(obj).html().replace(/&nbsp;/gi,'').replace(/<br>/gi,'')).length > 20) {
longurls++;
}
});
if(longurls>0) src.siblings('.queet-toolbar').find('button.shorten').removeClass('disabled');
else src.siblings('.queet-toolbar').find('button.shorten').addClass('disabled');
// limited
if(window.textLimit > 0) {
trgt.html(window.textLimit - numchars);
// activate/deactivare button
if(numchars > 0 && numchars < window.textLimit+1) {
btn.removeClass('disabled');
btn.addClass('enabled');
btn.removeClass('too-long');
// deactivate button if it's equal to the start text
var queetBox = btn.closest('.inline-reply-queetbox').children('.queet-box-syntax');
if(typeof queetBox.attr('data-replies-text') != 'undefined') {
var $startText = $('<div/>').append(decodeURIComponent(queetBox.attr('data-replies-text')));
if($.trim($startText.text()) == $.trim($src_txt.text())) {
btn.removeClass('enabled');
btn.addClass('disabled');
}
}
}
else if(numchars > window.textLimit){
btn.removeClass('enabled');
btn.addClass('disabled');
btn.addClass('too-long');
}
else {
btn.removeClass('enabled');
btn.addClass('disabled');
btn.removeClass('too-long');
}
// counter color
if((window.textLimit-numchars) < 0) {
trgt.css('color','#D40D12');
}
else {
trgt.removeAttr('style');
}
}
// unlimited
else {
if(numchars > 0) {
btn.removeClass('disabled');
btn.addClass('enabled');
}
else {
btn.removeClass('enabled');
btn.addClass('disabled');
}
}
}
/* ·
·
· Prefill the queet box with cached text, if there is any in an attribute
·
· @param queetBox: jQuery object for the queet box
·
· · · · · · · · · · · · · */
function maybePrefillQueetBoxWithCachedText(queetBox) {
var cachedText = decodeURIComponent(queetBox.attr('data-cached-text'));
var cachedTextText = $('<div/>').html(cachedText).text();
if(cachedText != 'undefined' && cachedText != 'false') {
queetBox.click();
queetBox.html(cachedText);
setSelectionRange(queetBox[0], cachedTextText.length, cachedTextText.length);
queetBox.trigger('input');
}
}
/* ·
·
· Remember my scroll position
·
· @param obj: jQuery object which position we want to remember
· @param id: id for position to remember
· @param offset: we might want to offset our remembered scroll, e.g. when stream-item gets margin after expand
·
· · · · · · · · · · · · · */
function rememberMyScrollPos(obj,id,offset) {
if(typeof offset == 'undefined') {
var offset = 0;
}
if(typeof window.scrollpositions == 'undefined') { window.scrollpositions = new Object();}
window.scrollpositions[id] = obj.offset().top - $(window).scrollTop() - offset;
}
/* ·
·
· Go back to my scroll po
·
· @param obj: jQuery object to put in the remebered position
· @param id: id for remembered position
· @param animate: if we want to animate the scroll
· @param callback: function to run when animation stops
·
· · · · · · · · · · · · · */
function backToMyScrollPos(obj,id,animate,callback) {
var pos = obj.offset().top-window.scrollpositions[id];
if(animate) {
if(animate == 'animate' || animate === true) {
animate = 1000;
}
if(typeof callback !== 'undefined'){
$('html, body').animate({ scrollTop: pos}, animate, 'swing',function(){
callback();
});
}
else {
$('html, body').animate({ scrollTop: pos }, animate, 'swing');
}
}
else {
$('html, body').scrollTop(pos);
}
}
/* ·
·
· Scroll to a stream item
·
· @param streamItem: jQuery object to scroll to
·
· · · · · · · · · · · · · */
function scrollToQueet(streamItem) {
var streamItemPos = streamItem.offset().top;
var windowHeight = $(window).height();
var streamItemHeight = streamItem.outerHeight();
// console.log(streamItemHeight);
// console.log(windowHeight);
var newScrollPos = Math.round(streamItemPos - windowHeight/2 + streamItemHeight/2);
$('html, body').scrollTop(newScrollPos);
}
/* ·
·
· Clean up user object, remove null etc
·
· · · · · · · · · · · · · */
function cleanUpUserObject(data) {
data.name = data.name || '';
data.profile_image_url = data.profile_image_url || '';
data.profile_image_url_profile_size = data.profile_image_url_profile_size || '';
data.profile_image_url_original = data.profile_image_url_original || '';
data.screen_name = data.screen_name || '';
data.description = data.description || '';
data.location = data.location || '';
data.url = data.url || '';
data.statusnet_profile_url = data.statusnet_profile_url || '';
data.statuses_count = data.statuses_count || 0;
data.followers_count = data.followers_count || 0;
data.groups_count = data.groups_count || 0;
data.friends_count = data.friends_count || 0;
return data;
}
/* ·
·
· outerHTML
·
· · · · · · · · · · · · · */
jQuery.fn.outerHTML = function(s) {
return s
? this.before(s).remove()
: jQuery("<p>").append(this.eq(0).clone()).html();
};
/* ·
·
· Sort divs by attribute descending
·
· · · · · · · · · · · · · */
jQuery.fn.sortDivsByAttrDesc = function sortDivsByAttrDesc(attr) {
$("> div", this[0]).sort(dec_sort).appendTo(this[0]);
function dec_sort(a, b){ return parseInt($(b).attr(attr),10) > parseInt($(a).attr(attr),10) ? 1 : -1; }
}
/* ·
·
· Stuff to get and set selection/caret in contenteditables
·
· · · · · · · · · · · · · */
function getSelectionInElement(element) {
var caretOffset = Array(0,0);
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
var range = win.getSelection().getRangeAt(0);
var preCaretRangeEnd = range.cloneRange();
preCaretRangeEnd.selectNodeContents(element);
preCaretRangeEnd.setEnd(range.endContainer, range.endOffset);
caretOffset[1] = preCaretRangeEnd.toString().length;
var preCaretRangeStart = range.cloneRange();
preCaretRangeStart.selectNodeContents(element);
preCaretRangeStart.setEnd(range.startContainer, range.startOffset);
caretOffset[0] = preCaretRangeStart.toString().length;
return caretOffset;
}
function getTextNodesIn(node) {
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
}
else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function setSelectionRange(el, start, end) {
if (document.createRange && window.getSelection) {
var range = document.createRange();
range.selectNodeContents(el);
var textNodes = getTextNodesIn(el);
var foundStart = false;
var charCount = 0, endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
endCharCount = charCount + textNode.length;
if(endCharCount == start && endCharCount == end) {
endCharCount = endCharCount+1;
}
if (!foundStart && start >= charCount
&& (start < endCharCount ||
(start == endCharCount && i < textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
textRange.select();
}
}
function createRangeFromCharacterIndices(containerEl, start, end) {
var charIndex = 0, range = document.createRange(), foundStart = false, stop = {};
range.setStart(containerEl, 0);
range.collapse(true);
function traverseTextNodes(node) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && start >= charIndex && start <= nextCharIndex) {
range.setStart(node, start - charIndex);
foundStart = true;
}
if (foundStart && end >= charIndex && end <= nextCharIndex) {
range.setEnd(node, end - charIndex);
throw stop;
}
charIndex = nextCharIndex;
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
traverseTextNodes(node.childNodes[i]);
}
}
}
try {
traverseTextNodes(containerEl);
} catch (ex) {
if (ex == stop) {
return range;
} else {
throw ex;
}
}
}
function deleteBetweenCharacterIndices(el, from, to) {
var range = createRangeFromCharacterIndices(el, from, to);
if(typeof range != 'undefined') {
range.deleteContents();
}
}
/* ·
·
· Shorten urls in a queet-box
·
· · · · · · · · · · · · · */
function shortenUrlsInBox(shortenButton) {
shortenButton.addClass('disabled');
$.each(shortenButton.parent().parent().siblings('.syntax-middle').find('span.url'),function(key,obj){
var url = $.trim($(obj).text());
display_spinner();
$.ajax({ url: window.urlShortenerAPIURL + '?format=' + window.urlshortenerFormat + '&action=shorturl&signature=' + window.urlShortenerSignature + '&url=' + encodeURIComponent(url), type: "GET", dataType: window.urlshortenerFormat,
success: function(data) {
if(typeof data.shorturl != 'undefined') {
shortenButton.closest('.queet-toolbar').siblings('.upload-image-container').children('img[data-shorturl="' + data.url.url + '"]').attr('data-shorturl',data.shorturl);
shortenButton.parent().parent().siblings('.queet-box-syntax').html(shortenButton.parent().parent().siblings('.queet-box-syntax').html().replace($('<div/>').text(data.url.url).html(), data.shorturl));
shortenButton.parent().parent().siblings('.queet-box-syntax').trigger('keyup');
shortenButton.addClass('disabled'); // make sure the button is disabled right after
}
remove_spinner();
},
error: function(data) {
console.log(data);
remove_spinner();
}
});
});
}
/* ·
·
· Youtube ID from Youtube URL
·
· · · · · · · · · · · · · */
function youTubeIDFromYouTubeURL(url) {
return url.replace('https://youtube.com/watch?v=','').replace('http://youtube.com/watch?v=','').replace('http://www.youtube.com/watch?v=','').replace('https://www.youtube.com/watch?v=','').replace('http://youtu.be/','').replace('https://youtu.be/','').substr(0,11);
}
/* ·
·
· Youtube embed link from youtube url
·
· · · · · · · · · · · · · */
function youTubeEmbedLinkFromURL(url, autoplay) {
// get start time hash
var l = document.createElement("a");
l.href = url;
var addStart = '';
if(l.hash.substring(0,3) == '#t=') {
addStart = '&start=' + l.hash.substring(3);
}
var addAutoplay = '';
if(typeof autoplay != 'undefined' && autoplay === true) {
addAutoplay = '&autoplay=1';
}
return '//www.youtube.com/embed/' + youTubeIDFromYouTubeURL(url) + '?enablejsapi=1&version=3&playerapiid=ytplayer' + addStart + addAutoplay;
}
/* ·
·
· Vimeo ID from Vimeo URL
·
· · · · · · · · · · · · · */
function vimeoIDFromVimeoURL(url) {
id = url.replace('http://vimeo.com/','').replace('https://vimeo.com/','');
if(id.indexOf('#') > -1) {
id = id.substring(0,id.indexOf('#'));
}
return id;
}
/* ·
·
· Vimeo embed link from vimeo url
·
· · · · · · · · · · · · · */
function vimeoEmbedLinkFromURL(url, autoplay) {
// get start time hash
var l = document.createElement("a");
l.href = url;
var addStart = '';
if(l.hash.substring(0,3) == '#t=') {
addStart = l.hash;
}
var addAutoplay = '&autoplay=0';
if(typeof autoplay != 'undefined' && autoplay === true) {
addAutoplay = '&autoplay=1';
}
return 'https://player.vimeo.com/video/' + vimeoIDFromVimeoURL(url) + '?api=1' + addAutoplay + addStart;
}
/* ·
·
· CSS class name from URL
·
· · · · · · · · · · · · · */
function CSSclassNameByHostFromURL(url) {
var host = getHost(url);
if(host.indexOf('www.') === 0) {
host = host.substring(4);
}
host = host.toLowerCase().replace(/\./g, "-");
host = host.replace(/[^a-zA-Z0-9-]+/g, "_");
if(host == 'youtu-be') {
host = 'youtube-com';
}
return 'host-' + host;
}
/* ·
·
· String similarity
·
· @params string1, string2:
· @returns (int) percent similarity
·
· · · · · · · · · · · · · */
function stringSimilarity(string1, string2) {
if(typeof string1 != 'string' || typeof string2 != 'string') {
return 0;
}
// trim and strip html tags
string1 = $('<div/>').html($.trim(string1)).text();
string2 = $('<div/>').html($.trim(string2)).text();
var longestStringLength = string1.length;
if(string2.length>string1.length) {
longestStringLength = string2.length;
}
var distanceArray = levenshteinenator(string1, string2);
var distance = distanceArray[distanceArray.length-1][distanceArray[distanceArray.length-1].length-1];
var percentSimilarity = 100-Math.round(distance/longestStringLength*100);
return percentSimilarity;
}
// from http://andrew.hedges.name/experiments/levenshtein/
var levenshteinenator = (function () {
/**
* @param String a
* @param String b
* @return Array
*/
function levenshteinenator(a, b) {
var cost;
var m = a.length;
var n = b.length;
// make sure a.length >= b.length to use O(min(n,m)) space, whatever that is
if (m < n) {
var c = a; a = b; b = c;
var o = m; m = n; n = o;
}
var r = []; r[0] = [];
for (var c = 0; c < n + 1; ++c) {
r[0][c] = c;
}
for (var i = 1; i < m + 1; ++i) {
r[i] = []; r[i][0] = i;
for ( var j = 1; j < n + 1; ++j ) {
cost = a.charAt( i - 1 ) === b.charAt( j - 1 ) ? 0 : 1;
r[i][j] = minimator( r[i-1][j] + 1, r[i][j-1] + 1, r[i-1][j-1] + cost );
}
}
return r;
}
/**
* Return the smallest of the three numbers passed in
* @param Number x
* @param Number y
* @param Number z
* @return Number
*/
function minimator(x, y, z) {
if (x <= y && x <= z) return x;
if (y <= x && y <= z) return y;
return z;
}
return levenshteinenator;
}());