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