hoover cards [work in progress...]

This commit is contained in:
Hannes Mannerheim 2015-09-15 03:58:11 +02:00
parent 69e29469a3
commit 3de43b6b33
5 changed files with 341 additions and 54 deletions

View File

@ -1337,6 +1337,50 @@ body.rtl #footer-spinner-container {
border-bottom:0 none;
}
.hoover-card {
width: auto;
max-width: 290px;
line-height: 20px;
padding:3px 3px;
font-size:12px;
background-color: #fff;
color: #eee;
z-index: 10000;
position:absolute;
display:block;
font-style: normal;
text-shadow: none;
border-radius: 7px;
opacity:0;
transition: opacity 0.1s ease-in 0;
word-break: break-all;
box-shadow: 0 0 10px rgba(0,0,0,0.4);
}
.hoover-card-caret {
z-index: 10001;
opacity:0;
display:block;
content:' ';
position:absolute;
width:0;
height:0;
border:5px solid #fff;
border-top:0 none;
border-left-color: transparent;
border-right-color: transparent;
transition: opacity 0.1s ease-in 0;
}
.hoover-card-caret.bottom {
border-top:5px solid #fff;
border-bottom:0 none;
}
.hoover-card .profile-card {
width:290px;
margin-bottom:0;
border:0 none;
}
#settings-container {
padding:10px 10px 150px 10px;
}

View File

