]> git.mxchange.org Git - friendica.git/commitdiff
Move mod/hovercard to src/Module/Contact/Hovercard
authorHypolite Petovan <hypolite@mrpetovan.com>
Thu, 17 Oct 2019 01:21:49 +0000 (21:21 -0400)
committerHypolite Petovan <hypolite@mrpetovan.com>
Sat, 2 Nov 2019 16:48:03 +0000 (12:48 -0400)
- Rework hovercard.js to remove JS template interpolation
- Remove template/json output from Module/Contact/Hovercard

src/Module/Contact/Hovercard.php [new file with mode: 0644]
static/routes.config.php
view/templates/hovercard.tpl
view/theme/frio/js/hovercard.js

diff --git a/src/Module/Contact/Hovercard.php b/src/Module/Contact/Hovercard.php
new file mode 100644 (file)
index 0000000..d5cdb1e
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+namespace Friendica\Module\Contact;
+
+use Friendica\BaseModule;
+use Friendica\Core\Config;
+use Friendica\Core\Renderer;
+use Friendica\Core\Session;
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+use Friendica\Model\GContact;
+use Friendica\Network\HTTPException;
+use Friendica\Util\Strings;
+use Friendica\Util\Proxy;
+
+/**
+ * Asynchronous HTML fragment provider for frio contact hovercards
+ */
+class Hovercard extends BaseModule
+{
+       public static function rawContent()
+       {
+               $contact_url = $_REQUEST['url'] ?? '';
+
+               // Get out if the system doesn't have public access allowed
+               if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
+                       throw new HTTPException\ForbiddenException();
+               }
+
+               // If a contact is connected the url is internally changed to 'redir/CID'. We need the pure url to search for
+               // the contact. So we strip out the contact id from the internal url and look in the contact table for
+               // the real url (nurl)
+               if (strpos($contact_url, 'redir/') === 0) {
+                       $cid = intval(substr($contact_url, 6));
+                       $remote_contact = Contact::selectFirst(['nurl'], ['id' => $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();
+       }
+}
index ee0669118b85a2c878c4dd5c95a35ce1885c4bde..1f2fe0ad1b98727e4bf1a58d5e8e2656b2cf38ef 100644 (file)
@@ -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]],
index 017e096afc561b89c5bb77aaecb89b6fc4c22b09..197e82f7e0e24d2982908b205e1500b8e312d854 100644 (file)
@@ -28,6 +28,7 @@
                                        {{if $profile.actions.network}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.network.1}}" aria-label="{{$profile.actions.network.0}}" title="{{$profile.actions.network.0}}"><i class="fa fa-cloud" aria-hidden="true"></i></a>{{/if}}
                                        {{if $profile.actions.edit}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.edit.1}}" aria-label="{{$profile.actions.edit.0}}" title="{{$profile.actions.edit.0}}"><i class="fa fa-user" aria-hidden="true"></i></a>{{/if}}
                                        {{if $profile.actions.follow}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.follow.1}}" aria-label="{{$profile.actions.follow.0}}" title="{{$profile.actions.follow.0}}"><i class="fa fa-user-plus" aria-hidden="true"></i></a>{{/if}}
+                    {{if $profile.actions.unfollow}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.unfollow.1}}" aria-label="{{$profile.actions.unfollow.0}}" title="{{$profile.actions.unfollow.0}}"><i class="fa fa-user-times" aria-hidden="true"></i></a>{{/if}}
                                </div>
                        </div>
                </div>
index 4e6cc8f7bb56d45281f3427435e7f0dbd4e81f21..0236d9a075236bfaf35f436ea882f3c7943a646d 100644 (file)
  * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
  *
  */
-$(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);
-
-                       // get href-attribute
-                       if(targetElement.is('[href]')) {
-                               hrefAttr = 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;
-                       }
+       $("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]')) {
+                       contact_url = targetElement.attr('href');
+               } else {
+                       return true;
+               }
 
-                       // no hovercard for anchor links
-                       if(hrefAttr.substring(0,1) == '#') {
-                               return true;
-                       }
+               // no hover card if the element has the no-hover-card class
+               if (targetElement.hasClass('no-hover-card')) {
+                       return true;
+               }
 
-                       targetElement.attr('data-awaiting-hover-card',timeNow);
+               // no hovercard for anchor links
+               if (contact_url.substring(0, 1) === '#') {
+                       return true;
+               }
 
-                       // 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";
+               targetElement.attr('data-awaiting-hover-card', timeNow);
 
-                       // 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: ""});
+               // 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: ""});
 
-                       // 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: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
-                                                                       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: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
+                                                       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);
+               success: function (data, textStatus, request) {
+                       getHoverCardContent.cache[nurl] = data;
+                       callback(data);
                },
-               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;
-                       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;
-}