/* * The javascript for friendicas hovercard. Bootstraps popover is needed. * * Much parts of the code are from Hannes Mannerheims * qvitter code (https://github.com/hannesmannerheim/qvitter) * * It is licensed under the GNU Affero General Public License * */ $(document).ready(function () { let $body = $('body'); // Prevents normal click action on click hovercard elements $body.on('click', '.userinfo.click-card', function (e) { e.preventDefault(); }); // This event listener needs to be declared before the one that removes // all cards so that we can stop the immediate propagation of the event // Since the manual popover appears instantly and the hovercard removal is // on a 100ms delay, leaving event propagation immediately hides any click hovercard $body.on('mousedown', '.userinfo.click-card', function (e) { e.stopImmediatePropagation(); let timeNow = new Date().getTime(); let contactUrl = false; let targetElement = $(this); // get href-attribute if (targetElement.is('[href]')) { contactUrl = targetElement.attr('href'); } else { return true; } // no hovercard for anchor links if (contactUrl.substring(0, 1) === '#') { return true; } openHovercard(targetElement, contactUrl, timeNow); }); // hover cards should be removed very easily, e.g. when any of these events happens $body.on('mouseleave touchstart scroll mousedown submit keydown', function (e) { // remove hover card only for desktiop user, since on mobile we open the hovercards // by click event insteadof hover removeAllHovercards(e, new Date().getTime()); }); $body.on('mouseover', '.userinfo.hover-card, .wall-item-responses a, .wall-item-bottom .mention a', function (e) { let timeNow = new Date().getTime(); removeAllHovercards(e, timeNow); let contactUrl = false; let targetElement = $(this); // get href-attribute if (targetElement.is('[href]')) { contactUrl = targetElement.attr('href'); } else { return true; } // no hover card if the element has the no-hover-card class if (targetElement.hasClass('no-hover-card')) { return true; } // no hovercard for anchor links if (contactUrl.substring(0, 1) === '#') { return true; } targetElement.attr('data-awaiting-hover-card', timeNow); // Delay until the hover-card does appear setTimeout(function () { if ( targetElement.is(':hover') && parseInt(targetElement.attr('data-awaiting-hover-card'), 10) === timeNow && $('.hovercard').length === 0 ) { openHovercard(targetElement, contactUrl, timeNow); } }, 500); }).on('mouseleave', '.userinfo.hover-card, .wall-item-responses a, .wall-item-bottom .mention a', function (e) { // action when mouse leaves the hover-card removeAllHovercards(e, new Date().getTime()); }); // if we're hovering a hover card, give it a class, so we don't remove it $body.on('mouseover', '.hovercard', function (e) { $(this).addClass('dont-remove-card'); }); $body.on('mouseleave', '.hovercard', function (e) { $(this).removeClass('dont-remove-card'); $(this).popover('hide'); }); }); // End of $(document).ready // removes all hover cards function removeAllHovercards(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($('.hovercard'), function () { let title = $(this).attr('data-orig-title'); // don't remove card if it was created after removeAllhoverCards() was called if ($(this).data('card-created') < priorTo) { // don't remove it if we're hovering it right now! if (!$(this).hasClass('dont-remove-card')) { let $handle = $('[data-hover-card-active="' + $(this).data('card-created') + '"]'); $handle.removeAttr('data-hover-card-active'); // Restoring the popover handle title let title = $handle.attr('data-orig-title'); $handle.attr({'data-orig-title': '', title: title}); $(this).popover('hide'); } } }); }, 100); } function openHovercard(targetElement, contactUrl, timeNow) { // store the title in a data attribute because Bootstrap // popover destroys the title attribute. let title = targetElement.attr('title'); targetElement.attr({'data-orig-title': title, title: ''}); // get an additional data atribute if the card is active targetElement.attr('data-hover-card-active', timeNow); // get the whole html content of the hover card and // push it to the bootstrap popover getHoverCardContent(contactUrl, function (data) { if (data) { targetElement.popover({ html: true, placement: function () { // Calculate the placement of the the hovercard (if top or bottom) // The placement depence on the distance between window top and the element // which triggers the hover-card let get_position = $(targetElement).offset().top - $(window).scrollTop(); if (get_position < 270) { return 'bottom'; } return 'top'; }, trigger: 'manual', template: '
', content: data, container: 'body', sanitizeFn: function (content) { return DOMPurify.sanitize(content) }, }).popover('show'); } }); } getHoverCardContent.cache = {}; function getHoverCardContent(contact_url, callback) { let postdata = { url: contact_url, }; // Normalize and clean the profile so we can use a standardized url // as key for the cache let nurl = cleanContactUrl(contact_url).normalizeLink(); // If the contact is already in the cache use the cached result instead // of doing a new ajax request if (nurl in getHoverCardContent.cache) { callback(getHoverCardContent.cache[nurl]); return; } $.ajax({ url: baseurl + '/contact/hovercard', data: postdata, success: function (data, textStatus, request) { getHoverCardContent.cache[nurl] = data; callback(data); }, }); }