@ -166,9 +166,7 @@ function getFromAPI(stream, actionOnSuccess) {
}
data = convertEmptyObjectToEmptyArray(data);
data = iterateRecursiveReplaceHtmlSpecialChars(data);
searchForUserDataToCache(data);
actionOnSuccess(data);
@ -282,7 +280,12 @@ function APIFollowOrUnfollowUser(followOrUnfollow,user_id,this_element,actionOnS
},
dataType:"json",
error: function(data){ actionOnSuccess(false,this_element); console.log(data); },
success: function(data) { actionOnSuccess(data,this_element);}
success: function(data) {
data = convertEmptyObjectToEmptyArray(data);
data = iterateRecursiveReplaceHtmlSpecialChars(data);
searchForUserDataToCache(data);
actionOnSuccess(data,this_element);
}
});
}
@ -305,7 +308,12 @@ function APIJoinOrLeaveGroup(joinOrLeave,group_id,this_element,actionOnSuccess)
},
dataType:"json",
error: function(data){ actionOnSuccess(false,this_element); console.log(data); },
success: function(data) { actionOnSuccess(data,this_element);}
success: function(data) {
data = convertEmptyObjectToEmptyArray(data);
data = iterateRecursiveReplaceHtmlSpecialChars(data);
searchForUserDataToCache(data);
actionOnSuccess(data,this_element);
}
});
}
@ -333,7 +341,9 @@ function postQueetToAPI(queetText_txt, in_reply_to_status_id, postToGroups, acti
dataType:"json",
error: function(data){ actionOnSuccess(false); console.log(data); },
success: function(data) {
data = convertEmptyObjectToEmptyArray(data);
data = iterateRecursiveReplaceHtmlSpecialChars(data);
searchForUserDataToCache(data);
actionOnSuccess(data);
}
});
@ -360,9 +370,9 @@ function postActionToAPI(action, actionOnSuccess) {
dataType:"json",
error: function(data){ actionOnSuccess(false); console.log(data); },
success: function(data) {
data = convertEmptyObjectToEmptyArray(data);
data = iterateRecursiveReplaceHtmlSpecialChars(data);
searchForUserDataToCache(data);
actionOnSuccess(data);
}
});
@ -428,6 +438,8 @@ function getFavsAndRequeetsForQueet(q,qid) {
dataType: 'json',
success: function(data) {
data = iterateRecursiveReplaceHtmlSpecialChars(data);
if(data.favs.length > 0 || data.repeats.length > 0) {
localStorageObjectCache_STORE('favsAndRequeets',qid, data); // cache response
}

View File

@ -298,7 +298,7 @@ function buildExternalProfileCard(data) {
data.screenNameWithServer = '@' + data.screen_name + '@' + serverUrl;
var followButton = '<div class="user-actions"><button' + followLocalIdHtml + ' data-follow-user="' + data.statusnet_profile_url + '" type="button" class="qvitter-follow-button ' + followingClass + '"><span class="button-text follow-text"><i class="follow"></i>' + window.sL.userFollow + '</span><span class="button-text following-text">' + window.sL.userFollowing + '</span><span class="button-text unfollow-text">' + window.sL.userUnfollow + '</span></button></div>';
data.profileCard = '\
data.profileCardHtml = '\
<div class="profile-card">\
<div class="profile-header-inner" style="background-image:url(\'' + cover_photo + '\')">\
<div class="profile-header-inner-overlay"></div>\
@ -556,7 +556,7 @@ function setNewCurrentStream(stream,actionOnSuccess,setLocation) {
// if we have a saved copy of this stream, show it immediately (but it is replaced when stream finishes to load later)
if(typeof window.oldStreams[window.currentStream] != "undefined") {
$('.profile-card').remove();
$('.profile-card,.hoover-card,.hoover-card-caret').remove();
$('#feed').remove();
$('#user-container').after(window.oldStreams[window.currentStream]);
$('.profile-card').css('display','none');
@ -574,7 +574,7 @@ function setNewCurrentStream(stream,actionOnSuccess,setLocation) {
// fade out
$('#feed,.profile-card').animate({opacity:'0'},150,function(){
// when fade out finishes, remove any profile cards and set new header
$('.profile-card').remove();
$('.profile-card,.hoover-card,.hoover-card-caret').remove();
$('#feed-header-inner h2').html(h2FeedHeader);
});
}
@ -1587,11 +1587,61 @@ function addToFeed(feed, after, extraClasses, isReply) {
if(obj.ntype == 'like') {
var noticeTime = parseTwitterDate(obj.notice.created_at);
var notificationHtml = '<div data-quitter-id-in-stream="' + obj.id + '" id="stream-item-n-' + obj.id + '" class="stream-item ' + extraClassesThisRun + ' notification like"><div class="queet"><div class="dogear"></div>' + ostatusHtml + '<div class="queet-content"><div class="stream-item-header"><a class="account-group" href="' + obj.from_profile.statusnet_profile_url + '"></span><img class="avatar" src="' + obj.from_profile.profile_image_url + '" /><strong class="name" data-user-id="' + obj.from_profile.id + '" title="@' + obj.from_profile.screen_name + '">' + obj.from_profile.name + '</strong></a> ' + window.sL.xFavedYourQueet + '<small class="created-at" data-created-at="' + obj.created_at + '" title="' + obj.created_at + '">' + notificationTime + '</small></div><div class="small-grey-notice"><a href="' + window.siteInstanceURL + 'notice/' + obj.notice.id + '">' + noticeTime + '</a>: ' + $.trim(obj.notice.statusnet_html) + '</div></div></div></div>';
var notificationHtml = '<div data-quitter-id-in-stream="' + obj.id + '" id="stream-item-n-' + obj.id + '" class="stream-item ' + extraClassesThisRun + ' notification like">\
<div class="queet">\
<div class="dogear"></div>\
' + ostatusHtml + '\
<div class="queet-content">\
<div class="stream-item-header">\
<a class="account-group" href="' + obj.from_profile.statusnet_profile_url + '">\
<img class="avatar" src="' + obj.from_profile.profile_image_url + '" />\
<strong class="name" data-user-id="' + obj.from_profile.id + '" title="@' + obj.from_profile.screen_name + '">\
' + obj.from_profile.name + '\
</strong>\
</a> \
' + window.sL.xFavedYourQueet + '\
<small class="created-at" data-created-at="' + obj.created_at + '" data-tooltip="' + parseTwitterLongDate(obj.created_at) + '">\
' + notificationTime + '\
</small>\
</div>\
<div class="small-grey-notice">\
<a data-tooltip="' + parseTwitterLongDate(obj.notice.created_at) + '" href="' + window.siteInstanceURL + 'notice/' + obj.notice.id + '">\
' + noticeTime + '\
</a>: \
' + $.trim(obj.notice.statusnet_html) + '\
</div>\
</div>\
</div>\
</div>';
}
else if(obj.ntype == 'repeat') {
var noticeTime = parseTwitterDate(obj.notice.created_at);
var notificationHtml = '<div data-quitter-id-in-stream="' + obj.id + '" id="stream-item-n-' + obj.id + '" class="stream-item ' + extraClassesThisRun + ' notification repeat"><div class="queet"><div class="queet-content"><div class="dogear"></div>' + ostatusHtml + '<div class="stream-item-header"><a class="account-group" href="' + obj.from_profile.statusnet_profile_url + '"><img class="avatar" src="' + obj.from_profile.profile_image_url + '" /><strong class="name" data-user-id="' + obj.from_profile.id + '" title="@' + obj.from_profile.screen_name + '">' + obj.from_profile.name + '</strong></a> ' + window.sL.xRepeatedYourQueet + '<small class="created-at" data-created-at="' + obj.created_at + '" title="' + obj.created_at + '">' + notificationTime + '</small></div><div class="small-grey-notice"><a href="' + window.siteInstanceURL + 'notice/' + obj.notice.id + '">' + noticeTime + '</a>: ' + $.trim(obj.notice.statusnet_html) + '</div></div></div></div>';
var notificationHtml = '<div data-quitter-id-in-stream="' + obj.id + '" id="stream-item-n-' + obj.id + '" class="stream-item ' + extraClassesThisRun + ' notification repeat">\
<div class="queet">\
<div class="queet-content">\
<div class="dogear"></div>\
' + ostatusHtml + '\
<div class="stream-item-header">\
<a class="account-group" href="' + obj.from_profile.statusnet_profile_url + '">\
<img class="avatar" src="' + obj.from_profile.profile_image_url + '" />\
<strong class="name" data-user-id="' + obj.from_profile.id + '" title="@' + obj.from_profile.screen_name + '">\
' + obj.from_profile.name + '\
</strong>\
</a> \
' + window.sL.xRepeatedYourQueet + '\
<small class="created-at" data-created-at="' + obj.created_at + '" data-tooltip="' + parseTwitterLongDate(obj.created_at) + '">\
' + notificationTime + '\
</small>\
</div>\
<div class="small-grey-notice">\
<a data-tooltip="' + parseTwitterLongDate(obj.notice.created_at) + '" href="' + window.siteInstanceURL + 'notice/' + obj.notice.id + '">\
' + noticeTime + '\
</a>: \
' + $.trim(obj.notice.statusnet_html) + '\
</div>\
</div>\
</div>\
</div>';
}
else if(obj.ntype == 'mention') {
var notificationHtml = buildQueetHtml(obj.notice, obj.id, extraClassesThisRun + ' notification mention');
@ -2036,7 +2086,7 @@ function buildQueetHtml(obj, idInStream, extraClassesThisRun, requeeted_by, isCo
</a>\
<i class="addressees">' + reply_to_html + in_groups_html + '</i>\
<small class="created-at" data-created-at="' + obj.created_at + '">\
<a href="' + window.siteInstanceURL + 'notice/' + obj.id + '">' + queetTime + '</a>\
<a data-tooltip="' + parseTwitterLongDate(obj.created_at) + '" href="' + window.siteInstanceURL + 'notice/' + obj.id + '">' + queetTime + '</a>\
</small>\
</div>\
<div class="queet-text">' + $.trim(obj.statusnet_html) + '</div>\

View File

@ -328,6 +328,7 @@ function cacheSyntaxHighlightingGroups() {
window.userArrayCache = new Object();
window.convertUriToUserArrayCacheKey = new Object();
window.convertStatusnetProfileUrlToUserArrayCacheKey = new Object();
function userArrayCacheStore(data) {
@ -393,19 +394,24 @@ function userArrayCacheStore(data) {
window.userArrayCache[key].local = dataToStore.local;
// easy conversion between URI and the key we're using in window.userArrayCache
// 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];
}
@ -415,12 +421,28 @@ function userArrayCacheGetByLocalNickname(localNickname) {
}
function userArrayCacheGetByProfileUrlAndNickname(profileUrl, nickname) {
var guessedInstanceUrl = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(profileUrl, nickname);
if(typeof window.userArrayCache[guessedInstanceUrl + '/' + nickname] == 'undefined') {
return false;
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 window.userArrayCache[guessedInstanceUrl + '/' + nickname];
return false;
}
}
@ -1039,32 +1061,6 @@ function convertEmptyObjectToEmptyArray(data) {
/* ·
·
· Return all URL:s in a string
·
· @param string: the string to search
·
· @return an array with the found urls
·
· · · · · · · · · · */
function findUrls(text) {
var source = (text || '').toString();
var urlArray = [];
var url;
var matchArray;
var regexToken = /(((ftp|https?):\/\/)[\-\w@:%_\+.~#?,&\/\/=]+)|((mailto:)?[_.\w-]+@([\w][\w\-]+\.)+[a-zA-Z]{2,3})/g;
while( (matchArray = regexToken.exec( source )) !== null ) {
var token = matchArray[0];
urlArray.push( token );
}
return urlArray;
}
/* ·
·
· Functions to show and remove the spinner

View File

@ -107,10 +107,10 @@ $('body').on({
$(e.target).removeAttr('title');
}
// show tooltips
// regular tooltips
if($(e.target).is('[data-tooltip]')) {
var tooltip_text = $(e.target).attr('data-tooltip');
var tooltipElement = $('<div class="tooltip">' + tooltip_text + '</div>');
tooltip_data = $(e.target).attr('data-tooltip');
var tooltipElement = $('<div class="tooltip">' + tooltip_data + '</div>');
var tooltipCaret = $('<div class="tooltip-caret"></div>');
$('body').prepend(tooltipElement);
$('body').prepend(tooltipCaret);
@ -119,8 +119,8 @@ $('body').on({
alignTooltipToHooveredElement(tooltipElement,tooltipCaret,$(e.target));
// fade in
$('.tooltip').css('opacity','1');
$('.tooltip-caret').css('opacity','1');
tooltipElement.css('opacity','1');
tooltipCaret.css('opacity','1');
}
},
mouseleave: function (e) {
@ -139,6 +139,190 @@ function removeAllTooltips() {
}
/* ·
·
· Check for profile hoovercards to display
·
· · · · · · · · · · · · · */
window.userArrayLastRetrieved = new Object();
$('body').on('mouseover',function (e) {
var timeNow = new Date().getTime();
removeAllHooverCards(e,timeNow);
var hooverCardData = false;
var userArray = false;
var hrefAttr = false;
var possibleNickname = false;
// closest a-element with a href
if($(e.target).is('[href]')) {
var targetElement = $(e.target);
}
else if($(e.target).closest('a').length>0) {
if($(e.target).closest('a').is('[href]')) {
var targetElement = $(e.target).closest('a');
}
else {
var targetElement = $(e.target);
}
}
else {
var targetElement = $(e.target);
}
// get href-attribute
if(targetElement.is('[href]')) {
hrefAttr = targetElement.attr('href');
}
// guess what element close by could be a nickname
if($(e.target).is('[href]')) {
possibleNickname = $(e.target).text();
}
else if($(e.target).closest('a').length>0 && $(e.target).closest('a').is('[href]')) {
if($(e.target).siblings('.screen-name').length>0) { // the screen name can be in a sibling if we're lucky
possibleNickname = $(e.target).siblings('.screen-name').text();
}
else {
possibleNickname = $(e.target).text();
}
}
// see if we have it in cache, otherwise query server
getUserArrayData(hrefAttr,possibleNickname,function(userArray){
// bad data
if(typeof userArray.local == 'undefined') {
return;
}
// build card from either the local or external data, depending on what we got
if (userArray.local.is_local == true) {
var profileCard = buildProfileCard(userArray.local);
}
else if(userArray.local.is_local == false && (typeof userArray.external == 'undefined' || userArray.external === false)) {
var profileCard = buildProfileCard(userArray.local);
}
else if (userArray.local.is_local == false && typeof userArray.external != 'undefined' && userArray.external !== false) {
var profileCard = buildExternalProfileCard(userArray);
}
else {
console.log('could not build profile card...');
return;
}
var hooverCardElement = $('<div id="hoover-card-' + timeNow + '" class="hoover-card" data-card-created="' + timeNow + '">' + profileCard.profileCardHtml + '</div>');
var hooverCardCaret = $('<div id="hoover-card-caret-' + timeNow + '" class="hoover-card-caret"></div>');
targetElement.attr('data-awaiting-hoover-card',timeNow);
// let user hoover for 600ms before showing the card
setTimeout(function(){
// make sure user is still hoovering the same link and that that the link awaits the same hoover card
// (user can have flickered on and off the link triggering two or more hoover cards to in setTimeout delay)
if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hoover-card'),10) == timeNow) {
if($('.hoover-card').length == 0) { // no card if there already is one open
$('body').prepend(hooverCardElement);
$('body').prepend(hooverCardCaret);
targetElement.attr('data-hoover-card-active',timeNow);
// if the user array has not been retrieved from the server for the last 60 seconds,
// we query it for the lastest data
if((typeof window.userArrayLastRetrieved[userArray.local.id] == 'undefined') || (timeNow - window.userArrayLastRetrieved[userArray.local.id]) > 60000) {
window.userArrayLastRetrieved[userArray.local.id] = timeNow;
// local users
if(userArray.local.is_local === true) {
getFromAPI('users/show.json?id=' + userArray.local.screen_name, function(data){
if(data) {
var newProfileCard = buildProfileCard(data);
hooverCardElement.html(newProfileCard.profileCardHtml);
alignTooltipToHooveredElement(hooverCardElement,hooverCardCaret,targetElement);
}
});
}
// external users
else if(userArray.local.is_local === false) {
getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(userArray.local.statusnet_profile_url),function(data){
if(data && data.external !== null) {
var newProfileCard = buildExternalProfileCard(data);
hooverCardElement.html(newProfileCard.profileCardHtml);
alignTooltipToHooveredElement(hooverCardElement,hooverCardCaret,targetElement);
}
});
}
}
// hide tooltips
$('.tooltip,.tooltip-caret').remove();
// align hoover card to the hoovered element
alignTooltipToHooveredElement(hooverCardElement,hooverCardCaret,targetElement);
// fade in
hooverCardElement.css('opacity','1');
hooverCardCaret.css('opacity','1');
}
}
},600);
});
});
// get user array from cache (or from server TODO!!)
function getUserArrayData(maybeProfileUrl,maybeNickname,callback) {
if(maybeProfileUrl && maybeNickname) {
userArray = userArrayCacheGetByProfileUrlAndNickname(maybeProfileUrl, maybeNickname);
// no cached user array found
if(!userArray) {
// see if we have reason to believe that maybeProfileUrl really is a profile url
// query server for that
}
// from cache
else {
callback(userArray);
}
}
}
// hoover cards should be removed very easily, e.g. when any of these events happen
$('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
var timeNow = new Date().getTime();
removeAllHooverCards(e,timeNow);
});
// removes all hover cards
function removeAllHooverCards(event,priorTo) {
// don't remove hovercards until after 100ms, so user have time to move the cursor to it (which gives it the dont-remove-card class)
setTimeout(function(){
$.each($('.hoover-card'),function(){
// don't remove card if it was created after removeAllHooverCards() was called
if($(this).data('card-created') < priorTo) {
// don't remove it if we're hoovering it right now!
if(!$(this).hasClass('dont-remove-card')) {
$('[data-hoover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hoover-card-active');
$('#hoover-card-caret-' + $(this).data('card-created')).remove();
$(this).remove();
}
}
});
},100);
}
// if we're hoovering a hoover card, give it a class, so we don't remove it
$('body').on('mouseover','.hoover-card', function(e) {
$(this).addClass('dont-remove-card');
});
$('body').on('mouseleave','.hoover-card', function(e) {
$(this).removeClass('dont-remove-card');
});
/* ·
·
@ -1400,8 +1584,8 @@ function checkForNewQueets() {
}
// if we have hidden items, show new-queets-bar
if($('#feed-body').find('.stream-item.hidden').length > 0) {
var new_queets_num = $('#feed-body').find('.stream-item.hidden').length;
var new_queets_num = $('#feed-body').find('.stream-item.hidden:not(.activity)').length;
if(new_queets_num > 0) {
// if this is notifications page, update site title with hidden notification count
if(window.currentStream == 'qvitter/statuses/notifications.json') {
@ -2799,7 +2983,7 @@ $('body').on('click','.show-full-conversation',function(){
·
· · · · · · · · · · · · · */
$('body').on('click','.edit-profile-button',function(){
$('body').on('click','#page-container > .profile-card .edit-profile-button',function(){
if(!$(this).hasClass('disabled')) {
$(this).addClass('disabled');
$('html').scrollTop(0);
@ -2815,6 +2999,7 @@ $('body').on('click','.edit-profile-button',function(){
if(data.cover_photo !== false) {
coverPhotoHtml = 'background-image:url(\'' + data.cover_photo + '\')';
}
$('.hoover-card,.hoover-card-caret').remove();
$('#edit-profile-popup').prepend('\
<div class="edit-profile-container">\
<div class="upload-background-image"></div>\
@ -3385,13 +3570,13 @@ function uploadImage(e, thisUploadButton) {
·
· · · · · · · · · · · · · */
$('body').on('click','#mini-edit-profile-button, #edit-profile-header-link',function(){
$('body').on('click','#mini-edit-profile-button, #edit-profile-header-link, .hoover-card .edit-profile-button',function(){
if(window.currentStream == 'statuses/user_timeline.json?screen_name=' + window.loggedIn.screen_name) {
$('.edit-profile-button').trigger('click');
$('#page-container > .profile-card .edit-profile-button').trigger('click');
}
else {
setNewCurrentStream('statuses/user_timeline.json?screen_name=' + window.loggedIn.screen_name, function(){
$('.edit-profile-button').trigger('click');
$('#page-container > .profile-card .edit-profile-button').trigger('click');
},true);
}
});