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 // Elements with the class "userinfo" will get a hover-card.
12 // Note that this elements does need a href attribute which links to
13 // a valid profile url
14 $("body").on("mouseover", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) {
15 var timeNow = new Date().getTime();
16 removeAllhoverCards(e,timeNow);
17 var hoverCardData = false;
19 var targetElement = $(this);
22 if(targetElement.is('[href]')) {
23 hrefAttr = targetElement.attr('href');
28 // no hover card if the element has the no-hover-card class
29 if(targetElement.hasClass('no-hover-card')) {
33 // no hovercard for anchor links
34 if(hrefAttr.substring(0,1) == '#') {
38 targetElement.attr('data-awaiting-hover-card',timeNow);
40 // Take link href attribute as link to the profile
41 var profileurl = hrefAttr;
42 // the url to get the contact and template data
43 var url = baseurl + "/hovercard";
45 // store the title in an other data attribute beause bootstrap
46 // popover destroys the title.attribute. We can restore it later
47 var title = targetElement.attr("title");
48 targetElement.attr({"data-orig-title": title, title: ""});
50 // if the device is a mobile open the hover card by click and not by hover
51 if(typeof is_mobile != "undefined") {
52 targetElement[0].removeAttribute("href");
53 var hctrigger = 'click';
55 var hctrigger = 'manual';
58 // Timeout until the hover-card does appear
59 setTimeout(function(){
60 if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
61 if($('.hovercard').length == 0) { // no card if there already is one open
62 // get an additional data atribute if the card is active
63 targetElement.attr('data-hover-card-active',timeNow);
64 // get the whole html content of the hover card and
65 // push it to the bootstrap popover
66 getHoverCardContent(profileurl, url, function(data){
68 targetElement.popover({
70 placement: function () {
71 // Calculate the placement of the the hovercard (if top or bottom)
72 // The placement depence on the distance between window top and the element
73 // which triggers the hover-card
74 var get_position = $(targetElement).offset().top - $(window).scrollTop();
75 if (get_position < 270 ){
81 template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
84 sanitizeFn: function (content) {
85 return DOMPurify.sanitize(content)
93 }).on("mouseleave", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) { // action when mouse leaves the hover-card
94 var timeNow = new Date().getTime();
95 // copy the original title to the title atribute
96 var title = $(this).attr("data-orig-title");
97 $(this).attr({"data-orig-title": "", title: title});
98 removeAllhoverCards(e,timeNow);
103 // hover cards should be removed very easily, e.g. when any of these events happen
104 $('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
105 // remove hover card only for desktiop user, since on mobile we openen the hovercards
106 // by click event insteadof hover
107 if(typeof is_mobile == "undefined") {
108 var timeNow = new Date().getTime();
109 removeAllhoverCards(e,timeNow);
113 // if we're hovering a hover card, give it a class, so we don't remove it
114 $('body').on('mouseover','.hovercard', function(e) {
115 $(this).addClass('dont-remove-card');
117 $('body').on('mouseleave','.hovercard', function(e) {
118 $(this).removeClass('dont-remove-card');
119 $(this).popover("hide");
122 }); // End of $(document).ready
124 // removes all hover cards
125 function removeAllhoverCards(event,priorTo) {
126 // 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)
127 setTimeout(function(){
128 $.each($('.hovercard'),function(){
129 var title = $(this).attr("data-orig-title");
130 // don't remove card if it was created after removeAllhoverCards() was called
131 if($(this).data('card-created') < priorTo) {
132 // don't remove it if we're hovering it right now!
133 if(!$(this).hasClass('dont-remove-card')) {
134 $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
135 $(this).popover("hide");
142 // Ajax request to get json contact data
143 function getContactData(purl, url, actionOnSuccess) {
150 // Normalize and clean the profile so we can use a standardized url
151 // as key for the cache
152 var nurl = cleanContactUrl(purl).normalizeLink();
154 // If the contact is allready in the cache use the cached result instead
155 // of doing a new ajax request
156 if(nurl in getContactData.cache) {
157 setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1);
165 success: function(data, textStatus, request){
166 // Check if the nurl (normalized profile url) is present and store it to the cache
167 // The nurl will be the identifier in the object
168 if(data.nurl.length > 0) {
169 // Test if the contact is allready connected with the user (if url containing
170 // the expression ("redir/") We will store different cache keys
171 if((data.url.search("redir/")) >= 0 ) {
176 getContactData.cache[key] = data;
178 actionOnSuccess(data, url, request);
180 error: function(data) {
181 actionOnSuccess(false, data, url);
185 getContactData.cache = {};
187 // Get hover-card template data and the contact-data and transform it with
188 // the help of jSmart. At the end we have full html content of the hovercard
189 function getHoverCardContent(purl, url, callback) {
190 // fetch the raw content of the template
191 getHoverCardTemplate(url, function(stpl) {
192 var template = unescape(stpl);
194 // get the contact data
195 getContactData (purl, url, function(data) {
196 if(typeof template != 'undefined') {
197 // get the hover-card variables
198 var variables = getHoverCardVariables(data);
201 // use friendicas template delimiters instead of
203 jSmart.prototype.left_delimiter = '{{';
204 jSmart.prototype.right_delimiter = '}}';
206 // create a new jSmart instant with the raw content
208 var tpl = new jSmart (template);
209 // insert the variables content into the template content
210 var HoverCardContent = tpl.fetch(variables);
212 callback(HoverCardContent);
217 // This is interisting. this pice of code ajax request are done asynchron.
218 // To make it work getHOverCardTemplate() and getHOverCardData have to return it's
219 // data (no succes handler for each of this). I leave it here, because it could be useful.
220 // https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/
222 // getHoverCardTemplate(url),
223 // getContactData (term, url )
225 // ).done(function(template, profile){
226 // if(typeof template != 'undefined') {
227 // var variables = getHoverCardVariables(profile);
229 // jSmart.prototype.left_delimiter = '{{';
230 // jSmart.prototype.right_delimiter = '}}';
231 // var tpl = new jSmart (template);
232 // var html = tpl.fetch(variables);
240 // Ajax request to get the raw template content
241 function getHoverCardTemplate (url, callback) {
247 // Look if we have the template already in the cace, so we don't have
249 if('hovercard' in getHoverCardTemplate.cache) {
250 setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1);
257 success: function(data, textStatus) {
258 // write the data in the cache
259 getHoverCardTemplate.cache['hovercard'] = data;
262 }).fail(function () {callback([]); });
264 getHoverCardTemplate.cache = {};
266 // The Variables used for the template
267 function getHoverCardVariables(object) {
275 location: object.location,
276 gender: object.gender,
278 network: object.network,
281 account_type: object.account_type,
282 actions: object.actions
285 var variables = { profile: profile};