]> git.mxchange.org Git - friendica.git/blob - view/theme/frio/js/hovercard.js
Merge pull request #2569 from tobiasd/doc-settings-de
[friendica.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         // 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;
18                         var hrefAttr = false;
19                         var targetElement = $(this);
20
21                         // get href-attribute
22                         if(targetElement.is('[href]')) {
23                                 hrefAttr = targetElement.attr('href');
24                         } else {
25                                 return true;
26                         }
27
28                         // no hover card if the element has the no-hover-card class
29                         if(targetElement.hasClass('no-hover-card')) {
30                                 return true;
31                         }
32
33                         // no hovercard for anchor links
34                         if(hrefAttr.substring(0,1) == '#') {
35                                 return true;
36                         }
37
38                         targetElement.attr('data-awaiting-hover-card',timeNow);
39
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";
44
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: ""});
49
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';
54                                 } else {
55                                         var hctrigger = 'manual';
56                         };
57
58                         // Timeoute 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){
67                                                         if(data) {
68                                                                 targetElement.popover({
69                                                                         html: true,
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 ){
76                                                                                         return "bottom";
77                                                                                 }
78                                                                                 return "top";
79                                                                         },
80                                                                         trigger: hctrigger,
81                                                                         template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
82                                                                         content: data
83                                                                 }).popover('show');
84                                                         }
85                                                 });
86                                         }
87                                 }
88                         }, 500);
89         }).on("mouseleave", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) { // action when mouse leaves the hover-card
90                 var timeNow = new Date().getTime();
91                 // copy the original title to the title atribute
92                 var title = $(this).attr("data-orig-title");
93                 $(this).attr({"data-orig-title": "", title: title});
94                 removeAllhoverCards(e,timeNow);
95         });
96
97
98
99         // hover cards should be removed very easily, e.g. when any of these events happen
100         $('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
101                 // remove hover card only for desktiop user, since on mobile we openen the hovercards
102                 // by click event insteadof hover
103                 if(typeof is_mobile == "undefined") {
104                         var timeNow = new Date().getTime();
105                         removeAllhoverCards(e,timeNow);
106                 };
107         });
108
109         // if we're hovering a hover card, give it a class, so we don't remove it
110         $('body').on('mouseover','.hovercard', function(e) {
111                 $(this).addClass('dont-remove-card');
112         });
113         $('body').on('mouseleave','.hovercard', function(e) {
114                 $(this).removeClass('dont-remove-card');
115                 $(this).popover("hide");
116         });
117
118 }); // End of $(document).ready
119
120 // removes all hover cards
121 function removeAllhoverCards(event,priorTo) {
122         // 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)
123         setTimeout(function(){
124                 $.each($('.hovercard'),function(){
125                         var title = $(this).attr("data-orig-title");
126                         // don't remove card if it was created after removeAllhoverCards() was called
127                         if($(this).data('card-created') < priorTo) {
128                                 // don't remove it if we're hovering it right now!
129                                 if(!$(this).hasClass('dont-remove-card')) {
130                                         $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
131                                         $(this).popover("hide");
132                                 }
133                         }
134                 });
135         },100);
136 }
137
138 // Ajax request to get json contact data
139 function getContactData(purl, url, actionOnSuccess) {
140         var postdata = {
141                 mode            : 'none',
142                 profileurl      : purl,
143                 datatype        : 'json',
144         };
145
146         // Normalize and clean the profile so we can use a standardized url
147         // as key for the cache
148         var nurl = cleanContactUrl(purl).normalizeLink();
149
150         // If the contact is allready in the cache use the cached result instead
151         // of doing a new ajax request
152         if(nurl in getContactData.cache) {
153                 setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1);
154                 return;
155         }
156
157         $.ajax({
158                 url: url,
159                 data: postdata,
160                 dataType: "json",
161                 success: function(data, textStatus, request){
162                         // Check if the nurl (normalized profile url) is present and store it to the cache
163                         // The nurl will be the identifier in the object
164                         if(data.nurl.length > 0) {
165                                 // Test if the contact is allready connected with the user (if url containing
166                                 // the expression ("redir/") We will store different cache keys 
167                                 if((data.url.search("redir/")) >= 0 ) {
168                                         var key = data.url;
169                                 } else {
170                                         var key = data.nurl;
171                                 }
172                                 getContactData.cache[key] = data;
173                         }
174                         actionOnSuccess(data, url, request);
175                 },
176                 error: function(data) {
177                         actionOnSuccess(false, data, url);
178                 }
179         });
180 }
181 getContactData.cache = {};
182
183 // Get hover-card template data and the contact-data and transform it with
184 // the help of jSmart. At the end we have full html content of the hovercard
185 function getHoverCardContent(purl, url, callback) {
186         // fetch the raw content of the template
187         getHoverCardTemplate(url, function(stpl) {
188                 var template = unescape(stpl);
189
190                 // get the contact data
191                 getContactData (purl, url, function(data) {
192                         if(typeof template != 'undefined') {
193                                 // get the hover-card variables
194                                 var variables = getHoverCardVariables(data);
195                                 var tpl;
196
197                                 // use friendicas template delimiters instead of
198                                 // the original one
199                                 jSmart.prototype.left_delimiter = '{{';
200                                 jSmart.prototype.right_delimiter = '}}';
201
202                                 // create a new jSmart instant with the raw content
203                                 // of the template
204                                 var tpl = new jSmart (template);
205                                 // insert the variables content into the template content
206                                 var HoverCardContent = tpl.fetch(variables);
207
208                                 callback(HoverCardContent);
209                         }
210                 });
211         });
212
213 // This is interisting. this pice of code ajax request are done asynchron.
214 // To make it work getHOverCardTemplate() and getHOverCardData have to return it's 
215 // data (no succes handler for each of this). I leave it here, because it could be useful.
216 // https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/
217 //      $.when(
218 //              getHoverCardTemplate(url),
219 //              getContactData (term, url )
220 //
221 //      ).done(function(template, profile){
222 //              if(typeof template != 'undefined') {
223 //                      var variables = getHoverCardVariables(profile);
224 //
225 //                      jSmart.prototype.left_delimiter = '{{';
226 //                      jSmart.prototype.right_delimiter = '}}';
227 //                      var tpl = new jSmart (template);
228 //                      var html = tpl.fetch(variables);
229 //
230 //                      return html;
231 //              }
232 //      });
233 }
234
235
236 // Ajax request to get the raw template content
237 function getHoverCardTemplate (url, callback) {
238         var postdata = {
239                 mode: 'none',
240                 datatype: 'tpl'
241         };
242
243         // Look if we have the template already in the cace, so we don't have
244         // request it again
245         if('hovercard' in getHoverCardTemplate.cache) {
246                 setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1);
247                 return;
248         }
249
250         $.ajax({
251                 url: url,
252                 data: postdata,
253                 success: function(data, textStatus) {
254                         // write the data in the cache
255                         getHoverCardTemplate.cache['hovercard'] = data;
256                         callback(data);
257                 }
258         }).fail(function () {callback([]); });
259 }
260 getHoverCardTemplate.cache = {};
261
262 // The Variables used for the template
263 function getHoverCardVariables(object) {
264         var profile = {
265                         name:           object.name,
266                         nick:           object.nick,
267                         addr:           object.addr,
268                         thumb:          object.thumb,
269                         url:            object.url,
270                         nurl:           object.nurl,
271                         location:       object.location,
272                         gender:         object.gender,
273                         about:          object.about,
274                         network:        object.network,
275                         tags:           object.tags,
276                         bd:             object.bd,
277                         account_type:   object.account_type,
278                         actions:        object.actions
279         };
280
281         var variables = { profile:  profile};
282
283         return variables;
284 }
285
286 // This is the html template for the hover-card
287 // Since we grab the original hovercard.tpl we don't
288 // need it anymore
289 function hovercard_template() {
290         var tempate = '\
291         <div class="basic-content" >\
292                 <div class="hover-card-details">\
293                         <div class="hover-card-header left-align">\
294                                 <div class="hover-card-pic left-align">\
295                                         <span class="image-wrapper medium">\
296                                                 <a href="{{$profile.url}}" title="{{$profile.name}}"><img href="" class="left-align thumbnail" src="{{$profile.thumb}}"></a>\
297                                         </span>\
298                                 </div>\
299                                 <div class="hover-card-content">\
300                                         <div class="profile-entry-name">\
301                                                 <h4 class="left-align1"><a href="{{$profile.url}}">{{$profile.name}}</a></h4>{{if $profile.account_type}}<span>{{$profile.account_type}}</span>{{/if}}\
302                                         </div>\
303                                         <div class="profile-details">\
304                                                 <span class="profile-addr">{{$profile.addr}}</span>\
305                                                 {{if $profile.network}}<span class="profile-network"> ({{$profile.network}})</span>{{/if}}\
306                                         </div>\
307                                         {{*{{if $profile.about}}<div class="profile-details profile-about">{{$profile.about}}</div>{{/if}}*}}\
308 \
309                                 </div>\
310                                 <div class="hover-card-actions  right-aligned">\
311                                         {{* here are the differnt actions like privat message, poke, delete and so on *}}\
312                                         {{* @todo we have two different photo menus one for contacts and one for items at the network stream. We currently use the contact photo menu, so the items options are missing We need to move them *}}\
313                                         <div class="hover-card-actions-social">\
314                                                 {{if $profile.actions.pm}}<a class="btn btn-labeled btn-primary btn-sm" onclick="addToModal("{{$profile.actions.pm.1}}")" title="{{$profile.actions.pm.0}}"><i class="fa fa-envelope" aria-hidden="true"></i></a>{{/if}}\
315                                                 {{if $profile.actions.poke}}<a class="btn btn-labeled btn-primary btn-sm" onclick="addToModal("{{$profile.actions.poke.1}}")" title="{{$profile.actions.poke.0}}"><i class="fa fa-heartbeat" aria-hidden="true"></i></a>{{/if}}\
316                                         </div>\
317                                         <div class="hover-card-actions-connection">\
318                                                 {{if $profile.actions.edit}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.edit.1}}" title="{{$profile.actions.edit.0}}"><i class="fa fa-pencil" aria-hidden="true"></i></a>{{/if}}\
319                                                 {{if $profile.actions.drop}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.drop.1}}" title="{{$profile.actions.drop.0}}"><i class="fa fa-user-times" aria-hidden="true"></i></a>{{/if}}\
320                                                 {{if $profile.actions.follow}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.follow.1}}" title="{{$profile.actions.follow.0}}"><i class="fa fa-user-plus" aria-hidden="true"></i></a>{{/if}}\
321                                         </div>\
322                                 </div>\
323                         </div>\
324 \
325                         <div class="clearfix"></div>\
326 \
327                 </div>\
328         </div>\
329         {{if $profile.tags}}<div class="hover-card-footer">{{$profile.tags}}</div>{{/if}}';
330 }