1 // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later
3 * The javascript for friendicas hovercard. Bootstraps popover is needed.
5 * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im>
6 * qvitter code (https://github.com/hannesmannerheim/qvitter)
8 * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
11 $(document).ready(function () {
12 let $body = $("body");
13 // Prevents normal click action on click hovercard elements
14 $body.on("click", ".userinfo.click-card", function (e) {
17 // This event listener needs to be declared before the one that removes
18 // all cards so that we can stop the immediate propagation of the event
19 // Since the manual popover appears instantly and the hovercard removal is
20 // on a 100ms delay, leaving event propagation immediately hides any click hovercard
21 $body.on("mousedown", ".userinfo.click-card", function (e) {
22 e.stopImmediatePropagation();
23 let timeNow = new Date().getTime();
25 let contactUrl = false;
26 let targetElement = $(this);
29 if (targetElement.is("[href]")) {
30 contactUrl = targetElement.attr("href");
35 // no hovercard for anchor links
36 if (contactUrl.substring(0, 1) === "#") {
40 openHovercard(targetElement, contactUrl, timeNow);
43 // hover cards should be removed very easily, e.g. when any of these events happens
44 $body.on("mouseleave touchstart scroll mousedown submit keydown", function (e) {
45 // remove hover card only for desktiop user, since on mobile we open the hovercards
46 // by click event insteadof hover
47 removeAllHovercards(e, new Date().getTime());
51 .on("mouseover", ".userinfo.hover-card, .wall-item-responses a, .wall-item-bottom .mention a", function (e) {
52 let timeNow = new Date().getTime();
53 removeAllHovercards(e, timeNow);
54 let contactUrl = false;
55 let targetElement = $(this);
58 if (targetElement.is("[href]")) {
59 contactUrl = targetElement.attr("href");
64 // no hover card if the element has the no-hover-card class
65 if (targetElement.hasClass("no-hover-card")) {
69 // no hovercard for anchor links
70 if (contactUrl.substring(0, 1) === "#") {
74 targetElement.attr("data-awaiting-hover-card", timeNow);
76 // Delay until the hover-card does appear
77 setTimeout(function () {
79 targetElement.is(":hover") &&
80 parseInt(targetElement.attr("data-awaiting-hover-card"), 10) === timeNow &&
81 $(".hovercard").length === 0
83 openHovercard(targetElement, contactUrl, timeNow);
87 .on("mouseleave", ".userinfo.hover-card, .wall-item-responses a, .wall-item-bottom .mention a", function (e) {
88 // action when mouse leaves the hover-card
89 removeAllHovercards(e, new Date().getTime());
92 // if we're hovering a hover card, give it a class, so we don't remove it
93 $body.on("mouseover", ".hovercard", function (e) {
94 $(this).addClass("dont-remove-card");
97 $body.on("mouseleave", ".hovercard", function (e) {
98 $(this).removeClass("dont-remove-card");
99 $(this).popover("hide");
101 }); // End of $(document).ready
103 // removes all hover cards
104 function removeAllHovercards(event, priorTo) {
105 // 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)
106 setTimeout(function () {
107 $.each($(".hovercard"), function () {
108 let title = $(this).attr("data-orig-title");
109 // don't remove card if it was created after removeAllhoverCards() was called
110 if ($(this).data("card-created") < priorTo) {
111 // don't remove it if we're hovering it right now!
112 if (!$(this).hasClass("dont-remove-card")) {
113 let $handle = $('[data-hover-card-active="' + $(this).data("card-created") + '"]');
114 $handle.removeAttr("data-hover-card-active");
116 // Restoring the popover handle title
117 let title = $handle.attr("data-orig-title");
118 $handle.attr({ "data-orig-title": "", title: title });
120 $(this).popover("hide");
127 function openHovercard(targetElement, contactUrl, timeNow) {
128 // store the title in a data attribute because Bootstrap
129 // popover destroys the title attribute.
130 let title = targetElement.attr("title");
131 targetElement.attr({ "data-orig-title": title, title: "" });
133 // get an additional data atribute if the card is active
134 targetElement.attr("data-hover-card-active", timeNow);
135 // get the whole html content of the hover card and
136 // push it to the bootstrap popover
137 getHoverCardContent(contactUrl, function (data) {
142 placement: function () {
143 // Calculate the placement of the the hovercard (if top or bottom)
144 // The placement depence on the distance between window top and the element
145 // which triggers the hover-card
146 let get_position = $(targetElement).offset().top - $(window).scrollTop();
147 if (get_position < 270) {
154 '<div class="popover hovercard" data-card-created="' +
156 '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
159 sanitizeFn: function (content) {
160 return DOMPurify.sanitize(content);
168 getHoverCardContent.cache = {};
170 function getHoverCardContent(contact_url, callback) {
175 // Normalize and clean the profile so we can use a standardized url
176 // as key for the cache
177 let nurl = cleanContactUrl(contact_url).normalizeLink();
179 // If the contact is already in the cache use the cached result instead
180 // of doing a new ajax request
181 if (nurl in getHoverCardContent.cache) {
182 callback(getHoverCardContent.cache[nurl]);
187 url: baseurl + "/contact/hovercard",
189 success: function (data, textStatus, request) {
190 getHoverCardContent.cache[nurl] = data;