1 function resizeIframe(obj) {
2 //obj.style.height = 0;
6 function _resizeIframe(obj, desth) {
7 var h = obj.style.height;
8 var ch = obj.contentWindow.document.body.scrollHeight;
9 if (h == (ch + 'px')) {
12 if (desth == ch && ch>0) {
13 obj.style.height = ch + 'px';
15 setTimeout(_resizeIframe, 100, obj, ch);
18 function openClose(theID) {
19 if(document.getElementById(theID).style.display == "block") {
20 document.getElementById(theID).style.display = "none"
23 document.getElementById(theID).style.display = "block"
27 function openMenu(theID) {
28 document.getElementById(theID).style.display = "block"
31 function closeMenu(theID) {
32 document.getElementById(theID).style.display = "none"
35 function decodeHtml(html) {
36 var txt = document.createElement("textarea");
45 var force_update = false;
47 var totStopped = false;
51 var in_progress = false;
52 var langSelect = false;
53 var commentBusy = false;
54 var last_popup_menu = null;
55 var last_popup_button = null;
56 var lockLoadContent = false;
59 $.ajaxSetup({cache: false});
61 /* setup tooltips *//*
62 $("a,.tt").each(function(){
65 if (e.hasClass("tttop")) pos="top";
66 if (e.hasClass("ttbottom")) pos="bottom";
67 if (e.hasClass("ttleft")) pos="left";
68 if (e.hasClass("ttright")) pos="right";
69 e.tipTip({defaultPosition: pos, edgeOffset: 8});
72 /* setup comment textarea buttons */
73 /* comment textarea buttons needs some "data-*" attributes to work:
74 * data-role="insert-formatting" : to mark the element as a formatting button
75 * data-comment="<string>" : string for "Comment", used by insertFormatting() function
76 * data-bbcode="<string>" : name of the bbcode element to insert. insertFormatting() will insert it as "[name][/name]"
77 * data-id="<string>" : id of the comment, used to find other comment-related element, like the textarea
79 $('body').on('click','[data-role="insert-formatting"]', function(e) {
82 var comment = o.data('comment');
83 var bbcode = o.data('bbcode');
84 var id = o.data('id');
86 Dialog.doImageBrowser("comment", id);
89 insertFormatting(comment, bbcode, id);
92 /* event from comment textarea button popups */
93 /* insert returned bbcode at cursor position or replace selected text */
94 $("body").on("fbrowser.image.comment", function(e, filename, bbcode, id) {
96 var textarea = document.getElementById("comment-edit-text-" +id);
97 var start = textarea.selectionStart;
98 var end = textarea.selectionEnd;
99 textarea.value = textarea.value.substring(0, start) + bbcode + textarea.value.substring(end, textarea.value.length);
104 /* setup onoff widgets */
105 $(".onoff input").each(function(){
107 id = $(this).attr("id");
108 $("#"+id+"_onoff ."+ (val==0?"on":"off")).addClass("hidden");
111 $(".onoff > a").click(function(event){
112 event.preventDefault();
113 var input = $(this).siblings("input");
114 var val = 1-input.val();
115 var id = input.attr("id");
116 $("#"+id+"_onoff ."+ (val==0?"on":"off")).addClass("hidden");
117 $("#"+id+"_onoff ."+ (val==1?"on":"off")).removeClass("hidden");
122 function close_last_popup_menu() {
123 if(last_popup_menu) {
124 last_popup_menu.hide();
125 last_popup_menu.off('click', function(e) {e.stopPropagation()});
126 last_popup_button.removeClass("selected");
127 last_popup_menu = null;
128 last_popup_button = null;
131 $('a[rel^=#]').click(function(e){
133 var parent = $(this).parent();
134 var isSelected = (last_popup_button && parent.attr('id') == last_popup_button.attr('id'));
135 close_last_popup_menu();
136 if(isSelected) return false;
137 menu = $( $(this).attr('rel') );
140 if (menu.attr('popup')=="false") return false;
141 parent.toggleClass("selected");
143 if (menu.css("display") == "none") {
144 last_popup_menu = null;
145 last_popup_button = null;
147 last_popup_menu = menu;
148 last_popup_menu.on('click', function(e) {e.stopPropagation()});
149 last_popup_button = parent;
150 $('#nav-notifications-menu').perfectScrollbar('update');
154 $('html').click(function() {
155 close_last_popup_menu();
159 $("a.popupbox").colorbox({
161 'transition' : 'elastic',
164 $("a.ajax-popupbox").colorbox({
165 'transition' : 'elastic',
169 /* notifications template */
170 var notifications_tpl= unescape($("#nav-notifications-template[rel=template]").html());
171 var notifications_all = unescape($('<div>').append( $("#nav-notifications-see-all").clone() ).html()); //outerHtml hack
172 var notifications_mark = unescape($('<div>').append( $("#nav-notifications-mark-all").clone() ).html()); //outerHtml hack
173 var notifications_empty = unescape($("#nav-notifications-menu").html());
175 /* enable perfect-scrollbars for different elements */
176 $('#nav-notifications-menu, aside').perfectScrollbar();
178 /* nav update event */
179 $('nav').bind('nav-update', function(e, data){
180 var invalid = data.invalid || 0;
181 if(invalid == 1) { window.location.href=window.location.href }
183 ['net', 'home', 'intro', 'mail', 'events', 'birthdays', 'notify'].forEach(function(type) {
184 var number = data[type];
187 $('#' + type + '-update').removeClass('show');
189 $('#' + type + '-update').addClass('show');
191 $('#' + type + '-update').text(number);
194 var intro = data['intro'];
195 if(intro == 0) { intro = ''; $('#intro-update-li').removeClass('show') } else { $('#intro-update-li').addClass('show') }
196 $('#intro-update-li').html(intro);
198 var mail = data['mail'];
199 if(mail == 0) { mail = ''; $('#mail-update-li').removeClass('show') } else { $('#mail-update-li').addClass('show') }
200 $('#mail-update-li').html(mail);
202 $(".sidebar-group-li .notify").removeClass("show");
203 $(data.groups).each(function(key, group) {
205 var gcount = group.count;
206 $(".group-"+gid+" .notify").addClass("show").text(gcount);
209 $(".forum-widget-entry .notify").removeClass("show");
210 $(data.forums).each(function(key, forum) {
212 var fcount = forum.count;
213 $(".forum-"+fid+" .notify").addClass("show").text(fcount);
216 if (data.notifications.length == 0) {
217 $("#nav-notifications-menu").html(notifications_empty);
219 var nnm = $("#nav-notifications-menu");
220 nnm.html(notifications_all + notifications_mark);
222 var notification_lastitem = parseInt(localStorage.getItem("notification-lastitem"));
223 var notification_id = 0;
224 $(data.notifications).each(function(key, notif){
225 var text = notif.message.format('<span class="contactname">' + notif.name + '</span>');
226 var contact = ('<a href="' + notif.url + '"><span class="contactname">' + notif.name + '</span></a>');
227 var seenclass = (notif.seen == 1) ? "notify-seen" : "notify-unseen";
228 var html = notifications_tpl.format(
229 notif.href, // {0} // link to the source
230 notif.photo, // {1} // photo of the contact
231 text, // {2} // preformatted text (autor + text)
232 notif.date, // {3} // date of notification (time ago)
233 seenclass, // {4} // visited status of the notification
234 new Date(notif.timestamp*1000), // {5} // date of notification
235 notif.url, // {6} // profile url of the contact
236 notif.message.format(contact), // {7} // preformatted html (text including author profile url)
237 '' // {8} // Deprecated
241 $(data.notifications.reverse()).each(function(key, e){
242 notification_id = parseInt(e.timestamp);
243 if (notification_lastitem !== null && notification_id > notification_lastitem) {
244 if (getNotificationPermission() === "granted") {
245 var notification = new Notification(document.title, {
246 body: decodeHtml(e.message.replace('→ ', '').format(e.name)),
249 notification['url'] = e.href;
250 notification.addEventListener("click", function(ev){
251 window.location = ev.target.url;
257 notification_lastitem = notification_id;
258 localStorage.setItem("notification-lastitem", notification_lastitem)
260 $("img[data-src]", nnm).each(function(i, el){
261 // Add src attribute for images with a data-src attribute
262 // However, don't bother if the data-src attribute is empty, because
263 // an empty "src" tag for an image will cause some browsers
264 // to prefetch the root page of the Friendica hub, which will
265 // unnecessarily load an entire profile/ or network/ page
266 if($(el).data("src") != '') $(el).attr('src', $(el).data("src"));
270 var notif = data['notify'];
272 $("#nav-notifications-linkmenu").addClass("on");
274 $("#nav-notifications-linkmenu").removeClass("on");
277 $(data.sysmsgs.notice).each(function(key, message){
278 $.jGrowl(message, {sticky: true, theme: 'notice'});
280 $(data.sysmsgs.info).each(function(key, message){
281 $.jGrowl(message, {sticky: false, theme: 'info', life: 5000});
284 /* update the js scrollbars */
285 $('#nav-notifications-menu').perfectScrollbar('update');
290 // Allow folks to stop the ajax page updates with the pause/break key
291 $(document).keydown(function(event) {
292 if(event.keyCode == '8') {
293 var target = event.target || event.srcElement;
294 if (!/input|textarea/i.test(target.nodeName)) {
298 if(event.keyCode == '19' || (event.ctrlKey && event.which == '32')) {
299 event.preventDefault();
300 if(stopped == false) {
305 $('#pause').html('<img src="images/pause.gif" alt="pause" style="border: 1px solid black;" />');
316 // Set an event listener for infinite scroll
317 if(typeof infinite_scroll !== 'undefined') {
318 $(window).scroll(function(e){
319 if ($(document).height() != $(window).height()) {
320 // First method that is expected to work - but has problems with Chrome
321 if ($(window).scrollTop() > ($(document).height() - $(window).height() * 1.5))
324 // This method works with Chrome - but seems to be much slower in Firefox
325 if ($(window).scrollTop() > (($("section").height() + $("header").height() + $("footer").height()) - $(window).height() * 1.5))
334 function NavUpdate() {
337 var pingCmd = 'ping?format=json' + ((localUser != 0) ? '&f=&uid=' + localUser : '');
338 $.get(pingCmd, function(data) {
340 // send nav-update event
341 $('nav').trigger('nav-update', data.result);
344 ['network', 'profile', 'community', 'notes', 'display'].forEach(function (src) {
345 if ($('#live-' + src).length) {
349 if ($('#live-photos').length) {
352 window.location.href = window.location.href;
358 timer = setTimeout(NavUpdate, updateInterval);
361 function liveUpdate(src) {
362 if((src == null) || (stopped) || (! profile_uid)) { $('.like-rotator').hide(); return; }
363 if(($('.comment-edit-text-full').length) || (in_progress)) {
365 clearTimeout(livetime);
367 livetime = setTimeout(function() {liveUpdate(src)}, 5000);
373 prev = 'live-' + src;
377 if ($(document).scrollTop() == 0)
380 var udargs = ((netargs.length) ? '/' + netargs : '');
381 var update_url = 'update_' + src + udargs + '&p=' + profile_uid + '&page=' + profile_page + '&force=' + ((force_update) ? 1 : 0);
383 $.get(update_url,function(data) {
385 force_update = false;
386 // $('.collapsed-comments',data).each(function() {
387 // var ident = $(this).attr('id');
388 // var is_hidden = $('#' + ident).is(':hidden');
389 // if($('#' + ident).length) {
390 // $('#' + ident).replaceWith($(this));
392 // $('#' + ident).hide();
397 $('.toplevel_item',data).each(function() {
398 var ident = $(this).attr('id');
400 if($('#' + ident).length == 0 && profile_page == 1) {
401 $('img',this).each(function() {
402 $(this).attr('src',$(this).attr('dst'));
404 $('#' + prev).after($(this));
407 // Find out if the hidden comments are open, so we can keep it that way
408 // if a new comment has been posted
409 var id = $('.hide-comments-total', this).attr('id');
410 if(typeof id != 'undefined') {
411 id = id.split('-')[3];
412 var commentsOpen = $("#collapsed-comments-" + id).is(":visible");
415 $('img',this).each(function() {
416 $(this).attr('src',$(this).attr('dst'));
418 //vScroll = $(document).scrollTop();
419 $('html').height($('html').height());
420 $('#' + ident).replaceWith($(this));
422 if(typeof id != 'undefined') {
423 if(commentsOpen) showHideComments(id);
425 $('html').height('auto');
426 //$(document).scrollTop(vScroll);
431 // reset vars for inserting individual items
433 /* prev = 'live-' + src;
435 $('.wall-item-outside-wrapper',data).each(function() {
436 var ident = $(this).attr('id');
438 if($('#' + ident).length == 0 && prev != 'live-' + src) {
439 $('img',this).each(function() {
440 $(this).attr('src',$(this).attr('dst'));
442 $('#' + prev).after($(this));
445 $('#' + ident + ' ' + '.wall-item-ago').replaceWith($(this).find('.wall-item-ago'));
446 if($('#' + ident + ' ' + '.comment-edit-text-empty').length)
447 $('#' + ident + ' ' + '.wall-item-comment-wrapper').replaceWith($(this).find('.wall-item-comment-wrapper'));
448 $('#' + ident + ' ' + '.hide-comments-total').replaceWith($(this).find('.hide-comments-total'));
449 $('#' + ident + ' ' + '.wall-item-like').replaceWith($(this).find('.wall-item-like'));
450 $('#' + ident + ' ' + '.wall-item-dislike').replaceWith($(this).find('.wall-item-dislike'));
451 $('#' + ident + ' ' + '.my-comment-photo').each(function() {
452 $(this).attr('src',$(this).attr('dst'));
458 $('.like-rotator').hide();
461 $('body').css('cursor', 'auto');
463 /* autocomplete @nicknames */
464 $(".comment-edit-form textarea").editor_autocomplete(baseurl+"/acl");
465 /* autocomplete bbcode */
466 $(".comment-edit-form textarea").bbco_autocomplete('bbcode');
468 // setup videos, since VideoJS won't take care of any loaded via AJAX
469 if(typeof videojs != 'undefined') videojs.autoSetup();
473 function imgbright(node) {
474 $(node).removeClass("drophide").addClass("drop");
477 function imgdull(node) {
478 $(node).removeClass("drop").addClass("drophide");
481 // Since our ajax calls are asynchronous, we will give a few
482 // seconds for the first ajax call (setting like/dislike), then
483 // run the updater to pick up any changes and display on the page.
484 // The updater will turn any rotators off when it's done.
485 // This function will have returned long before any of these
486 // events have completed and therefore there won't be any
487 // visible feedback that anything changed without all this
488 // trickery. This still could cause confusion if the "like" ajax call
489 // is delayed and NavUpdate runs before it completes.
491 function dolike(ident,verb) {
493 $('#like-rotator-' + ident.toString()).show();
494 $.get('like/' + ident.toString() + '?verb=' + verb, NavUpdate );
499 function dosubthread(ident) {
501 $('#like-rotator-' + ident.toString()).show();
502 $.get('subthread/' + ident.toString(), NavUpdate );
507 function dostar(ident) {
508 ident = ident.toString();
509 $('#like-rotator-' + ident).show();
510 $.get('starred/' + ident, function(data) {
511 if(data.match(/1/)) {
512 $('#starred-' + ident).addClass('starred');
513 $('#starred-' + ident).removeClass('unstarred');
514 $('#star-' + ident).addClass('hidden');
515 $('#unstar-' + ident).removeClass('hidden');
518 $('#starred-' + ident).addClass('unstarred');
519 $('#starred-' + ident).removeClass('starred');
520 $('#star-' + ident).removeClass('hidden');
521 $('#unstar-' + ident).addClass('hidden');
523 $('#like-rotator-' + ident).hide();
527 function doignore(ident) {
528 ident = ident.toString();
529 $('#like-rotator-' + ident).show();
530 $.get('ignored/' + ident, function(data) {
531 if(data.match(/1/)) {
532 $('#ignored-' + ident).addClass('ignored');
533 $('#ignored-' + ident).removeClass('unignored');
534 $('#ignore-' + ident).addClass('hidden');
535 $('#unignore-' + ident).removeClass('hidden');
538 $('#ignored-' + ident).addClass('unignored');
539 $('#ignored-' + ident).removeClass('ignored');
540 $('#ignore-' + ident).removeClass('hidden');
541 $('#unignore-' + ident).addClass('hidden');
543 $('#like-rotator-' + ident).hide();
547 function getPosition(e) {
548 var cursor = {x:0, y:0};
549 if ( e.pageX || e.pageY ) {
554 if( e.clientX || e.clientY ) {
555 cursor.x = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft) - document.documentElement.clientLeft;
556 cursor.y = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop) - document.documentElement.clientTop;
568 var lockvisible = false;
570 function lockview(event,id) {
571 event = event || window.event;
572 cursor = getPosition(event);
578 $.get('lockview/' + id, function(data) {
579 $('#panel').html(data);
580 $('#panel').css({ 'left': cursor.x + 5 , 'top': cursor.y + 5});
586 function lockviewhide() {
591 function post_comment(id) {
594 $('body').css('cursor', 'wait');
595 $("#comment-preview-inp-" + id).val("0");
598 $("#comment-edit-form-" + id).serialize(),
601 $("#comment-edit-wrapper-" + id).hide();
602 $("#comment-edit-text-" + id).val('');
603 var tarea = document.getElementById("comment-edit-text-" + id);
605 commentClose(tarea,id);
606 if(timer) clearTimeout(timer);
607 timer = setTimeout(NavUpdate,10);
611 window.location.href=data.reload;
620 function preview_comment(id) {
621 $("#comment-preview-inp-" + id).val("1");
622 $("#comment-edit-preview-" + id).show();
625 $("#comment-edit-form-" + id).serialize(),
628 $("#comment-edit-preview-" + id).html(data.preview);
629 $("#comment-edit-preview-" + id + " a").click(function() { return false; });
639 function showHideComments(id) {
640 if( $("#collapsed-comments-" + id).is(":visible")) {
641 $("#collapsed-comments-" + id).hide();
642 $("#hide-comments-" + id).html(window.showMore);
645 $("#collapsed-comments-" + id).show();
646 $("#hide-comments-" + id).html(window.showFewer);
652 function preview_post() {
653 $("#jot-preview").val("1");
654 $("#jot-preview-content").show();
657 $("#profile-jot-form").serialize(),
660 $("#jot-preview-content").html(data.preview);
661 $("#jot-preview-content" + " a").click(function() { return false; });
666 $("#jot-preview").val("0");
672 // unpause auto reloads if they are currently stopped
675 $('#pause').html('');
678 // load more network content (used for infinite scroll)
679 function loadScrollContent() {
680 if (lockLoadContent) return;
681 lockLoadContent = true;
683 $("#scroll-loader").fadeIn('normal');
685 // the page number to load is one higher than the actual
687 infinite_scroll.pageno+=1;
689 // get the raw content from the next page and insert this content
690 // right before "#conversation-end"
691 $.get('network?mode=raw' + infinite_scroll.reload_uri + '&page=' + infinite_scroll.pageno, function(data) {
692 $("#scroll-loader").hide();
693 if ($(data).length > 0) {
694 $(data).insertBefore('#conversation-end');
695 lockLoadContent = false;
697 $("#scroll-end").fadeIn('normal');
703 // Converts the binary representation of data to hex
706 // discuss at: http://phpjs.org/functions/bin2hex
707 // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
708 // + bugfixed by: Onno Marsman
709 // + bugfixed by: Linuxworld
710 // * example 1: bin2hex('Kev');
711 // * returns 1: '4b6576'
712 // * example 2: bin2hex(String.fromCharCode(0x00));
714 var v,i, f = 0, a = [];
718 for (i = 0; i<f; i++) {
719 a[i] = s.charCodeAt(i).toString(16).replace(/^([\da-f])$/,"0$1");
725 function groupChangeMember(gid, cid, sec_token) {
726 $('body .fakelink').css('cursor', 'wait');
727 $.get('group/' + gid + '/' + cid + "?t=" + sec_token, function(data) {
728 $('#group-update-wrapper').html(data);
729 $('body .fakelink').css('cursor', 'auto');
733 function profChangeMember(gid,cid) {
734 $('body .fakelink').css('cursor', 'wait');
735 $.get('profperm/' + gid + '/' + cid, function(data) {
736 $('#prof-update-wrapper').html(data);
737 $('body .fakelink').css('cursor', 'auto');
741 function contactgroupChangeMember(gid,cid) {
742 $('body').css('cursor', 'wait');
743 $.get('contactgroup/' + gid + '/' + cid, function(data) {
744 $('body').css('cursor', 'auto');
749 function checkboxhighlight(box) {
750 if($(box).is(':checked')) {
751 $(box).addClass('checkeditem');
754 $(box).removeClass('checkeditem');
758 function notifyMarkAll() {
759 $.get('notify/mark/all', function(data) {
760 if(timer) clearTimeout(timer);
761 timer = setTimeout(NavUpdate,1000);
767 * sprintf in javascript
768 * "{0} and {1}".format('zero','uno');
770 String.prototype.format = function() {
771 var formatted = this;
772 for (var i = 0; i < arguments.length; i++) {
773 var regexp = new RegExp('\\{'+i+'\\}', 'gi');
774 formatted = formatted.replace(regexp, arguments[i]);
779 Array.prototype.remove = function(item) {
780 to=undefined; from=this.indexOf(item);
781 var rest = this.slice((to || from) + 1 || this.length);
782 this.length = from < 0 ? this.length + from : from;
783 return this.push.apply(this, rest);
786 function previewTheme(elm) {
787 theme = $(elm).val();
788 $.getJSON('pretheme?f=&theme=' + theme,function(data) {
789 $('#theme-preview').html('<div id="theme-desc">' + data.desc + '</div><div id="theme-version">' + data.version + '</div><div id="theme-credits">' + data.credits + '</div><a href="' + data.img + '"><img src="' + data.img + '" width="320" height="240" alt="' + theme + '" /></a>');
794 // notification permission settings in localstorage
795 // set by settings page
796 function getNotificationPermission() {
797 if (window["Notification"] === undefined) {
800 if (Notification.permission === 'granted') {
801 var val = localStorage.getItem('notification-permissions');
802 if (val === null) return 'denied';
805 return Notification.permission;
810 * Show a dialog loaded from an url
811 * By defaults this load the url in an iframe in colorbox
812 * Themes can overwrite `show()` function to personalize it
819 * @return object colorbox
821 show : function (url) {
822 var size = Dialog._get_size();
823 return $.colorbox({href: url, iframe:true,innerWidth: size.width+'px',innerHeight: size.height+'px'})
827 * Show the Image browser dialog
830 * @param string id (optional)
833 * The name will be used to build the event name
834 * fired by image browser dialog when the user select
835 * an image. The optional id will be passed as argument
836 * to the event handler
838 doImageBrowser : function (name, id) {
839 var url = Dialog._get_url("image",name,id);
840 return Dialog.show(url);
844 * Show the File browser dialog
847 * @param string id (optional)
850 * The name will be used to build the event name
851 * fired by file browser dialog when the user select
852 * a file. The optional id will be passed as argument
853 * to the event handler
855 doFileBrowser : function (name, id) {
856 var url = Dialog._get_url("file",name,id);
857 return Dialog.show(url);
860 _get_url : function(type, name, id) {
862 if (id !== undefined) hash = hash + "-" + id;
863 return baseurl + "/fbrowser/"+type+"/?mode=minimal#"+hash;
866 _get_size: function() {
868 width: window.innerWidth-50,
869 height: window.innerHeight-100