From ff27f45cb9d199a94ccf877d1f8234da2a86a9e8 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 16 Oct 2019 21:21:49 -0400 Subject: [PATCH] Move mod/hovercard to src/Module/Contact/Hovercard - Rework hovercard.js to remove JS template interpolation - Remove template/json output from Module/Contact/Hovercard --- src/Module/Contact/Hovercard.php | 104 ++++++++++ static/routes.config.php | 34 ++-- view/templates/hovercard.tpl | 1 + view/theme/frio/js/hovercard.js | 320 ++++++++++--------------------- 4 files changed, 220 insertions(+), 239 deletions(-) create mode 100644 src/Module/Contact/Hovercard.php diff --git a/src/Module/Contact/Hovercard.php b/src/Module/Contact/Hovercard.php new file mode 100644 index 0000000000..d5cdb1e95c --- /dev/null +++ b/src/Module/Contact/Hovercard.php @@ -0,0 +1,104 @@ + $cid]); + $contact_url = $remote_contact['nurl'] ?? ''; + } + + $contact = []; + + // if it's the url containing https it should be converted to http + $contact_nurl = Strings::normaliseLink(GContact::cleanContactUrl($contact_url)); + if (!$contact_nurl) { + throw new HTTPException\BadRequestException(); + } + + // Search for contact data + // Look if the local user has got the contact + if (Session::isAuthenticated()) { + $contact = Contact::getDetailsByURL($contact_nurl, local_user()); + } + + // If not then check the global user + if (!count($contact)) { + $contact = Contact::getDetailsByURL($contact_nurl); + } + + // Feeds url could have been destroyed through "cleanContactUrl", so we now use the original url + if (!count($contact) && Session::isAuthenticated()) { + $contact_nurl = Strings::normaliseLink($contact_url); + $contact = Contact::getDetailsByURL($contact_nurl, local_user()); + } + + if (!count($contact)) { + $contact_nurl = Strings::normaliseLink($contact_url); + $contact = Contact::getDetailsByURL($contact_nurl); + } + + if (!count($contact)) { + throw new HTTPException\NotFoundException(); + } + + // Get the photo_menu - the menu if possible contact actions + if (local_user()) { + $actions = Contact::photoMenu($contact); + } else { + $actions = []; + } + + // Move the contact data to the profile array so we can deliver it to + $tpl = Renderer::getMarkupTemplate('hovercard.tpl'); + $o = Renderer::replaceMacros($tpl, [ + '$profile' => [ + 'name' => $contact['name'], + 'nick' => $contact['nick'], + 'addr' => $contact['addr'] ?: $contact['url'], + 'thumb' => Proxy::proxifyUrl($contact['thumb'], false, Proxy::SIZE_THUMB), + 'url' => Contact::magicLink($contact['url']), + 'nurl' => $contact['nurl'], + 'location' => $contact['location'], + 'gender' => $contact['gender'], + 'about' => $contact['about'], + 'network_link' => Strings::formatNetworkName($contact['network'], $contact['url']), + 'tags' => $contact['keywords'], + 'bd' => $contact['birthday'] <= DBA::NULL_DATE ? '' : $contact['birthday'], + 'account_type' => Contact::getAccountType($contact), + 'actions' => $actions, + ], + ]); + + echo $o; + exit(); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index ee0669118b..1f2fe0ad1b 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -74,23 +74,25 @@ return [ '/compose[/{type}]' => [Module\Item\Compose::class, [R::GET, R::POST]], '/contact' => [ - '[/]' => [Module\Contact::class, [R::GET]], - '/{id:\d+}[/]' => [Module\Contact::class, [R::GET, R::POST]], - '/{id:\d+}/archive' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/block' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/conversations' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/drop' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/ignore' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/posts' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/update' => [Module\Contact::class, [R::GET]], - '/{id:\d+}/updateprofile' => [Module\Contact::class, [R::GET]], - '/archived' => [Module\Contact::class, [R::GET]], - '/batch' => [Module\Contact::class, [R::GET, R::POST]], - '/pending' => [Module\Contact::class, [R::GET]], - '/blocked' => [Module\Contact::class, [R::GET]], - '/hidden' => [Module\Contact::class, [R::GET]], - '/ignored' => [Module\Contact::class, [R::GET]], + '[/]' => [Module\Contact::class, [R::GET]], + '/{id:\d+}[/]' => [Module\Contact::class, [R::GET, R::POST]], + '/{id:\d+}/archive' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/block' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/conversations' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/drop' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/ignore' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/posts' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/update' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/updateprofile' => [Module\Contact::class, [R::GET]], + '/archived' => [Module\Contact::class, [R::GET]], + '/batch' => [Module\Contact::class, [R::GET, R::POST]], + '/pending' => [Module\Contact::class, [R::GET]], + '/blocked' => [Module\Contact::class, [R::GET]], + '/hidden' => [Module\Contact::class, [R::GET]], + '/ignored' => [Module\Contact::class, [R::GET]], + '/hovercard' => [Module\Contact\Hovercard::class, [R::GET]], ], + '/credits' => [Module\Credits::class, [R::GET]], '/delegation'=> [Module\Delegation::class, [R::GET, R::POST]], '/dirfind' => [Module\Search\Directory::class, [R::GET]], diff --git a/view/templates/hovercard.tpl b/view/templates/hovercard.tpl index 017e096afc..197e82f7e0 100644 --- a/view/templates/hovercard.tpl +++ b/view/templates/hovercard.tpl @@ -28,6 +28,7 @@ {{if $profile.actions.network}}{{/if}} {{if $profile.actions.edit}}{{/if}} {{if $profile.actions.follow}}{{/if}} + {{if $profile.actions.unfollow}}{{/if}} diff --git a/view/theme/frio/js/hovercard.js b/view/theme/frio/js/hovercard.js index 4e6cc8f7bb..0236d9a075 100644 --- a/view/theme/frio/js/hovercard.js +++ b/view/theme/frio/js/hovercard.js @@ -7,282 +7,156 @@ * It is licensed under the GNU Affero General Public License * */ -$(document).ready(function(){ +$(document).ready(function () { // Elements with the class "userinfo" will get a hover-card. // Note that this elements does need a href attribute which links to // a valid profile url - $("body").on("mouseover", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) { - var timeNow = new Date().getTime(); - removeAllhoverCards(e,timeNow); - var hoverCardData = false; - var hrefAttr = false; - var targetElement = $(this); + $("body").on("mouseover", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function (e) { + let timeNow = new Date().getTime(); + removeAllHovercards(e, timeNow); + let contact_url = false; + let targetElement = $(this); - // get href-attribute - if(targetElement.is('[href]')) { - hrefAttr = targetElement.attr('href'); - } else { - return true; - } + // get href-attribute + if (targetElement.is('[href]')) { + contact_url = 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 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(hrefAttr.substring(0,1) == '#') { - return true; - } + // no hovercard for anchor links + if (contact_url.substring(0, 1) === '#') { + return true; + } - targetElement.attr('data-awaiting-hover-card',timeNow); + targetElement.attr('data-awaiting-hover-card', timeNow); - // Take link href attribute as link to the profile - var profileurl = hrefAttr; - // the url to get the contact and template data - var url = baseurl + "/hovercard"; + // store the title in an other data attribute beause bootstrap + // popover destroys the title.attribute. We can restore it later + let title = targetElement.attr("title"); + targetElement.attr({"data-orig-title": title, title: ""}); - // store the title in an other data attribute beause bootstrap - // popover destroys the title.attribute. We can restore it later - var title = targetElement.attr("title"); - targetElement.attr({"data-orig-title": title, title: ""}); + // if the device is a mobile open the hover card by click and not by hover + if (typeof is_mobile != "undefined") { + targetElement[0].removeAttribute("href"); + var hctrigger = 'click'; + } else { + var hctrigger = 'manual'; + } - // if the device is a mobile open the hover card by click and not by hover - if(typeof is_mobile != "undefined") { - targetElement[0].removeAttribute("href"); - var hctrigger = 'click'; - } else { - var hctrigger = 'manual'; - }; - - // Timeout until the hover-card does appear - setTimeout(function(){ - if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) { - if($('.hovercard').length == 0) { // no card if there already is one open - // 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(profileurl, url, 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 - var get_position = $(targetElement).offset().top - $(window).scrollTop(); - if (get_position < 270 ){ - return "bottom"; - } - return "top"; - }, - trigger: hctrigger, - template: '
', - content: data, - container: "body", - sanitizeFn: function (content) { - return DOMPurify.sanitize(content) - }, - }).popover('show'); - } - }); + // Timeout until the hover-card does appear + setTimeout(function () { + if ( + targetElement.is(":hover") + && parseInt(targetElement.attr('data-awaiting-hover-card'), 10) === timeNow + && $('.hovercard').length === 0 + ) { // no card if there already is one open + // 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(contact_url, 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 + var get_position = $(targetElement).offset().top - $(window).scrollTop(); + if (get_position < 270) { + return "bottom"; + } + return "top"; + }, + trigger: hctrigger, + template: '
', + content: data, + container: "body", + sanitizeFn: function (content) { + return DOMPurify.sanitize(content) + }, + }).popover('show'); } - } - }, 500); - }).on("mouseleave", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) { // action when mouse leaves the hover-card + }); + } + }, 500); + }).on("mouseleave", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function (e) { // action when mouse leaves the hover-card var timeNow = new Date().getTime(); // copy the original title to the title atribute var title = $(this).attr("data-orig-title"); $(this).attr({"data-orig-title": "", title: title}); - removeAllhoverCards(e,timeNow); + removeAllHovercards(e, timeNow); }); - - // hover 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){ + $('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function (e) { // remove hover card only for desktiop user, since on mobile we openen the hovercards // by click event insteadof hover - if(typeof is_mobile == "undefined") { + if (typeof is_mobile == "undefined") { var timeNow = new Date().getTime(); - removeAllhoverCards(e,timeNow); - }; + removeAllHovercards(e, timeNow); + } }); // if we're hovering a hover card, give it a class, so we don't remove it - $('body').on('mouseover','.hovercard', function(e) { + $('body').on('mouseover', '.hovercard', function (e) { $(this).addClass('dont-remove-card'); }); - $('body').on('mouseleave','.hovercard', function(e) { + + $('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) { +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(){ + setTimeout(function () { + $.each($('.hovercard'), function () { var title = $(this).attr("data-orig-title"); // don't remove card if it was created after removeAllhoverCards() was called - if($(this).data('card-created') < priorTo) { + if ($(this).data('card-created') < priorTo) { // don't remove it if we're hovering it right now! - if(!$(this).hasClass('dont-remove-card')) { + if (!$(this).hasClass('dont-remove-card')) { $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active'); $(this).popover("hide"); } } }); - },100); + }, 100); } -// Ajax request to get json contact data -function getContactData(purl, url, actionOnSuccess) { - var postdata = { - mode : 'none', - profileurl : purl, - datatype : 'json', +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 - var nurl = cleanContactUrl(purl).normalizeLink(); + let nurl = cleanContactUrl(contact_url).normalizeLink(); - // If the contact is allready in the cache use the cached result instead + // If the contact is already in the cache use the cached result instead // of doing a new ajax request - if(nurl in getContactData.cache) { - setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1); + if (nurl in getHoverCardContent.cache) { + callback(getHoverCardContent.cache[nurl]); return; } $.ajax({ - url: url, + url: baseurl + "/contact/hovercard", data: postdata, - dataType: "json", - success: function(data, textStatus, request){ - // Check if the nurl (normalized profile url) is present and store it to the cache - // The nurl will be the identifier in the object - if(data.nurl.length > 0) { - // Test if the contact is allready connected with the user (if url containing - // the expression ("redir/") We will store different cache keys - if((data.url.search("redir/")) >= 0 ) { - var key = data.url; - } else { - var key = data.nurl; - } - getContactData.cache[key] = data; - } - actionOnSuccess(data, url, request); - }, - error: function(data) { - actionOnSuccess(false, data, url); - } - }); -} -getContactData.cache = {}; - -// Get hover-card template data and the contact-data and transform it with -// the help of jSmart. At the end we have full html content of the hovercard -function getHoverCardContent(purl, url, callback) { - // fetch the raw content of the template - getHoverCardTemplate(url, function(stpl) { - var template = unescape(stpl); - - // get the contact data - getContactData (purl, url, function(data) { - if(typeof template != 'undefined') { - // get the hover-card variables - var variables = getHoverCardVariables(data); - var tpl; - - // use friendicas template delimiters instead of - // the original one - jSmart.prototype.left_delimiter = '{{'; - jSmart.prototype.right_delimiter = '}}'; - - // create a new jSmart instant with the raw content - // of the template - var tpl = new jSmart (template); - // insert the variables content into the template content - var HoverCardContent = tpl.fetch(variables); - - callback(HoverCardContent); - } - }); - }); - -// This is interisting. this pice of code ajax request are done asynchron. -// To make it work getHOverCardTemplate() and getHOverCardData have to return it's -// data (no succes handler for each of this). I leave it here, because it could be useful. -// https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/ -// $.when( -// getHoverCardTemplate(url), -// getContactData (term, url ) -// -// ).done(function(template, profile){ -// if(typeof template != 'undefined') { -// var variables = getHoverCardVariables(profile); -// -// jSmart.prototype.left_delimiter = '{{'; -// jSmart.prototype.right_delimiter = '}}'; -// var tpl = new jSmart (template); -// var html = tpl.fetch(variables); -// -// return html; -// } -// }); -} - - -// Ajax request to get the raw template content -function getHoverCardTemplate (url, callback) { - var postdata = { - mode: 'none', - datatype: 'tpl' - }; - - // Look if we have the template already in the cace, so we don't have - // request it again - if('hovercard' in getHoverCardTemplate.cache) { - setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1); - return; - } - - $.ajax({ - url: url, - data: postdata, - success: function(data, textStatus) { - // write the data in the cache - getHoverCardTemplate.cache['hovercard'] = data; + success: function (data, textStatus, request) { + getHoverCardContent.cache[nurl] = data; callback(data); - } - }).fail(function () {callback([]); }); -} -getHoverCardTemplate.cache = {}; - -// The Variables used for the template -function getHoverCardVariables(object) { - var profile = { - name: object.name, - nick: object.nick, - addr: object.addr, - thumb: object.thumb, - url: object.url, - nurl: object.nurl, - location: object.location, - gender: object.gender, - about: object.about, - network: object.network, - tags: object.tags, - bd: object.bd, - account_type: object.account_type, - actions: object.actions - }; - - var variables = { profile: profile}; - - return variables; + }, + }); }