Qvitter/js/misc-functions.js

1962 lines
74 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. ·
· ·
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
/* ·
·
· 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 100 old localstorage items');
callback();
}
/* ·
·
· 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'
];
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') {
removeOldestLocalStorageEntries(function(){
localStorageIsEnabled();
});
}
else {
return false;
}
}
}
/* ·
·
· Check for hidden items and show the new queets bar if there are any
·
· · · · · · · · · */
function maybeShowTheNewQueetsBar() {
var new_queets_num = $('#feed-body').find('.stream-item.hidden:not(.always-hidden)').length;
if(new_queets_num > 0) {
$('#new-queets-bar').parent().removeClass('hidden');
// bar label
if(new_queets_num == 1) { var q_txt = window.sL.newQueet; }
else { var q_txt = window.sL.newQueets; }
if(window.currentStreamObject.name == 'notifications') {
if(new_queets_num == 1) { var q_txt = window.sL.newNotification; }
else { var q_txt = window.sL.newNotifications; }
}
$('#new-queets-bar').html(q_txt.replace('{new-notice-count}',new_queets_num));
}
}
/* ·
·
· 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-7;
// 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
·
· Stored in localStorage 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();
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 dataProfileImageUrlWithoutProtocol = removeProtocolFromUrl(data.profile_image_url);
var siteInstanceURLWithoutProtocol = removeProtocolFromUrl(window.siteInstanceURL);
// local
if(dataProfileImageUrlWithoutProtocol.substring(0,siteInstanceURLWithoutProtocol.length) == siteInstanceURLWithoutProtocol){
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;
// easy conversion between URI and statusnet_profile_url and the key we're using in window.userArrayCache
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.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;
}
}
}
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) {
if(nickname.substring(0,1) == '@') {
nickname = nickname.substring(1);
}
// 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 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;
}
/* ·
·
· 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);
}
return guessedInstanceUrl;
}
/* ·
·
· Remove the protocol (e.g. "http://") from an URL
·
· · · · · · · · · */
function removeProtocolFromUrl(url) {
if(url.indexOf('://') == -1) {
return url;
}
return url.substring(url.indexOf('://')+3);
}
/* ·
·
· 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);
}
}
}
}
/* ·
·
· 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) {
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);
}
// ordinary notices
else if(streamItemFoundInFeed.length>0) {
var queetFoundInFeed = streamItemFoundInFeed.children('.queet');
var queetID = streamItemFoundInFeed.attr('data-quitter-id');
// console.log(obj);
// 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_activity) {
streamItemFoundInFeed.addClass('activity always-hidden');
}
// 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);
}
}
// avatar may have changed
if(typeof obj.user != 'undefined'
&& typeof obj.user.profile_image_url_profile_size != 'undefined'
&& queetFoundInFeed.find('.stream-item-header').find('img.avatar').src != obj.user.profile_image_url_profile_size) {
queetFoundInFeed.find('.stream-item-header').find('img.avatar').attr('src',obj.user.profile_image_url_profile_size);
}
// name may have changed
if(typeof obj.user != 'undefined'
&& typeof obj.user.name != 'undefined'
&& queetFoundInFeed.find('.stream-item-header').find('strong.name').html() != obj.user.name) {
queetFoundInFeed.find('.stream-item-header').find('strong.name').html(obj.user.name);
}
// attachments might have been added/changed/have had time to be processed
if(streamItemFoundInFeed.attr('data-attachments') != JSON.stringify(obj.attachments)) {
streamItemFoundInFeed.attr('data-attachments',JSON.stringify(obj.attachments));
// we might want to add thumbnails
if(queetFoundInFeed.find('.queet-thumbs').hasClass('thumb-num-0')) {
var attachmentsHTMLBuild = buildAttachmentHTML(obj.attachments);
queetFoundInFeed.find('.queet-thumbs').removeClass('thumb-num-0');
queetFoundInFeed.find('.queet-thumbs').addClass('thumb-num-' + attachmentsHTMLBuild.num);
queetFoundInFeed.find('.queet-thumbs').html(attachmentsHTMLBuild.html);
}
}
// 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);
}
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);
}
// 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);
}
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');
}
}
}
}
}
}
/* ·
·
· 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');
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);
feed.find('.view-more-container-top').remove();
feed.find('.view-more-container-bottom').remove();
feed.find('.stream-item.conversation').remove();
feed.find('.expanded-content').remove();
feed.find('.inline-reply-queetbox').remove();
feed.children('.stream-item').removeClass('not-seen');
feed.find('.show-full-conversation').remove();
feed.find('.stream-item').removeClass('expanded').removeClass('next-expanded').removeClass('hidden').addClass('visible');
var feedHtml = feed.html();
var profileCardHtml = $('#feed').siblings('.profile-card').outerHTML();
var streamData = {
card: profileCardHtml,
feed: feedHtml
};
localStorageObjectCache_STORE('streamState',window.currentStreamObject.path, streamData);
}
}
/* ·
·
· 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 && streamItem.attr('data-attachments') != '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 an array in a data-attachments attribute
else if(cacheOnly === false){
var attachmentId = attachmentMore.attr('data-attachment-id');
$.each(JSON.parse(streamItem.attr('data-attachments')), 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;
}
}
/* ·
·
· 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 = window.siteTitle + ' (' + totNotif + ')'; // update html page title
$('#unseen-notifications').show();
}
else {
$('#unseen-notifications').hide();
document.title = window.siteTitle;
}
}
/* ·
·
· Mark all notifications as seen
·
· · · · · · · · · · · · · */
function markAllNotificationsAsSeen(arg,callback) {
display_spinner();
getFromAPI('qvitter/mark_all_notifications_as_seen.json',function(data){
if(data === false) {
showErrorMessage(window.sL.ERRORfailedMarkingAllNotificationsAsRead);
callback(true);
}
else {
helloAPI(function(){
$('.stream-item').removeClass('not-seen');
$('#new-queets-bar').trigger('click'); // show any hidden notifications (this will also remove the dropdown menu)
remove_spinner();
callback(true);
});
}
});
}
/* ·
·
· 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 != 'source') {
obj[property] = replaceHtmlSpecialChars(obj[property]);
}
}
}
return obj;
}
function replaceHtmlSpecialChars(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(/^(([^<>()[\]\\.,;:\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.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;
}
/* ·
·
· 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
$streamItem.find('.queet-text').find('.h-card.mention').prepend('@');
$streamItem.find('.queet-text').find('.h-card.group').prepend('!');
$streamItem.find('.queet-text').find('.vcard .fn.nickname:not(.group)').prepend('@'); // very old style
$streamItem.find('.queet-text').find('.vcard .nickname.mention:not(.fn)').prepend('@'); // old style
$streamItem.find('.queet-text').find('.vcard .nickname.group').prepend('!'); // old style
$streamItem.find('.queet-text').find('a[rel="tag"]').prepend('#');
}
// 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">');
}
/* ·
·
· 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();
}
/* ·
·
· 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) {
$('#history-container').append('<a class="stream-selection" href="' + obj.dataStreamHref + '">' + obj.dataStreamHeader + '<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=jsonp&action=shorturl&signature=' + window.urlShortenerSignature + '&url=' + encodeURIComponent(url), type: "GET", dataType: "jsonp",
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 embed link from youtube url
·
· · · · · · · · · · · · · */
function youTubeEmbedLinkFromURL(url) {
var youtubeId = url.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);
// get start time hash
var l = document.createElement("a");
l.href = url;
if(l.hash.substring(0,3) == '#t=') {
return '//www.youtube.com/embed/' + youtubeId + '?start=' + l.hash.substring(3);
}
else {
return '//www.youtube.com/embed/' + youtubeId;
}
}