2 * The javascript for friendicas hovercard. Bootstraps popover is needed.
4 * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im>
5 * qvitter code (https://github.com/hannesmannerheim/qvitter)
7 * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
10 $(document).ready(function () {
11 let $body = $('body');
12 // Prevents normal click action on click hovercard elements
13 $body.on('click', '.userinfo.click-card', function (e) {
16 // This event listener needs to be declared before the one that removes
17 // all cards so that we can stop the immediate propagation of the event
18 // Since the manual popover appears instantly and the hovercard removal is
19 // on a 100ms delay, leaving event propagation immediately hides any click hovercard
20 $body.on('mousedown', '.userinfo.click-card', function (e) {
21 e.stopImmediatePropagation();
22 let timeNow = new Date().getTime();
24 let contactUrl = false;
25 let targetElement = $(this);
28 if (targetElement.is('[href]')) {
29 contactUrl = targetElement.attr('href');
34 // no hovercard for anchor links
35 if (contactUrl.substring(0, 1) === '#') {
39 openHovercard(targetElement, contactUrl, timeNow);
42 // hover cards should be removed very easily, e.g. when any of these events happens
43 $body.on('mouseleave touchstart scroll mousedown submit keydown', function (e) {
44 // remove hover card only for desktiop user, since on mobile we open the hovercards
45 // by click event insteadof hover
46 removeAllHovercards(e, new Date().getTime());
49 $body.on('mouseover', '.userinfo.hover-card, .wall-item-responses a, .wall-item-bottom .mention a', function (e) {
50 let timeNow = new Date().getTime();
51 removeAllHovercards(e, timeNow);
52 let contactUrl = false;
53 let targetElement = $(this);
56 if (targetElement.is('[href]')) {
57 contactUrl = targetElement.attr('href');
62 // no hover card if the element has the no-hover-card class
63 if (targetElement.hasClass('no-hover-card')) {
67 // no hovercard for anchor links
68 if (contactUrl.substring(0, 1) === '#') {
72 targetElement.attr('data-awaiting-hover-card', timeNow);
74 // Delay until the hover-card does appear
75 setTimeout(function () {
77 targetElement.is(':hover')
78 && parseInt(targetElement.attr('data-awaiting-hover-card'), 10) === timeNow
79 && $('.hovercard').length === 0
81 openHovercard(targetElement, contactUrl, timeNow);
84 }).on('mouseleave', '.userinfo.hover-card, .wall-item-responses a, .wall-item-bottom .mention a', function (e) { // action when mouse leaves the hover-card
85 removeAllHovercards(e, new Date().getTime());
88 // if we're hovering a hover card, give it a class, so we don't remove it
89 $body.on('mouseover', '.hovercard', function (e) {
90 $(this).addClass('dont-remove-card');
93 $body.on('mouseleave', '.hovercard', function (e) {
94 $(this).removeClass('dont-remove-card');
95 $(this).popover('hide');
97 }); // End of $(document).ready
99 // removes all hover cards
100 function removeAllHovercards(event, priorTo) {
101 // 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)
102 setTimeout(function () {
103 $.each($('.hovercard'), function () {
104 let title = $(this).attr('data-orig-title');
105 // don't remove card if it was created after removeAllhoverCards() was called
106 if ($(this).data('card-created') < priorTo) {
107 // don't remove it if we're hovering it right now!
108 if (!$(this).hasClass('dont-remove-card')) {
109 let $handle = $('[data-hover-card-active="' + $(this).data('card-created') + '"]');
110 $handle.removeAttr('data-hover-card-active');
112 // Restoring the popover handle title
113 let title = $handle.attr('data-orig-title');
114 $handle.attr({'data-orig-title': '', title: title});
116 $(this).popover('hide');
123 function openHovercard(targetElement, contactUrl, timeNow) {
124 // store the title in a data attribute because Bootstrap
125 // popover destroys the title attribute.
126 let title = targetElement.attr('title');
127 targetElement.attr({'data-orig-title': title, title: ''});
129 // get an additional data atribute if the card is active
130 targetElement.attr('data-hover-card-active', timeNow);
131 // get the whole html content of the hover card and
132 // push it to the bootstrap popover
133 getHoverCardContent(contactUrl, function (data) {
135 targetElement.popover({
137 placement: function () {
138 // Calculate the placement of the the hovercard (if top or bottom)
139 // The placement depence on the distance between window top and the element
140 // which triggers the hover-card
141 let get_position = $(targetElement).offset().top - $(window).scrollTop();
142 if (get_position < 270) {
148 template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
151 sanitizeFn: function (content) {
152 return DOMPurify.sanitize(content)
159 getHoverCardContent.cache = {};
161 function getHoverCardContent(contact_url, callback) {
166 // Normalize and clean the profile so we can use a standardized url
167 // as key for the cache
168 let nurl = cleanContactUrl(contact_url).normalizeLink();
170 // If the contact is already in the cache use the cached result instead
171 // of doing a new ajax request
172 if (nurl in getHoverCardContent.cache) {
173 callback(getHoverCardContent.cache[nurl]);
178 url: baseurl + '/contact/hovercard',
180 success: function (data, textStatus, request) {
181 getHoverCardContent.cache[nurl] = data;