464c7e06e1af84e015fab5f3a9f527d547157892
[friendica.git/.git] / view / theme / frio / js / hovercard.js
1 /*
2  * The javascript for friendicas hovercard. Bootstraps popover is needed.
3  *
4  * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im>
5  * qvitter code (https://github.com/hannesmannerheim/qvitter)
6  *
7  * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
8  *
9  */
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) {
14                 e.preventDefault();
15         });
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();
23
24                 let contactUrl = false;
25                 let targetElement = $(this);
26
27                 // get href-attribute
28                 if (targetElement.is('[href]')) {
29                         contactUrl = targetElement.attr('href');
30                 } else {
31                         return true;
32                 }
33
34                 // no hovercard for anchor links
35                 if (contactUrl.substring(0, 1) === '#') {
36                         return true;
37                 }
38
39                 openHovercard(targetElement, contactUrl, timeNow);
40         });
41
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());
47         });
48
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);
54
55                 // get href-attribute
56                 if (targetElement.is('[href]')) {
57                         contactUrl = targetElement.attr('href');
58                 } else {
59                         return true;
60                 }
61
62                 // no hover card if the element has the no-hover-card class
63                 if (targetElement.hasClass('no-hover-card')) {
64                         return true;
65                 }
66
67                 // no hovercard for anchor links
68                 if (contactUrl.substring(0, 1) === '#') {
69                         return true;
70                 }
71
72                 targetElement.attr('data-awaiting-hover-card', timeNow);
73
74                 // Delay until the hover-card does appear
75                 setTimeout(function () {
76                         if (
77                                 targetElement.is(':hover')
78                                 && parseInt(targetElement.attr('data-awaiting-hover-card'), 10) === timeNow
79                                 && $('.hovercard').length === 0
80                         ) {
81                                 openHovercard(targetElement, contactUrl, timeNow);
82                         }
83                 }, 500);
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());
86         });
87
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');
91         });
92
93         $body.on('mouseleave', '.hovercard', function (e) {
94                 $(this).removeClass('dont-remove-card');
95                 $(this).popover('hide');
96         });
97 }); // End of $(document).ready
98
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');
111
112                                         // Restoring the popover handle title
113                                         let title = $handle.attr('data-orig-title');
114                                         $handle.attr({'data-orig-title': '', title: title});
115
116                                         $(this).popover('hide');
117                                 }
118                         }
119                 });
120         }, 100);
121 }
122
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: ''});
128
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) {
134                 if (data) {
135                         targetElement.popover({
136                                 html: true,
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) {
143                                                 return 'bottom';
144                                         }
145                                         return 'top';
146                                 },
147                                 trigger: 'manual',
148                                 template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
149                                 content: data,
150                                 container: 'body',
151                                 sanitizeFn: function (content) {
152                                         return DOMPurify.sanitize(content)
153                                 },
154                         }).popover('show');
155                 }
156         });
157 }
158
159 getHoverCardContent.cache = {};
160
161 function getHoverCardContent(contact_url, callback) {
162         let postdata = {
163                 url: contact_url,
164         };
165
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();
169
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]);
174                 return;
175         }
176
177         $.ajax({
178                 url: baseurl + '/contact/hovercard',
179                 data: postdata,
180                 success: function (data, textStatus, request) {
181                         getHoverCardContent.cache[nurl] = data;
182                         callback(data);
183                 },
184         });
185 }