diff --git a/css/qvitter.css b/css/qvitter.css index 36ae3d0..b315efb 100644 --- a/css/qvitter.css +++ b/css/qvitter.css @@ -1858,22 +1858,30 @@ body.rtl .view-more-container-bottom { direction:rtl; } #popup-external-profile .profile-card, -#popup-external-profile .profile-card .profile-banner-footer { +#popup-external-profile .profile-card .profile-banner-footer, +#popup-local-profile .profile-card, +#popup-local-profile .profile-card .profile-banner-footer { border-radius:0; } -#popup-external-profile ul.queet-actions { +#popup-external-profile ul.queet-actions, +#popup-local-profile ul.queet-actions { display:none; } #popup-external-profile .queet, #popup-external-profile .queet-content, -#popup-external-profile .queet-text { +#popup-external-profile .queet-text +#popup-local-profile .queet, +#popup-local-profile .queet-content, +#popup-local-profile .queet-text { cursor:auto; border-bottom:0 none; } -#popup-external-profile .stream-item .stream-item-header .name:before { +#popup-external-profile .stream-item .stream-item-header .name:before, +#popup-local-profile .stream-item .stream-item-header .name:before { left:10px; } -#popup-external-profile .go-to-external-profile { +#popup-external-profile .go-to-external-profile, +#popup-local-profile .go-to-local-profile { font-weight:bold; } @@ -3507,6 +3515,12 @@ button.shorten:after { top: 20px; } +#popup-external-profile-spinner .loader, +#popup-local-profile-spinner .loader { + position:absolute; + top:150px; + } + .reload-stream { display: block; position: absolute; diff --git a/js/ajax-functions.js b/js/ajax-functions.js index 79e2349..7b5a8f7 100644 --- a/js/ajax-functions.js +++ b/js/ajax-functions.js @@ -147,18 +147,18 @@ function getFromAPI(stream, actionOnSuccess) { displayOrHideUnreadNotifications(request.getResponseHeader('Qvitter-Notifications')); - // profile card from user array + // profile card from user array, also cache it if(request.getResponseHeader('Qvitter-User-Array') !== null) { - addProfileCardToDOM( - buildProfileCard( - iterateRecursiveReplaceHtmlSpecialChars( - $.parseJSON( - request.getResponseHeader('Qvitter-User-Array'))))); + var userArray = iterateRecursiveReplaceHtmlSpecialChars($.parseJSON(request.getResponseHeader('Qvitter-User-Array'))); + userArrayCacheStore(userArray); + addProfileCardToDOM(buildProfileCard(userArray)); } data = convertEmptyObjectToEmptyArray(data); data = iterateRecursiveReplaceHtmlSpecialChars(data); + + searchForUserDataToCache(data); actionOnSuccess(data); }, diff --git a/js/dom-functions.js b/js/dom-functions.js index 6855a63..09b0a6b 100644 --- a/js/dom-functions.js +++ b/js/dom-functions.js @@ -277,9 +277,8 @@ function buildExternalProfileCard(data) { emptyWebpage = ' empty'; } - var serverUrl = data.statusnet_profile_url.replace('/' + data.screen_name,''); - var userApiUrl = serverUrl + '/api/statuses/user_timeline.json?screen_name=' + data.screen_name; - data.screenNameWithServer = '@' + data.screen_name + '@' + serverUrl.replace('http://','').replace('https://',''); + var serverUrl = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(data.statusnet_profile_url, data.screen_name); + data.screenNameWithServer = '@' + data.screen_name + '@' + serverUrl; var followButton = '
'; data.profileCard = '\ @@ -343,6 +342,50 @@ function addProfileCardToDOM(data) { } +/* · + · + · Open external profile card in popup + · + · @param data: an object with a user array + · + · · · · · · · · · */ + +function openExternalProfileInPopup(data) { + + var data = buildExternalProfileCard(data); + + // preview latest notice + var noticeHtml = ''; + if(typeof data.status != 'undefined') { + data.status.user = data; + var noticeHtml = buildQueetHtml(data.status); + } + + popUpAction('popup-external-profile', data.screenNameWithServer,data.profileCard + noticeHtml,'' + window.sL.goToExternalProfile + ''); + } + + +/* · + · + · Open local profile card in popup + · + · @param data: an object with a user array + · + · · · · · · · · · */ + +function openLocalProfileInPopup(data) { + + var data = buildProfileCard(data); + + // preview latest notice + var noticeHtml = ''; + if(typeof data.status != 'undefined') { + data.status.user = data; + var noticeHtml = buildQueetHtml(data.status); + } + + popUpAction('popup-local-profile', '@' + data.screen_name, data.profileCardHtml + '' + noticeHtml,'' + window.sL.goToExternalProfile + ''); + } /* · diff --git a/js/misc-functions.js b/js/misc-functions.js index f77b42f..18710e9 100644 --- a/js/misc-functions.js +++ b/js/misc-functions.js @@ -216,6 +216,185 @@ function checkLocalStorage() { console.log(corrected + ' entries corrected, ' + deleted + ' entries deleted'); } + +/* · + · + · User array cache + · + · Stored in window.userArrayCache as instance_url/nickname + · with protocol (http:// or https://) trimmed off, e.g. "quitter.se/hannes2peer" + · + · · · · · · · · · */ + +window.userArrayCache = 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.external.screen_name); + var key = instanceUrlWithoutProtocol + '/' + data.external.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; + } + 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; + } + if(dataToStore.external) { + window.userArrayCache[key].external = dataToStore.external; + } + } + } + +function userArrayCacheGetByLocalNickname(localNickname) { + if(typeof window.userArrayCache[window.siteRootDomain + '/' + localNickname] != 'undefined') { + return window.userArrayCache[window.siteRootDomain + '/' + localNickname]; + } + else { + return false; + } + } + +function userArrayCacheGetByProfileUrlAndNickname(profileUrl, nickname) { + var guessedInstanceUrl = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(profileUrl, nickname); + if(typeof window.userArrayCache[guessedInstanceUrl + '/' + nickname] == 'undefined') { + return false; + } + else { + return window.userArrayCache[guessedInstanceUrl + '/' + nickname]; + } + } + + + +/* · + · + · 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); + } + } + } + } + + + /* · · · Display unread notifications @@ -284,10 +463,8 @@ function iterateRecursiveReplaceHtmlSpecialChars(obj) { if (typeof obj[property] == "object") { iterateRecursiveReplaceHtmlSpecialChars(obj[property]); } - else { - if(typeof obj[property] == 'string' && property != 'statusnet_html' && property != 'source') { - obj[property] = replaceHtmlSpecialChars(obj[property]); - } + else if(typeof obj[property] == 'string' && property != 'statusnet_html' && property != 'source') { + obj[property] = replaceHtmlSpecialChars(obj[property]); } } } diff --git a/js/qvitter.js b/js/qvitter.js index 17abb3a..f26dccf 100644 --- a/js/qvitter.js +++ b/js/qvitter.js @@ -935,6 +935,11 @@ $('body').on('click','a', function(e) { if(!!$(this).attr('donthijack') || $(this).attr('donthijack') == '') { return; } + + // if we're clicking something in a profile card popup, close it! + if($(this).closest('#popup-local-profile, #popup-external-profile').length>0) { + $('.modal-container').remove(); + } // all links opens in new tab $(this).attr('target','_blank'); @@ -969,8 +974,18 @@ $('body').on('click','a', function(e) { setNewCurrentStream('favorites.json',function(){},true); } // profiles - else if ((/^[a-zA-Z0-9]+$/.test($(this).attr('href').replace('http://','').replace('https://','').replace(window.siteRootDomain + '/','')))) { - var linkNickname = $(this).attr('href').replace('http://','').replace('https://','').replace(window.siteRootDomain + '/',''); + else if ((/^[a-zA-Z0-9]+$/.test($(this).attr('href').replace('http://','').replace('https://','').replace(window.siteRootDomain + '/',''))) + || (/^[0-9]+$/.test($(this).attr('href').replace('http://','').replace('https://','').replace(window.siteRootDomain + '/user/','')))) { + + if($(this).attr('href').indexOf('/user/') > -1) { + var linkNickname = $(this).text().toLowerCase(); + if(linkNickname.substring(0,1) == '@') { + linkNickname = linkNickname.substring(1); + } + } + else { + var linkNickname = $(this).attr('href').replace('http://','').replace('https://','').replace(window.siteRootDomain + '/',''); + } // don't hijack /groups-url if(linkNickname == 'groups') { @@ -978,17 +993,52 @@ $('body').on('click','a', function(e) { } e.preventDefault(); - if($(this).parent().attr('id') == 'user-profile-link') { // logged in user + + // logged in user + if($(this).parent().attr('id') == 'user-profile-link' + || linkNickname == window.loggedIn.screen_name) { setNewCurrentStream('statuses/user_timeline.json?screen_name=' + window.loggedIn.screen_name,function(){},true); } - else { // any user - setNewCurrentStream('statuses/user_timeline.json?screen_name=' + linkNickname,function(){},true); + // when in local profile popups + else if($(this).closest('#popup-local-profile').length>0) { + setNewCurrentStream('statuses/user_timeline.json?screen_name=' + linkNickname,function(){},true); + } + // any local user, not in popups –> open popup + else { + + $(this).addClass('local-profile-clicked'); + + popUpAction('popup-local-profile', '','',false); + display_spinner('#popup-local-profile-spinner'); + + // try getting from cache, to display immediately + if($(this).hasClass('account-group')) { + var localNickname = $(this).children('.screen-name').text().toLowerCase(); + } + else { + var localNickname = $(this).text().toLowerCase(); + } + if(localNickname.substring(0,1) == '@') { + localNickname = localNickname.substring(1); + } + var cachedUserArray = userArrayCacheGetByProfileUrlAndNickname($(this).attr('href'), localNickname); + + if(cachedUserArray && cachedUserArray.local) { + openLocalProfileInPopup(cachedUserArray.local); + remove_spinner(); + $('.local-profile-clicked').removeClass('local-profile-clicked'); + } + + // but always query the server also + getFromAPI('users/show.json?id=' + localNickname,function(data){ + if(data) { + openLocalProfileInPopup(data); + remove_spinner(); + $('.local-profile-clicked').removeClass('local-profile-clicked'); + } + }); } } - else if((/^[0-9]+$/.test($(this).attr('href').replace('http://','').replace('https://','').replace(window.siteRootDomain + '/user/','')))) { - e.preventDefault(); - setNewCurrentStream('statuses/user_timeline.json?screen_name=' + $(this).text().toLowerCase(),function(){},true); - } // tags else if ($(this).attr('href').indexOf(window.siteRootDomain + '/tag/')>-1) { e.preventDefault(); @@ -1036,30 +1086,43 @@ $('body').on('click','a', function(e) { || ($(this).closest('.stream-item').hasClass('activity') && $(this).attr('href').indexOf('/group/')==-1)) // or if it's a activity notice but not a group link && typeof window.loggedIn.screen_name != 'undefined') { // if logged in e.preventDefault(); - display_spinner(); $(this).addClass('external-profile-clicked'); + + popUpAction('popup-external-profile', '','',false); + display_spinner('#popup-external-profile-spinner'); + + // try getting from cache, to display immediately + if($(this).hasClass('account-group')) { + var externalNickname = $(this).children('.screen-name').text(); + } + else { + var externalNickname = $(this).text(); + } + if(externalNickname.substring(0,1) == '@') { + externalNickname = externalNickname.substring(1); + } + var cachedUserArray = userArrayCacheGetByProfileUrlAndNickname($(this).attr('href'), externalNickname); + + if(cachedUserArray && cachedUserArray.external) { + openExternalProfileInPopup(cachedUserArray); + remove_spinner(); + $('.external-profile-clicked').removeClass('external-profile-clicked'); + } + + // but always query the server also getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent($(this).attr('href')),function(data){ if(data && data.external !== null) { - var data = buildExternalProfileCard(data); - - // preview latest notice - var noticeHtml = ''; - if(typeof data.status != 'undefined') { - data.status.user = data; - var noticeHtml = buildQueetHtml(data.status); - } - - popUpAction('popup-external-profile', data.screenNameWithServer,data.profileCard + noticeHtml,'' + window.sL.goToExternalProfile + ''); - + openExternalProfileInPopup(data); remove_spinner(); - $('a').removeClass('external-profile-clicked'); + + $('.external-profile-clicked').removeClass('external-profile-clicked'); } - // if external lookup failed, trigger click again. + // if external lookup failed, and we don't have a cached profile card, trigger click again. // it will not be hijacked since we don't remove the external-profile-clicked class here - else { - remove_spinner(); + else if($('#popup-external-profile-spinner').length > 0){ + $('.modal-container').remove(); $('.external-profile-clicked')[0].click(); $('.external-profile-clicked').removeClass('external-profile-clicked'); }