From e67833a784de4db5a17454895596cb4214f7e42b Mon Sep 17 00:00:00 2001 From: Hypolite Petovan <hypolite@mrpetovan.com> Date: Tue, 29 Oct 2019 23:18:17 -0400 Subject: [PATCH] [frio] Add click card on contact avatars --- view/theme/frio/css/style.css | 22 +++ view/theme/frio/js/hovercard.js | 171 ++++++++++-------- .../frio/templates/event_stream_item.tpl | 2 +- view/theme/frio/templates/nav.tpl | 2 +- view/theme/frio/templates/notify.tpl | 2 +- view/theme/frio/templates/photo_item.tpl | 4 +- view/theme/frio/templates/search_item.tpl | 26 ++- view/theme/frio/templates/wall_thread.tpl | 28 ++- 8 files changed, 163 insertions(+), 94 deletions(-) diff --git a/view/theme/frio/css/style.css b/view/theme/frio/css/style.css index f1c78abc77..b0458d513c 100644 --- a/view/theme/frio/css/style.css +++ b/view/theme/frio/css/style.css @@ -1800,6 +1800,28 @@ aside .panel-body { font-size: 14px; } +/* Contact avatar click card */ +.userinfo.click-card { + position: relative; +} + +.userinfo.click-card > *:hover:after { + content: 'â'; + color: #bebebe; + font-size: 1em; + font-weight: bold; + background-color: #ffffff; + text-align: center; + line-height: 40%; + position: absolute; + top: 0; + left: 0; + width: 33%; + height: 33%; + opacity: .8; + border-radius: 0 0 40% 0; +} + /* The lock symbol popup */ #panel { position: absolute; diff --git a/view/theme/frio/js/hovercard.js b/view/theme/frio/js/hovercard.js index 0236d9a075..464c7e06e1 100644 --- a/view/theme/frio/js/hovercard.js +++ b/view/theme/frio/js/hovercard.js @@ -8,18 +8,53 @@ * */ $(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) { + 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 contact_url = false; + let contactUrl = false; let targetElement = $(this); // get href-attribute if (targetElement.is('[href]')) { - contact_url = targetElement.attr('href'); + contactUrl = targetElement.attr('href'); } else { return true; } @@ -30,88 +65,34 @@ $(document).ready(function () { } // no hovercard for anchor links - if (contact_url.substring(0, 1) === '#') { + if (contactUrl.substring(0, 1) === '#') { return true; } 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 - 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'; - } - - // Timeout until the hover-card does appear + // Delay until the hover-card does appear setTimeout(function () { if ( - targetElement.is(":hover") + 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'); - } - }); + ) { + openHovercard(targetElement, contactUrl, timeNow); } }, 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); - }); - - // 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) { - // remove hover card only for desktiop user, since on mobile we openen the hovercards - // by click event insteadof hover - if (typeof is_mobile == "undefined") { - var timeNow = new Date().getTime(); - removeAllHovercards(e, timeNow); - } + }).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) { + $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"); + $(this).popover('hide'); }); }); // End of $(document).ready @@ -120,19 +101,61 @@ 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 () { - var title = $(this).attr("data-orig-title"); + 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')) { - $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active'); - $(this).popover("hide"); + 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: '<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'); + } + }); +} + getHoverCardContent.cache = {}; function getHoverCardContent(contact_url, callback) { @@ -152,7 +175,7 @@ function getHoverCardContent(contact_url, callback) { } $.ajax({ - url: baseurl + "/contact/hovercard", + url: baseurl + '/contact/hovercard', data: postdata, success: function (data, textStatus, request) { getHoverCardContent.cache[nurl] = data; diff --git a/view/theme/frio/templates/event_stream_item.tpl b/view/theme/frio/templates/event_stream_item.tpl index 9264e9d2e3..2f2af2732e 100644 --- a/view/theme/frio/templates/event_stream_item.tpl +++ b/view/theme/frio/templates/event_stream_item.tpl @@ -27,7 +27,7 @@ {{/if}} </div> <div class="event-card-profile-name profile-entry-name"> - <a href="{{$author_link}}" class="userinfo">{{$author_name}}</a> + <a href="{{$author_link}}" class="userinfo hover-card">{{$author_name}}</a> </div> {{if $location.map}} <div id="event-location-map-{{$id}}" class="event-location-map">{{$location.map nofilter}}</div> diff --git a/view/theme/frio/templates/nav.tpl b/view/theme/frio/templates/nav.tpl index 4b515efcd1..a2b5106a61 100644 --- a/view/theme/frio/templates/nav.tpl +++ b/view/theme/frio/templates/nav.tpl @@ -285,7 +285,7 @@ <ul id="nav-notifications-template" class="media-list" style="display:none;" rel="template"> <li class="{4} notif-entry"> <div class="notif-entry-wrapper media"> - <div class="notif-photo-wrapper media-object pull-left"><a href="{6}" class="userinfo"><img data-src="{1}"></a></div> + <div class="notif-photo-wrapper media-object pull-left"><a href="{6}" class="userinfo click-card"><img data-src="{1}"></a></div> <a href="{0}" class="notif-desc-wrapper media-body"> {2} <div><time class="notif-when time" data-toggle="tooltip" title="{5}">{3}</time></div> diff --git a/view/theme/frio/templates/notify.tpl b/view/theme/frio/templates/notify.tpl index a42647cff8..58f3b0da9f 100644 --- a/view/theme/frio/templates/notify.tpl +++ b/view/theme/frio/templates/notify.tpl @@ -1,7 +1,7 @@ <div class="notif-item {{if !$item_seen}}unseen{{/if}} {{$item_label}} media"> <div class="notif-photo-wrapper media-object pull-left"> - <a class="userinfo" href="{{$item_url}}"><img src="{{$item_image}}" class="notif-image"></a> + <a class="userinfo click-card" href="{{$item_url}}"><img src="{{$item_image}}" class="notif-image"></a> </div> <div class="notif-desc-wrapper media-body"> <a href="{{$item_link}}"> diff --git a/view/theme/frio/templates/photo_item.tpl b/view/theme/frio/templates/photo_item.tpl index 935e6288b3..54eb3c1d43 100644 --- a/view/theme/frio/templates/photo_item.tpl +++ b/view/theme/frio/templates/photo_item.tpl @@ -21,7 +21,7 @@ {{* avatar picture *}} <div class="contact-photo-wrapper mframe p-author h-card pull-left"> - <a class="userinfo u-url" id="wall-item-photo-menu-{{$id}}" href="{{$profile_url}}"> + <a class="userinfo click-card u-url" id="wall-item-photo-menu-{{$id}}" href="{{$profile_url}}"> <div class="contact-photo-image-wrapper"> <img src="{{$thumb}}" class="contact-photo-xs media-object p-name u-photo" id="wall-item-photo-{{$id}}" alt="{{$name}}" /> </div> @@ -33,7 +33,7 @@ {{* the header with the comment author name *}} <div role="heading " class="contact-info-comment"> <h5 class="media-heading"> - <a href="{{$profile_url}}" title="View {{$name}}'s profile" class="wall-item-name-link userinfo"><span class="btn-link">{{$name}}</span></a> + <a href="{{$profile_url}}" title="View {{$name}}'s profile" class="wall-item-name-link userinfo hover-card"><span class="btn-link">{{$name}}</span></a> </h5> </div> diff --git a/view/theme/frio/templates/search_item.tpl b/view/theme/frio/templates/search_item.tpl index 7f3c0936d1..6c2b815161 100644 --- a/view/theme/frio/templates/search_item.tpl +++ b/view/theme/frio/templates/search_item.tpl @@ -74,14 +74,14 @@ {{* The avatar picture and the photo-menu *}} <div class="dropdown pull-left"><!-- Dropdown --> <div class="hidden-sm hidden-xs contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}"> - <a href="{{$item.profile_url}}" class="userinfo u-url" id="wall-item-photo-menu-{{$item.id}}"> + <a href="{{$item.profile_url}}" class="userinfo click-card u-url" id="wall-item-photo-menu-{{$item.id}}"> <div class="contact-photo-image-wrapper"> <img src="{{$item.thumb}}" class="contact-photo media-object {{$item.sparkle}}" id="wall-item-photo-{{$item.id}}" alt="{{$item.name}}" /> </div> </a> </div> <div class="hidden-lg hidden-md contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}"> - <a href="{{$item.profile_url}}" class="userinfo u-url" id="wall-item-photo-menu-xs-{{$item.id}}"> + <a href="{{$item.profile_url}}" class="userinfo click-card u-url" id="wall-item-photo-menu-xs-{{$item.id}}"> <div class="contact-photo-image-wrapper"> <img src="{{$item.thumb}}" class="contact-photo-xs media-object {{$item.sparkle}}" id="wall-item-photo-xs-{{$item.id}}" alt="{{$item.name}}" /> </div> @@ -91,10 +91,22 @@ {{* contact info header*}} - <div role="heading " class="contact-info hidden-sm hidden-xs media-body"><!-- <= For computer --> - <h4 class="media-heading"><a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo"><span class="wall-item-name {{$item.sparkle}}">{{$item.name}}</span></a> - {{if $item.owner_url}}{{$item.via}} <a href="{{$item.owner_url}}" target="redir" title="{{$item.olinktitle}}" class="wall-item-name-link userinfo"><span class="wall-item-name {{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}">{{$item.owner_name}}</span></a>{{/if}} - {{if $item.lock}}<span class="navicon lock fakelink" onClick="lockview(event, {{$item.id}});" title="{{$item.lock}}"> <small><i class="fa fa-lock" aria-hidden="true"></i></small></span>{{/if}} + <div role="heading" class="contact-info hidden-sm hidden-xs media-body"><!-- <= For computer --> + <h4 class="media-heading"> + <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"> + <span class="wall-item-name {{$item.sparkle}}">{{$item.name}}</span> + </a> + {{if $item.owner_url}} + {{$item.via}} + <a href="{{$item.owner_url}}" target="redir" title="{{$item.olinktitle}}" class="wall-item-name-link userinfo hover-card"> + <span class="wall-item-name {{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}">{{$item.owner_name}}</span> + </a> + {{/if}} + {{if $item.lock}} + <span class="navicon lock fakelink" onClick="lockview(event, {{$item.id}});" title="{{$item.lock}}"> + <small><i class="fa fa-lock" aria-hidden="true"></i></small> + </span> + {{/if}} <div class="additional-info text-muted"> <div id="wall-item-ago-{{$item.id}}" class="wall-item-ago"> @@ -114,7 +126,7 @@ {{* contact info header for smartphones *}} <div role="heading " class="contact-info-xs hidden-lg hidden-md"> <h5 class="media-heading"> - <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo"><span>{{$item.name}}</span></a> + <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"><span>{{$item.name}}</span></a> <p class="text-muted"><small> <span class="wall-item-ago">{{$item.ago}}</span> {{if $item.location}} — ({{$item.location nofilter}}){{/if}}</small> </p> diff --git a/view/theme/frio/templates/wall_thread.tpl b/view/theme/frio/templates/wall_thread.tpl index 5a10a02552..631aad3be2 100644 --- a/view/theme/frio/templates/wall_thread.tpl +++ b/view/theme/frio/templates/wall_thread.tpl @@ -159,14 +159,14 @@ as the value of $top_child_total (this is done at the end of this file) <div class="dropdown pull-left"><!-- Dropdown --> {{if $item.thread_level==1}} <div class="hidden-sm hidden-xs contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}} p-author h-card"> - <a class="userinfo u-url" id="wall-item-photo-menu-{{$item.id}}" href="{{$item.profile_url}}"> + <a class="userinfo click-card u-url" id="wall-item-photo-menu-{{$item.id}}" href="{{$item.profile_url}}"> <div class="contact-photo-image-wrapper"> <img src="{{$item.thumb}}" class="contact-photo media-object {{$item.sparkle}} p-name u-photo" id="wall-item-photo-{{$item.id}}" alt="{{$item.name}}" /> </div> </a> </div> <div class="hidden-lg hidden-md contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}}"> - <a class="userinfo u-url" id="wall-item-photo-menu-xs-{{$item.id}}" href="{{$item.profile_url}}"> + <a class="userinfo click-card u-url" id="wall-item-photo-menu-xs-{{$item.id}}" href="{{$item.profile_url}}"> <div class="contact-photo-image-wrapper"> <img src="{{$item.thumb}}" class="contact-photo-xs media-object {{$item.sparkle}}" id="wall-item-photo-xs-{{$item.id}}" alt="{{$item.name}}" /> </div> @@ -187,7 +187,7 @@ as the value of $top_child_total (this is done at the end of this file) {{* The avatar picture for comments *}} {{if $item.thread_level!=1}} <div class="contact-photo-wrapper mframe{{if $item.owner_url}} wwfrom{{/if}} p-author h-card"> - <a class="userinfo u-url" id="wall-item-photo-menu-{{$item.id}}" href="{{$item.profile_url}}"> + <a class="userinfo click-card u-url" id="wall-item-photo-menu-{{$item.id}}" href="{{$item.profile_url}}"> <div class="contact-photo-image-wrapper"> <img src="{{$item.thumb}}" class="contact-photo-xs media-object {{$item.sparkle}} p-name u-photo" id="wall-item-photo-comment-{{$item.id}}" alt="{{$item.name}}" /> </div> @@ -201,9 +201,21 @@ as the value of $top_child_total (this is done at the end of this file) {{* contact info header*}} {{if $item.thread_level==1}} <div role="heading " aria-level="{{$item.thread_level}}" class="contact-info hidden-sm hidden-xs media-body"><!-- <= For computer --> - <h4 class="media-heading"><a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo"><span class="wall-item-name {{$item.sparkle}}">{{$item.name}}</span></a> - {{if $item.owner_url}}{{$item.via}} <a href="{{$item.owner_url}}" target="redir" title="{{$item.olinktitle}}" class="wall-item-name-link userinfo"><span class="wall-item-name {{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}">{{$item.owner_name}}</span></a>{{/if}} - {{if $item.lock}}<span class="navicon lock fakelink" onClick="lockview(event,{{$item.id}});" title="{{$item.lock}}" data-toggle="tooltip"> <small><i class="fa fa-lock" aria-hidden="true"></i></small></span>{{/if}} + <h4 class="media-heading"> + <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"> + <span class="wall-item-name {{$item.sparkle}}">{{$item.name}}</span> + </a> + {{if $item.owner_url}} + {{$item.via}} + <a href="{{$item.owner_url}}" target="redir" title="{{$item.olinktitle}}" class="wall-item-name-link userinfo hover-card"> + <span class="wall-item-name {{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}">{{$item.owner_name}}</span> + </a> + {{/if}} + {{if $item.lock}} + <span class="navicon lock fakelink" onClick="lockview(event,{{$item.id}});" title="{{$item.lock}}" data-toggle="tooltip"> + <small><i class="fa fa-lock" aria-hidden="true"></i></small> + </span> + {{/if}} </h4> <div class="additional-info text-muted"> @@ -232,7 +244,7 @@ as the value of $top_child_total (this is done at the end of this file) {{* contact info header for smartphones *}} <div role="heading " aria-level="{{$item.thread_level}}" class="contact-info-xs hidden-lg hidden-md"><!-- <= For smartphone (responsive) --> <h5 class="media-heading"> - <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo"><span>{{$item.name}}</span></a> + <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"><span>{{$item.name}}</span></a> <p class="text-muted"> <small> <a class="time" href="{{$item.plink.orig}}"><span class="wall-item-ago">{{$item.ago}}</span></a> @@ -251,7 +263,7 @@ as the value of $top_child_total (this is done at the end of this file) <div class="media-body">{{*this is the media body for comments - this div must be closed at the end of the file *}} <div role="heading " aria-level="{{$item.thread_level}}" class="contact-info-comment"> <h5 class="media-heading"> - <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo"><span class="fakelink">{{$item.name}}</span></a> + <a href="{{$item.profile_url}}" title="{{$item.linktitle}}" class="wall-item-name-link userinfo hover-card"><span class="fakelink">{{$item.name}}</span></a> <span class="text-muted"> <small> <a class="time" href="{{$item.plink.orig}}" title="{{$item.localtime}}" data-toggle="tooltip">{{$item.ago}}</a> -- 2.39.5