1var src = null; 2var prev = null; 3var livetime = null; 4var msie = false; 5var stopped = false; 6var totStopped = false; 7var timer = null; 8var pr = 0; 9var liking = 0; 10var in_progress = false; 11var langSelect = false; 12var commentBusy = false; 13var last_popup_menu = null; 14var last_popup_button = null; 15var scroll_next = false; 16var next_page = 1; 17var page_load = true; 18var loadingPage = true; 19var pageHasMoreContent = true; 20var divmore_height = 400; 21var last_filestorage_id = null; 22var mediaPlaying = false; 23var liveRecurse = 0; 24var savedTitle = ''; 25var followUpPageLoad = false; 26var window_needs_alert = true; 27var expanded_items = []; 28var updateTimeout = []; 29const singlethread_modules = ['display', 'hq']; 30const redirect_modules = ['display', 'notify']; 31let b64mids = []; 32 33 34var page_cache = {}; 35 36// take care of tab/window reloads on channel change 37if(localStorage.getItem('uid') !== localUser.toString()) { 38 localStorage.clear(); 39 sessionStorage.clear(); 40 localStorage.setItem('uid', localUser.toString()); 41} 42 43window.onstorage = function(e) { 44 if(e.key === 'uid' && parseInt(e.newValue) !== localUser) { 45 if(window_needs_alert) { 46 window_needs_alert = false; 47 alert("Your identity has changed. Page reload required!"); 48 window.location.reload(); 49 return; 50 } 51 } 52} 53 54if ('serviceWorker' in navigator) { 55 navigator.serviceWorker.register('/ServiceWorker.js', { scope: '/' }).then(function(registration) { 56 console.log('Service worker registered. scope is', registration.scope); 57 }).catch(function(error) { 58 console.log('Service worker registration failed because ' + error); 59 }); 60} 61 62$.ajaxSetup({cache: false}); 63 64var tf = new Function('n', 's', 'var k = s.split("/")['+aStr['plural_func']+']; return (k ? k : s);'); 65 66$(document).ready(function() { 67 68 $(document).on('click focus', '.comment-edit-form', handle_comment_form); 69 $(document).on('click', '.conversation-settings-link', getConversationSettings); 70 $(document).on('click', '#settings_module_ajax_submit', postConversationSettings); 71 72 $(document).on('click', '#expand-aside', toggleAside); 73 74 $(document).on('click focus', '.comment-edit-form textarea', function(e) { 75 if(! this.autocomplete_handled) { 76 /* autocomplete @nicknames */ 77 $(this).editor_autocomplete(baseurl+"/acl?n=1"); 78 /* autocomplete bbcode */ 79 $(this).bbco_autocomplete('bbcode'); 80 81 this.autocomplete_handled = true; 82 } 83 84 }); 85 86 $(document).on('keydown', '.comment-edit-form textarea.expanded', function (e) { 87 if (e.ctrlKey && e.keyCode === 13) { 88 post_comment(this.id.replace('comment-edit-text-','')); 89 } 90 }); 91 92 document.addEventListener('click', function(event) { 93 // Only handle clicks on .wall-item-reaction or its children 94 const target = event.target.closest('.wall-item-reaction'); 95 if (!target) return; 96 97 let doRequest = true; 98 const isUserClick = event.isTrusted; 99 100 // Destructure relevant data attributes 101 const { itemId: id, itemMid: mid, itemParent: parentId, itemUuid: uuid, itemVerb: verb } = target.dataset; 102 const isCommentBtn = target.classList.contains('wall-item-comment'); 103 104 if (isCommentBtn) { 105 if (id === parentId) { 106 // Handle blog mode 107 target.classList.add('disabled'); 108 document.getElementById(`load-more-progress-wrapper-${id}`).classList.remove('d-none'); 109 document.getElementById(`load-more-${id}`).classList.remove('d-none') 110 request(id, mid, 'load', parentId, uuid, isUserClick); 111 return; 112 } 113 114 // Get relevant DOM elements 115 const threadWrapper = document.getElementById(`thread-wrapper-${id}`); 116 const parentWrapper = document.getElementById(`thread-wrapper-${parentId}`); 117 const subThreadWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${id}`); 118 const parentSubThreadWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${parentId}`); 119 120 // Query related sub-thread and highlight elements 121 const parentIndentedThreads = document.querySelectorAll(`#wall-item-sub-thread-wrapper-${parentId} .wall-item-sub-thread-wrapper.item-indent`); 122 123 let ancestorIds = []; 124 125 doRequest = !subThreadWrapper.children.length; 126 127 // Set visual styles using UUID 128 subThreadWrapper.style.setProperty('--hz-item-indent', stringToHslColor(uuid)); 129 threadWrapper.style.setProperty('--hz-item-highlight', stringToHslColor(uuid)); 130 threadWrapper.style.setProperty('--hz-wall-item-expanded-before-content', '"' + aStr.dblclick_to_exit_zoom + '"'); 131 132 // Clear previous highlights 133 parentSubThreadWrapper.querySelectorAll('.thread-wrapper.item-highlight').forEach(el => el.classList.remove('item-highlight')); 134 135 if (isUserClick && parentIndentedThreads.length === 0 && !subThreadWrapper.children.length) { 136 // Handle first-time expansion and highlighting but not for toplevels (blog mode) 137 threadWrapper.classList.add('item-highlight'); 138 } else { 139 // Handle indentation and zooming 140 let ancestor = subThreadWrapper.parentElement; 141 ancestorIds.push(ancestor.id.slice(15)); // thread-wrapper-1234 142 143 while (ancestor) { 144 if (ancestor.classList.contains('item-indent') && ancestor.classList.contains('wall-item-sub-thread-wrapper')) { 145 ancestorIds.push(ancestor.parentElement.id.slice(15)); 146 } 147 ancestor = ancestor.parentElement; 148 } 149 150 ancestorIds.reverse(); 151 152 if (ancestorIds.length > 3) { 153 // Handle zooming in 154 let firstWrapper = document.getElementById('thread-wrapper-' + ancestorIds[0]); 155 let firstSubWrapper = document.getElementById('wall-item-sub-thread-wrapper-' + ancestorIds[0]); 156 157 firstWrapper.querySelector('.wall-item-comment').classList.remove('indented'); 158 firstWrapper.classList.remove('wall-item-expanded', 'shadow'); 159 firstSubWrapper.classList.remove('item-indent'); 160 161 let newFirstWrapper = document.getElementById('thread-wrapper-' + ancestorIds[1]) 162 let newFirstSubWrapper = document.getElementById('wall-item-sub-thread-wrapper-' + ancestorIds[1]) 163 164 newFirstWrapper.classList.add('wall-item-expanded', 'shadow'); 165 parentWrapper.classList.add('wall-item-backdrop'); 166 167 // Exit zoom on double-click 168 newFirstWrapper.addEventListener('dblclick', function() { 169 parentWrapper.querySelectorAll('.wall-item-comment.indented').forEach(el => el.classList.remove('indented')); 170 parentWrapper.querySelectorAll('.wall-item-comment.collapsed').forEach(el => el.classList.remove('collapsed')); 171 parentWrapper.classList.remove('wall-item-backdrop'); 172 parentWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.item-indent').forEach(el => el.classList.remove('item-indent')); 173 parentWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.d-none').forEach(el => el.classList.remove('d-none')); 174 parentWrapper.querySelectorAll('.thread-wrapper.wall-item-expanded').forEach(el => el.classList.remove('wall-item-expanded', 'shadow')); 175 }, { once: true }); 176 } 177 178 // Toggle sub-thread visibility if indented 179 if (isUserClick && target.classList.contains('indented')) { 180 doRequest = false;
181 subThreadWrapper.classList.toggle('d-none'); 182 target.classList.toggle('collapsed'); 183 } 184 185 // Indenting of already expanded but flattened items 186 if (isUserClick && subThreadWrapper.classList.contains('item-expanded') && !subThreadWrapper.classList.contains('item-indent')) { 187 doRequest = false; 188 189 threadWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.item-expanded').forEach(function (el, i) { 190 el.classList.add('item-indent'); 191 192 el.querySelectorAll('.wall-item-comment.expanded').forEach(function (el, i) { 193 el.classList.add('collapsed', 'indented'); 194 }); 195 196 // Collapse everything below the first level 197 if (i > 0) { 198 el.classList.add('d-none'); 199 } 200 }); 201 } 202 203 // Indent the subthread 204 subThreadWrapper.classList.add('item-indent', 'item-expanded'); 205 206 // Mark as indented after visibility toggle 207 target.classList.add('indented'); 208 } 209 210 // Mark as expanded 211 target.classList.add('expanded'); 212 } 213 214 if (doRequest) { 215 request(id, mid, verb, parentId, uuid, isUserClick); 216 } 217 }); 218 219 document.addEventListener('click', function(event) { 220 const targetElement = event.target.closest('.dropdown-item-expand'); 221 if (!targetElement) return; 222 223 event.preventDefault(); 224 225 const id = targetElement.dataset.itemId; 226 const subWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${id}`); 227 228 subWrapper.innerHTML = ''; 229 autoExpand(id); 230 }); 231 232 // @hilmar |-> 233 if ( typeof(window.tao) == 'undefined' ) { 234 window.tao = {}; 235 } 236 if ( typeof(window.tao.zin) == 'undefined' ) { 237 tao.zin = { syslc: '', syslcs: {}, htm: '', me: '', debug: '' }; 238 tao.zin.axim = '<i class="zinpax bi bi-arrow-repeat"></i>'; 239 $('.navbar-app[href*="/lang"]').attr({"aria-expand": "true", "id": "zintog"}) 240 .removeAttr('href').addClass('zinlcx zinlcxp dropdown dropdown-toggle').css('cursor','pointer'); 241 $('.nav-link[href*="/lang"]').addClass('zinlcxmi zinlcx').removeAttr('href').css('cursor','pointer'); 242 } 243 $('.zinlcx').on('click', function(e) { 244 if (tao.zin.syslc == '') { 245 $('.zinlcx').append(tao.zin.axim); 246 $.ajax({ 247 type: 'POST', url: 'lang', 248 data: { zinlc: '??' } 249 }).done( function(re) { 250 tao.zin.re = JSON.parse(re); 251 tao.zin.syslc = tao.zin.re.lc; 252 tao.zin.syslcs = tao.zin.re.lcs; 253 tao.zin.htm = '<ul class="zinlcs">'; 254 $.each( tao.zin.syslcs, function( k, v ) { 255 tao.zin.htm += '<li><a id="zinlc' + k + '" class="zinlc fakelink">' + k + ' ' + v + '</a></li>'; 256 }); 257 tao.zin.htm += '</ul>'; 258 $('.zinpax').remove(); 259 $('.zinlcx').append(tao.zin.htm); 260 $('.zinlcxp > ul').addClass('dropdown dropdown-menu dropdown-menu-end mt-2 show').css('right', 0); 261 }); 262 return false; 263 } else { 264 if (e.target.id == '' || e.target.id == 'zintog') { 265 // noop click on lang again 266 return false; 267 } 268 tao.zin.me = e.target.id.substr(5); 269 $('#right_aside_wrapper').append(tao.zin.axim); 270 $.ajax({ 271 type: 'POST', url: 'lang', 272 data: { zinlc: tao.zin.me } 273 }).done( function(re) { 274 tao.zin.re = JSON.parse(re); 275 location.reload(true); 276 }); 277 } 278 }); 279 $('#zintog').on('click', function() { 280 $('.zinlcs').toggle(); 281 }); 282 $('#dropdown-menu').on('shown.bs.dropdown', function() { 283 tao.zin.debug += 'e,'; 284 //$('.zinlcs').removeAttr('display'); 285 }) 286 .on('mouseleave', function() { 287 $(this).trigger('click'); 288 }); 289 // @hilmar <-| 290 291 $(document).on('click', '.notification, .message', function(e) { 292 let b64mid = this.dataset.b64mid; 293 let notify_id = this.dataset.notify_id; 294 let path = $(this)[0].pathname.split('/')[1]; 295 let stateObj = { b64mid: b64mid }; 296 297 if (!b64mid && !notify_id) { 298 return; 299 } 300 301 if (document.querySelector('main').classList.contains('region_1-on')) { 302 toggleAside(); 303 } 304 305 if (localUser && redirect_modules.indexOf(path) !== -1) { 306 path = 'hq'; 307 } 308 309 if (notify_id) { 310 $.ajax({ 311 type: 'post', 312 url: 'notify', 313 data: { 314 'notify_id' : notify_id 315 }, 316 async: ((module !== path) ? false : true) 317 }); 318 } 319 320 if (module !== path) { 321 e.preventDefault(); 322 window.location.href = path + '/' + b64mid; 323 } 324 else { 325 if (singlethread_modules.indexOf(module) !== -1) { 326 history.pushState(stateObj, '', module + '/' + b64mid); 327 } 328 329 if (b64mid) { 330 e.preventDefault();
331 if (!page_load) { 332 prepareLiveUpdate(b64mid, notify_id); 333 $('.message').removeClass('active'); 334 $('[data-b64mid="' + b64mid + '"].message').addClass('active'); 335 $('[data-b64mid="' + b64mid + '"].message .unseen_count').remove(); 336 } 337 } 338 } 339 }); 340 341 window.onpopstate = function(e) { 342 if(e.state !== null && e.state.b64mid !== bParam_mid) { 343 prepareLiveUpdate(e.state.b64mid, ''); 344 $('.message').removeClass('active'); 345 $('[data-b64mid="' + e.state.b64mid + '"].message').addClass('active'); 346 } 347 348 }; 349 350 savedTitle = document.title; 351 352 updateInit(); 353 354 if (document.getElementById('content-complete')) { 355 pageHasMoreContent = false; 356 } 357 358 document.addEventListener('hz:updateConvItems', function(e) { 359 if (!bParam_mid) { 360 cache_next_page(); 361 } 362 }); 363 364 document.addEventListener('hz:handleNetworkNotificationsItems', function(e) { 365 push_notification( 366 e.detail.name, 367 $('<p>' + e.detail.message + '</p>').text(), 368 e.detail.b64mid 369 ); 370 }); 371}); 372 373function getConversationSettings() { 374 $.get('settings/conversation/?aj=1',function(data) { 375 $('#conversation_settings_body').html(data); 376 }); 377} 378 379function postConversationSettings() { 380 if(next_page === 1) { 381 page_load = true; 382 } 383 384 $.post( 385 'settings/conversation', 386 $('#settings_module_ajax_form').serialize() + "&auto_update=" + next_page 387 ).done(function() { 388 $('#conversation_settings').modal('hide'); 389 toast('Conversation features updated', 'info'); 390 updateInit(); 391 }); 392 393 return false; 394} 395 396function datasrc2src(selector) { 397 $(selector).each(function(i, el) { 398 $(el).attr("src", $(el).data("src")); 399 $(el).removeAttr("data-src"); 400 }); 401} 402 403function confirmDelete() { 404 return confirm(aStr.delitem); 405} 406 407function handle_comment_form(e) { 408 e.stopPropagation(); 409 410 //handle eventual expanded forms 411 var expanded = $('.comment-edit-text.expanded'); 412 var i = 0; 413 414 if(expanded.length) { 415 expanded.each(function() { 416 var ex_form = $(expanded[i].form); 417 var ex_fields = ex_form.find(':input[type=text], textarea'); 418 var ex_fields_empty = true; 419 420 ex_fields.each(function() { 421 if($(this).val() != '') 422 ex_fields_empty = false; 423 }); 424 if(ex_fields_empty) { 425 ex_form.find('.comment-edit-text').removeClass('expanded').attr('placeholder', aStr.comment); 426 ex_form.find(':not(.comment-edit-text)').hide(); 427 } 428 i++; 429 }); 430 } 431 432 // handle clicked form 433 var form = $(this); 434 435 var fields = form.find(':input[type=text], textarea'); 436 var fields_empty = true; 437 438 if(form.find('.comment-edit-text').length) { 439 var commentElm = form.find('.comment-edit-text').attr('id'); 440 var submitElm = commentElm.replace(/text/,'submit'); 441 442 $('#' + commentElm).addClass('expanded').removeAttr('placeholder'); 443 $('#' + commentElm).attr('tabindex','9'); 444 $('#' + submitElm).attr('tabindex','10'); 445 446 form.find(':not(:visible)').show(); 447 } 448 449 450 // handle click outside of form (close empty forms) 451 $(document).one('click', function(e) { 452 fields.each(function() { 453 if($(this).val() != '') 454 fields_empty = false; 455 }); 456 if(fields_empty) { 457 var emptyCommentElm = form.find('.comment-edit-text').attr('id'); 458 var emptySubmitElm = commentElm.replace(/text/,'submit'); 459 460 $('#' + emptyCommentElm).removeClass('expanded').attr('placeholder', aStr.comment); 461 $('#' + emptyCommentElm).removeAttr('tabindex'); 462 $('#' + emptySubmitElm).removeAttr('tabindex'); 463 form.find(':not(.comment-edit-text)').hide(); 464 form.find(':input[name=parent]').val(emptyCommentElm.replace(/\D/g,'')); 465 var btn = form.find(':button[type=submit]').html(); 466 form.find(':button[type=submit]').html(btn.replace(/<[^>]*>/g, '').trim()); 467 form.find(':button[type=submit]').prop('title', ''); 468 } 469 }); 470 471 var commentSaveTimer = null; 472 var emptyCommentElm = form.find('.comment-edit-text').attr('id'); 473 var convId = emptyCommentElm.replace('comment-edit-text-',''); 474 475 // in case parent input is set use it as convId 476 const parentInputVal = form.find(':input[name=parent]').val(); 477 if (parentInputVal) { 478 convId = parentInputVal; 479 } 480 481 $('#' + emptyCommentElm).on('focusout',function(e){ 482 if(commentSaveTimer) 483 clearTimeout(commentSaveTimer); 484 commentSaveChanges(convId,true); 485 commentSaveTimer = null; 486 $('#' + emptyCommentElm).off(); 487 }); 488 489 $('#' + emptyCommentElm).on('focusin', function (e){ 490 commentSaveTimer = setTimeout(function () { 491 commentSaveChanges(convId,false); 492 },10000); 493 }); 494 495 function commentSaveChanges(convId, isFinal) { 496 497 if(typeof isFinal === 'undefined') 498 isFinal = false;
499 500 if(auto_save_draft) { 501 tmp = $('#' + emptyCommentElm).val(); 502 if(tmp) { 503 localStorage.setItem("comment_body-" + convId, tmp); 504 } 505 else { 506 localStorage.removeItem("comment_body-" + convId); 507 } 508 if( !isFinal) { 509 commentSaveTimer = setTimeout(commentSaveChanges,10000,convId); 510 } 511 } 512 } 513 514} 515 516 517 518function commentClose(obj, id) { 519 if(obj.value === '') { 520 $("#comment-edit-text-" + id).removeClass("expanded"); 521 $("#mod-cmnt-wrap-" + id).hide(); 522 $("#comment-tools-" + id).hide(); 523 $("#comment-edit-anon-" + id).hide(); 524 return true; 525 } 526 return false; 527} 528 529function showHideCommentBox(id) { 530 if( $('#comment-edit-form-' + id).is(':visible')) { 531 $('#comment-edit-form-' + id).hide(); 532 } else { 533 $('#comment-edit-form-' + id).show(); 534 } 535} 536 537function commentInsert(obj, id) { 538 var tmpStr = $("#comment-edit-text-" + id).val(); 539 if(tmpStr == '$comment') { 540 tmpStr = ''; 541 $("#comment-edit-text-" + id).addClass("expanded"); 542 openMenu("comment-tools-" + id); 543 } 544 var ins = $(obj).html(); 545 ins = ins.replace('<','<'); 546 ins = ins.replace('>','>'); 547 ins = ins.replace('&','&'); 548 ins = ins.replace('"','"'); 549 $("#comment-edit-text-" + id).val(tmpStr + ins); 550} 551 552function insertbbcomment(comment, BBcode, id) { 553 // allow themes to override this 554 if(typeof(insertFormatting) != 'undefined') 555 return(insertFormatting(comment, BBcode, id)); 556 557 var urlprefix = ((BBcode == 'url') ? '#^' : ''); 558 559 var tmpStr = $("#comment-edit-text-" + id).val(); 560 if(tmpStr == comment) { 561 tmpStr = ""; 562 $("#comment-edit-text-" + id).addClass("expanded"); 563 openMenu("comment-tools-" + id); 564 $("#comment-edit-text-" + id).val(tmpStr); 565 } 566 567 textarea = document.getElementById("comment-edit-text-" +id); 568 if (document.selection) { 569 textarea.focus(); 570 selected = document.selection.createRange(); 571 selected.text = urlprefix+"["+BBcode+"]" + selected.text + "[/"+BBcode+"]"; 572 } else if (textarea.selectionStart || textarea.selectionStart == "0") { 573 var start = textarea.selectionStart; 574 var end = textarea.selectionEnd; 575 textarea.value = textarea.value.substring(0, start) + urlprefix+"["+BBcode+"]" + textarea.value.substring(start, end) + "[/"+BBcode+"]" + textarea.value.substring(end, textarea.value.length); 576 } 577 return true; 578} 579 580function inserteditortag(BBcode, id) { 581 // allow themes to override this 582 if(typeof(insertEditorFormatting) != 'undefined') 583 return(insertEditorFormatting(BBcode)); 584 585 textarea = document.getElementById(id); 586 if (document.selection) { 587 textarea.focus(); 588 selected = document.selection.createRange(); 589 selected.text = urlprefix+"["+BBcode+"]" + selected.text + "[/"+BBcode+"]"; 590 } else if (textarea.selectionStart || textarea.selectionStart == "0") { 591 var start = textarea.selectionStart; 592 var end = textarea.selectionEnd; 593 textarea.value = textarea.value.substring(0, start) + "["+BBcode+"]" + textarea.value.substring(start, end) + "[/"+BBcode+"]" + textarea.value.substring(end, textarea.value.length); 594 } 595 return true; 596} 597 598function insertCommentAttach(comment,id) { 599 activeCommentID = id; 600 activeCommentText = comment; 601 $('body').css('cursor', 'wait'); 602 $('#invisible-comment-upload').trigger('click'); 603 return false; 604} 605 606function insertCommentEmbed(comment,id) { 607 activeCommentID = id; 608 activeCommentText = comment; 609 initializeEmbedPhotoDialog(); 610 return false; 611} 612 613function insertCommentURL(comment, id) { 614 reply = prompt(aStr.linkurl); 615 if(reply && reply.length) { 616 reply = bin2hex(reply); 617 $('body').css('cursor', 'wait'); 618 $.get('linkinfo?binurl=' + reply, function(data) { 619 var tmpStr = $("#comment-edit-text-" + id).val(); 620 if(tmpStr == comment) { 621 tmpStr = ""; 622 $("#comment-edit-text-" + id).addClass("expanded"); 623 openMenu("comment-tools-" + id); 624 $("#comment-edit-text-" + id).val(tmpStr); 625 } 626 627 textarea = document.getElementById("comment-edit-text-" +id); 628 textarea.value = textarea.value + data; 629 preview_comment(id); 630 $('body').css('cursor', 'auto'); 631 }); 632 } 633 return true; 634} 635 636function doFollowAuthor(url) { 637 $.get(url, function(data) { notificationsUpdate(); }); 638 return true; 639} 640 641 642function viewsrc(id) { 643 $.colorbox({href: 'viewsrc/' + id, maxWidth: '80%', maxHeight: '80%' }); 644} 645 646function showHideComments(id) { 647 let collapsedComments = document.getElementById('collapsed-comments-' + id); 648 let hideCommentsLabel = document.getElementById('hide-comments-label-' + id); 649 let hideCommentsTotal = document.getElementById('hide-comments-total-' + id); 650 let hideCommentsIcon = document.getElementById('hide-comments-icon-' + id); 651 let isCollapsed = collapsedComments.style.display === 'none'; 652 653 collapsedComments.style.display = isCollapsed ? '' : 'none'; 654 hideCommentsLabel.textContent = isCollapsed ? hideCommentsLabel.dataset.expanded : hideCommentsLabel.dataset.collapsed; 655 656 if (hideCommentsTotal) { 657 hideCommentsTotal.style.display = isCollapsed ? 'none' : ''; 658 } 659 660 let oldClass = isCollapsed ? 'bi-chevron-down' : 'bi-chevron-up'; 661 let newClass = isCollapsed ? 'bi-chevron-up' : 'bi-chevron-down'; 662 hideCommentsIcon.classList.replace(oldClass, newClass); 663} 664 665function openClose(theID, display) { 666 if (typeof display == typeof undefined) 667 display = 'block'; 668 669 if(document.getElementById(theID).style.display == display) { 670 document.getElementById(theID).style.display = "none"; 671 } else { 672 document.getElementById(theID).style.display = display; 673 } 674} 675 676function openCloseTR(theID) { 677 if(document.getElementById(theID).style.display == "table-row") { 678 document.getElementById(theID).style.display = "none"; 679 } else { 680 document.getElementById(theID).style.display = "table-row"; 681 } 682} 683 684function closeOpen(theID, display) { 685 if (typeof display == typeof undefined) 686 display = 'block'; 687 if(document.getElementById(theID).style.display == "none") { 688 document.getElementById(theID).style.display = display; 689 } else { 690 document.getElementById(theID).style.display = "none"; 691 } 692} 693 694function openMenu(theID) { 695 document.getElementById(theID).style.display = "block"; 696} 697 698function closeMenu(theID) { 699 document.getElementById(theID).style.display = "none"; 700} 701 702function markRead(notifType) { 703 $.get('notifications?markRead='+notifType); 704 $('.' + notifType + '-button').fadeOut(function() { 705 $("." + notifType + "-update").html('0'); 706 $('#nav-' + notifType + '-menu').html(''); 707 $('#nav-' + notifType + '-sub').removeClass('show'); 708 sessionStorage.removeItem('notification_open'); 709 $(document).trigger('hz:sse_setNotificationsStatus'); 710 }); 711 712 713} 714 715function markItemRead(itemId) { 716 $.get('notifications?markItemRead='+itemId); 717 $('.unseen-wall-indicator-'+itemId).remove(); 718} 719 720function contextualHelp() { 721 var container = $("#contextual-help-content"); 722 723 if(container.hasClass('contextual-help-content-open')) { 724 container.removeClass('contextual-help-content-open'); 725 $('main').css('margin-top', '') 726 } 727 else { 728 container.addClass('contextual-help-content-open'); 729 var mainTop = container.outerHeight(true); 730 $('main').css('margin-top', mainTop + 'px'); 731 } 732} 733 734function contextualHelpFocus(target, openSidePanel) { 735 if($(target).length) { 736 if (openSidePanel) { 737 $("main").addClass('region_1-on'); // Open the side panel to highlight element 738 } 739 else { 740 $("main").removeClass('region_1-on'); 741 } 742 743 var css_position = $(target).parent().css('position'); 744 if (css_position === 'fixed') { 745 $(target).parent().css('position', 'static'); 746 } 747 748 $('html,body').animate({ scrollTop: $(target).offset().top - $('nav').outerHeight(true) - $('#contextual-help-content').outerHeight(true)}, 'slow'); 749 for (i = 0; i < 3; i++) { 750 $(target).fadeTo('slow', 0.1).fadeTo('slow', 1.0); 751 } 752 753 $(target).parent().css('position', css_position); 754 } 755} 756 757function updatePageItems(mode, data) { 758 759 $(document).trigger('hz:updatePageItems'); 760 761 if(mode === 'append') { 762 newitemcount = 0; 763 $(data).each(function() { 764 $('#page-end').before($(this)); 765 newitemcount++; 766 }); 767 768 if(loadingPage) { 769 loadingPage = false;
770 } 771 } 772 773 var e = document.getElementById('content-complete'); 774 if(e) { 775 pageHasMoreContent = false; 776 } else { 777 if (newitemcount < 1) { 778 pageUpdate(); 779 } 780 } 781 782 collapseHeight(); 783} 784 785function updateConvItems(mode, data) { 786 let scroll_position = window.scrollY; 787 let b64mids = []; 788 789 // Parse the data string into a DOM object 790 let parser = new DOMParser(); 791 let doc = parser.parseFromString(data, 'text/html'); 792 793 if (mode !== 'update') { 794 document.dispatchEvent(new Event('hz:updateConvItems')); 795 } 796 797 let prev, next; 798 if (mode === 'update' || mode === 'replace') { 799 prev = document.getElementById('threads-begin'); 800 } 801 if (mode === 'append') { 802 next = document.getElementById('threads-end'); 803 } 804 805 doc.querySelectorAll('.thread-wrapper').forEach(function (elem) { 806 if (elem.classList.contains('toplevel_item')) { 807 let ident = elem.id; 808 let convId = ident.replace('thread-wrapper-', ''); 809 let commentWrap = elem.querySelector('.collapsed-comments')?.id; 810 811 let itmId = 0; 812 let isVisible = false; 813 814 // figure out the comment state 815 if (commentWrap !== undefined) { 816 itmId = commentWrap.replace('collapsed-comments-', ''); 817 } 818 819 let collapsedComment = document.getElementById('collapsed-comments-' + itmId); 820 if (collapsedComment && collapsedComment.style.display !== 'none') { 821 isVisible = true; 822 } 823 824 // insert the content according to the mode and first_page 825 // and whether or not the content exists already (overwrite it) 826 let existingElem = document.getElementById(ident); 827 if (!existingElem) { 828 if ((mode === 'update' || mode === 'replace') && profile_page == 1) { 829 if (prev) { 830 prev.after(elem); 831 prev = elem; 832 } 833 } 834 if (mode === 'append') { 835 if (next) { 836 next.before(elem); 837 } 838 } 839 } else { 840 existingElem.replaceWith(elem); 841 } 842 843 // DOMParser will prevent scripts from execution for security reasons. 844 // We remove all scripts but possibly injected some from 845 // addons like for example gallery later. 846 // TODO: make the script run from the addon itself. 847 let scripts = elem.querySelectorAll('script'); 848 scripts.forEach(script => { 849 let scriptContent = script.textContent || script.innerText; 850 eval(scriptContent); // Execute the script 851 }); 852 853 // set the comment state to the state we discovered earlier 854 if (isVisible) { 855 showHideComments(itmId); 856 } 857 858 let commentBody = localStorage.getItem("comment_body-" + convId); 859 if (commentBody) { 860 let commentElm = document.getElementById('comment-edit-text-' + convId); 861 if (auto_save_draft && commentElm) { 862 if (commentElm.value === '') { 863 let commentForm = document.getElementById('comment-edit-form-' + convId); 864 if (commentForm) { 865 commentForm.style.display = 'block'; 866 } 867 commentElm.classList.add("expanded"); 868 openMenu("comment-tools-" + convId); 869 commentElm.value = commentBody; 870 } 871 } else { 872 localStorage.removeItem("comment_body-" + convId); 873 } 874 } 875 } 876 877 if ((mode === 'append' || mode === 'replace') && loadingPage) { 878 loadingPage = false; 879 } 880 881 // if single thread view and the item has a title, display it in the title bar 882 if (mode === 'replace') { 883 if (window.location.search.includes("mid=") || window.location.pathname.includes("display")) { 884 let titleElem = document.querySelector(".wall-item-title"); 885 if (titleElem) { 886 let title = titleElem.textContent.trim(); 887 if (title) { 888 savedTitle = title + ' ' + savedTitle; 889 document.title = title; 890 } 891 } 892 } 893 } 894 895 let data_json = JSON.parse(elem.dataset.b64mids); 896 897 if (elem.parentNode.classList.contains('wall-item-sub-thread-wrapper') && elem.parentNode.children.length) { 898 // Set the highlight state 899 if (data_json.includes(bParam_mid) && !elem.parentNode.parentNode.classList.contains('toplevel_item')) { 900 elem.parentNode.parentNode.classList.add('item-highlight'); 901 document.documentElement.style.setProperty('--hz-item-highlight', stringToHslColor(JSON.parse(elem.parentNode.parentNode.dataset.b64mids)[0])); 902 } 903 904 let elemSubThreadWrapper = elem.querySelector('.wall-item-sub-thread-wrapper'); 905 let elemCommentButton = elem.querySelector('.wall-item-comment'); 906 907 // Set the button and sub-thread-wrapper state 908 if (elemCommentButton && elemSubThreadWrapper.children.length) { 909 elemCommentButton.classList.add('expanded'); 910 } 911 912 elem.parentNode.classList.add('item-expanded'); 913 } 914 915 b64mids.push(...data_json); 916 }); 917 918 document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); 919 920 window.scrollTo(0, scroll_position); 921 922 if (followUpPageLoad) { 923 document.dispatchEvent(new Event('hz:sse_bs_counts')); 924 } else { 925 document.dispatchEvent(new Event('hz:sse_bs_init')); 926 } 927 928 if (commentBusy) { 929 commentBusy = false;
930 document.body.style.cursor = 'auto'; 931 } 932 933 // Setup to determine if the media player is playing. This affects some content loading decisions. 934 ['playing', 'pause'].forEach(event => { 935 document.querySelectorAll('video, audio').forEach(media => { 936 media.removeEventListener(event, mediaHandler); 937 media.addEventListener(event, mediaHandler); 938 }); 939 }); 940 941 function mediaHandler(event) { 942 mediaPlaying = event.type === 'playing'; 943 } 944 945 if (bParam_mid && mode === 'replace') { 946 scrollToItem(); 947 } 948 949 // A slight delay to give the browser time to render images. 950 // Otherwise height calculation might not be accurate. 951 setTimeout(collapseHeight, 10); 952 953 // Reset rotators and cursors we may have set before reaching this place 954 let pageSpinner = document.getElementById("page-spinner"); 955 if (pageSpinner) { 956 pageSpinner.style.display = 'none'; 957 } 958 959 let profileJotTextLoading = document.getElementById("profile-jot-text-loading"); 960 if (profileJotTextLoading) { 961 profileJotTextLoading.style.display = 'none'; 962 } 963 964 followUpPageLoad = true; 965 966 967 updateRelativeTime('.autotime'); 968} 969 970function imagesLoaded(elements, callback) { 971 let loadedCount = 0; 972 let totalImages = 0; 973 let timeoutId; 974 let timedOut = false; 975 const timeout = 10000; 976 const processed = new Set(); // Use a Set for efficient lookup 977 978 // Helper function to extract img elements from an HTML string 979 function extractImagesFromHtml(htmlString) { 980 const tempDiv = document.createElement('div'); 981 tempDiv.innerHTML = htmlString; 982 return tempDiv.querySelectorAll('.wall-item-body img, .wall-photo-item img'); 983 } 984 985 function checkComplete(src) { 986 // If preloading timed out make sure to not call the callback again 987 // in case a load event listener fires later. 988 if (timedOut) return; 989 990 // Skip processing if image has already been processed 991 if (processed.has(src)) return; 992 993 processed.add(src); 994 loadedCount++; 995 996 // Update progress 997 const progress = Math.round((loadedCount * 100) / totalImages); 998 document.getElementById('image_counter').innerText = `${progress}%`; 999 1000 // If all images are loaded, trigger the callback 1001 if (loadedCount === totalImages) { 1002 document.getElementById('image_counter').innerText = ''; 1003 clearTimeout(timeoutId); 1004 callback(); 1005 } 1006 } 1007 1008 // Convert HTML string to img elements if necessary 1009 if (typeof elements === 'string') { 1010 elements = extractImagesFromHtml(elements); 1011 } 1012 1013 // Exit early if there are no images to load 1014 if (!elements || elements.length === 0) { 1015 callback(); 1016 return; 1017 } 1018 1019 // Filter valid image elements (only img with src attribute) 1020 const images = Array.from(elements) 1021 .filter((element) => element.tagName.toLowerCase() === 'img' && element.src) 1022 .filter((element, index, self) => 1023 index === self.findIndex(e => e.src === element.src) // Avoid duplicates 1024 ); 1025 1026 // If no images are found, call the callback immediately 1027 if (images.length === 0) { 1028 callback(); 1029 return; 1030 } 1031 1032 totalImages = images.length; 1033 1034 // Set timeout for the loading process 1035 timeoutId = setTimeout(() => { 1036 console.warn(`Image loading timed out after ${timeout}ms`); 1037 document.getElementById('image_counter').innerText = ''; 1038 callback(false); 1039 timedOut = true; 1040 }, timeout); 1041 1042 // Iterate through images to add load and error event listeners 1043 images.forEach((img) => { 1044 let new_img = new Image(); 1045 new_img.src = img.src; 1046 1047 if (new_img.complete && new_img.naturalHeight > 0) { 1048 // Image is already loaded, handle immediately 1049 // console.log(`Image cached: ${new_img.src}`); 1050 checkComplete(new_img.src); 1051 } else { 1052 // Add event listeners for load and error events 1053 new_img.addEventListener('load', () => { 1054 // console.log(`Image loaded: ${new_img.src}`); 1055 checkComplete(new_img.src) 1056 }); 1057 new_img.addEventListener('error', () => { 1058 console.log(`Image failed to load: ${new_img.src}`); 1059 checkComplete(new_img.src); 1060 }); 1061 } 1062 1063 }); 1064} 1065 1066function updateRelativeTime(selector) { 1067 // Get all elements with the given selector 1068 const timeElements = document.querySelectorAll(selector); 1069 if (timeElements.length === 0) return; 1070 1071 // Default time style and map for supported options 1072 const styleMap = ['narrow', 'short', 'long']; 1073 const style = styleMap.find(s => selector.includes(s)) || 'long'; 1074 1075 // Create an instance of RelativeTimeFormat 1076 const rtf = new Intl.RelativeTimeFormat(lang, { 1077 localeMatcher: 'best fit', // 'best fit' or 'lookup' 1078 numeric: 'always', // 'always' or 'auto' 1079 style: style // 'long', 'short', or 'narrow' 1080 }); 1081 1082 const now = Date.now(); // Get the current time only once 1083 1084 // Helper function to calculate the time difference in appropriate units 1085 function getRelativeTime(diffInSeconds) { 1086 const isFuture = diffInSeconds > 0; 1087 const absDiffInSeconds = Math.abs(diffInSeconds); 1088 1089 if (absDiffInSeconds < 60) return { value: absDiffInSeconds, unit: 'second' }; 1090 if (absDiffInSeconds < 3600) return { value: Math.floor(absDiffInSeconds / 60), unit: 'minute' }; 1091 if (absDiffInSeconds < 86400) return { value: Math.floor(absDiffInSeconds / 3600), unit: 'hour' }; 1092 if (absDiffInSeconds < 2592000) return { value: Math.floor(absDiffInSeconds / 86400), unit: 'day' }; 1093 if (absDiffInSeconds < 31536000) return { value: Math.floor(absDiffInSeconds / 2592000), unit: 'month' }; 1094 return { value: Math.floor(absDiffInSeconds / 31536000), unit: 'year' }; 1095 } 1096 1097 // Process each element
1098 timeElements.forEach(element => { 1099 const timestamp = new Date(element.title).getTime(); 1100 if (isNaN(timestamp)) return; // Skip invalid timestamps 1101 1102 const diffInSeconds = Math.floor((timestamp - now) / 1000); // Time difference in seconds 1103 const { value, unit } = getRelativeTime(diffInSeconds); 1104 1105 // Format the relative time and set it as the element's text 1106 const formattedTime = rtf.format(diffInSeconds > 0 ? value : -value, unit); 1107 element.textContent = formattedTime; 1108 }); 1109 1110 // Avoid duplicate timeout registrations for the same selector 1111 if (!updateTimeout.includes(selector)) { 1112 updateTimeout.push(selector); 1113 setTimeout(() => updateRelativeTime(selector), 60000); // Re-run the update every 60 seconds 1114 } 1115} 1116 1117function scrollToItem() { 1118 // auto-scroll to a particular comment in a thread (designated by mid) when in single-thread mode 1119 1120 if (justifiedGalleryActive) return; 1121 1122 let submid = ((bParam_mid.length) ? bParam_mid : 'abcdefg'); 1123 1124 // Select all thread wrappers 1125 let threadWrappers = document.querySelectorAll('.thread-wrapper'); 1126 1127 threadWrappers.forEach(thread => { 1128 // Get the 'data-b64mids' attribute and check if it contains submid 1129 let b64mids = thread.dataset.b64mids; 1130 1131 if (b64mids && b64mids.includes(submid)) { 1132 // Handle collapsed comments if any 1133 let collapsedComments = document.querySelectorAll('.collapsed-comments'); 1134 if (collapsedComments.length) { 1135 let scrollToId = collapsedComments[0].id.substring(19); 1136 showHideComments(scrollToId); 1137 } 1138 1139 collapseHeight(); 1140 1141 if (!thread.classList.contains('toplevel_item')) { 1142 // Scroll to the target element 1143 let navHeight = document.getElementById('navbar-top') ? document.getElementById('navbar-top').offsetHeight : 0; 1144 window.scrollTo({ 1145 top: getOffsetTopRelativeToBody(thread) - navHeight, 1146 behavior: 'smooth' 1147 }); 1148 } 1149 1150 let id = thread.id.replace('thread-wrapper-', ''); 1151 let content = document.getElementById('wall-item-content-wrapper-' + id); 1152 content.classList.add('item-highlight-fade'); 1153 } 1154 }); 1155} 1156 1157function getOffsetTopRelativeToBody(element) { 1158 let offsetTop = 0; 1159 while (element) { 1160 offsetTop += element.offsetTop; 1161 element = element.offsetParent; 1162 } 1163 return offsetTop; 1164} 1165 1166function collapseHeight() { 1167 $(".wall-item-content:not('.divmore_checked'), .directory-collapse:not('.divmore_checked')").each(function(i) { 1168 let orgHeight = $(this).outerHeight(true); 1169 let id = (($(this).attr('id')) ? $(this).attr('id').split('wall-item-content-').pop() : 0); 1170 let b64mid = ((typeof bParam_mid !== 'undefined') ? bParam_mid : ''); 1171 1172 if (b64mid) { 1173 // Display the selected mid in an open state 1174 let b64mids = $('#thread-wrapper-' + id).data('b64mids'); 1175 1176 if (b64mids.length && b64mids.indexOf(b64mid) !== -1) {; 1177 expanded_items.push(id); 1178 } 1179 } 1180 1181 let open = ((expanded_items.indexOf(id) === -1) ? false : true); 1182 1183 if(orgHeight > divmore_height) { 1184 if(! $(this).hasClass('divmore') && $(this).has('div.no-collapse').length == 0) { 1185 $(this).readmore({ 1186 speed: 0, 1187 startOpen: open, 1188 heightMargin: 50, 1189 collapsedHeight: divmore_height, 1190 moreLink: '<a href="#" class="divgrow-showmore fakelink"><i class="bi bi-chevron-down align-middle divgrow-showmore-icon"></i> <span class="divgrow-showmore-label align-middle">' + aStr.divgrowmore + '</span></a>', 1191 lessLink: '<a href="#" class="divgrow-showmore fakelink"><i class="bi bi-chevron-up align-middle divgrow-showmore-icon"></i> <span class="divgrow-showmore-label align-middle">' + aStr.divgrowless + '</span></a>', 1192 beforeToggle: function(trigger, element, expanded) { 1193 if(expanded) { 1194 if((($(element).offset().top + divmore_height) - $(window).scrollTop()) < 65 ) { 1195 $(window).scrollTop($(window).scrollTop() - ($(element).outerHeight(true) - divmore_height)); 1196 } 1197 expanded_items = expanded_items.filter(expanded_items => expanded_items !== id); 1198 } 1199 else { 1200 expanded_items.push(id); 1201 } 1202 } 1203 }); 1204 $(this).addClass('divmore'); 1205 } 1206 } 1207 $(this).addClass('divmore_checked'); 1208 }); 1209 1210} 1211 1212function updateInit() { 1213
1214 if($('#live-network').length) { src = 'network'; } 1215 if($('#live-channel').length) { src = 'channel'; } 1216 if($('#live-pubstream').length) { src = 'pubstream'; } 1217 if($('#live-display').length) { src = 'display'; } 1218 //if($('#live-hq').length) { src = 'hq'; } 1219 if($('#live-search').length) { src = 'search'; } 1220 // if($('#live-cards').length) { src = 'cards'; } 1221 // if($('#live-articles').length) { src = 'articles'; } 1222 1223 if(src) { 1224 liveUpdate(); 1225 } 1226 else { 1227 document.dispatchEvent(new Event('hz:sse_bs_init')); 1228 } 1229 1230 if($('#live-photos').length || $('#live-cards').length || $('#live-articles').length ) { 1231 if(liking) { 1232 liking = 0; 1233 window.location.href=window.location.href; 1234 } 1235 } 1236} 1237 1238function prepareLiveUpdate(b64mid, notify_id) { 1239 $(document).scrollTop(0); 1240 $('.thread-wrapper').remove(); 1241 bParam_mid = b64mid; 1242 mode = 'replace'; 1243 page_load = true; 1244 if (module == 'hq') { 1245 liveUpdate(notify_id); 1246 } 1247 if (module == 'display') { 1248 liveUpdate(); 1249 } 1250} 1251 1252function liveUpdate(notify_id) { 1253 1254 if(typeof profile_uid === 'undefined') profile_uid = false; /* Should probably be unified with channelId defined in head.tpl */ 1255 1256 if((src === null) || (stopped) || (! profile_uid)) { $('.like-rotator').hide(); return; } 1257 1258 if(in_progress || mediaPlaying) { 1259 if(livetime) { 1260 clearTimeout(livetime); 1261 } 1262 livetime = setTimeout(liveUpdate, 10000); 1263 return; 1264 } 1265 1266 if(timer) 1267 clearTimeout(timer); 1268 1269 if(livetime !== null) 1270 livetime = null; 1271 1272 prev = 'live-' + src; 1273 1274 in_progress = true; 1275 1276 var update_url; 1277 var update_mode; 1278 1279 if(scroll_next) { 1280 bParam_page = next_page; 1281 page_load = true; 1282 } 1283 else { 1284 bParam_page = 1; 1285 } 1286 1287 update_url = buildCmd(); 1288 1289 console.log('displaying: ' + update_url); 1290 1291 if(page_load) { 1292 1293 $("#page-spinner").show(); 1294 1295 if(bParam_page == 1) { 1296 update_mode = 'replace'; 1297 $('.thread-wrapper').remove(); 1298 } 1299 else 1300 update_mode = 'append'; 1301 } 1302 else { 1303 update_mode = 'update'; 1304 var orgHeight = $("#region_2").height(); 1305 } 1306 1307 if(page_cache.data && bParam_page == page_cache.page) { 1308 page_load = false; 1309 scroll_next = false; 1310 updateConvItems(update_mode,page_cache.data); 1311 in_progress = false; 1312 return; 1313 } 1314 1315 var dstart = new Date(); 1316 console.log('LOADING data...'); 1317 $.get(update_url, function(data) { 1318 1319 // on shared hosts occasionally the live update process will be killed 1320 // leaving an incomplete HTML structure, which leads to conversations getting 1321 // truncated and the page messed up if all the divs aren't closed. We will try 1322 // again and give up if we can't get a valid HTML response after 10 tries. 1323 1324 if((data.indexOf("<html>") != (-1)) && (data.indexOf("</html>") == (-1))) { 1325 console.log('Incomplete data. Reloading'); 1326 in_progress = false; 1327 liveRecurse ++; 1328 if(liveRecurse < 10) { 1329 liveUpdate(notify_id); 1330 return; 1331 } 1332 else { 1333 console.log('Incomplete data. Too many attempts. Giving up.'); 1334 return; 1335 } 1336 } 1337 1338 // else data was valid - reset the recursion counter 1339 liveRecurse = 0; 1340 1341 if(notify_id) { 1342 $.post( 1343 "notify", 1344 { 1345 "notify_id" : notify_id 1346 } 1347 ); 1348 } 1349 1350 var dready = new Date(); 1351 console.log('DATA ready in: ' + (dready - dstart)/1000 + ' seconds.'); 1352 1353 console.log('LOADING images...'); 1354 imagesLoaded(data, function () { 1355 var iready = new Date(); 1356 console.log('IMAGES ready in: ' + (iready - dready)/1000 + ' seconds.'); 1357 1358 page_load = false; 1359 scroll_next = false; 1360 updateConvItems(update_mode,data); 1361 in_progress = false; 1362 }); 1363 }); 1364} 1365 1366function cache_next_page() { 1367 page_load = true; 1368 bParam_page++; 1369 update_url = buildCmd(); 1370 1371 $.get(update_url, function(data) { 1372 1373 // on shared hosts occasionally the live update process will be killed 1374 // leaving an incomplete HTML structure, which leads to conversations getting 1375 // truncated and the page messed up if all the divs aren't closed. We will try 1376 // again and give up if we can't get a valid HTML response after 10 tries. 1377 1378 if((data.indexOf("<html>") != (-1)) && (data.indexOf("</html>") == (-1))) { 1379 console.log('Incomplete data. Reloading'); 1380 in_progress = false; 1381 bParam_page--; 1382 liveRecurse++; 1383 if(liveRecurse < 10) { 1384 liveUpdate(); 1385 return; 1386 } 1387 else { 1388 console.log('Incomplete data. Too many attempts. Giving up.'); 1389 return; 1390 } 1391 } 1392 1393 // else data was valid - reset the recursion counter 1394 liveRecurse = 0; 1395 1396 console.log('cached: ' + update_url); 1397 1398 imagesLoaded(data, function () { 1399 console.log('page_cache: images loaded'); 1400 1401 page_cache.data = data; 1402 page_cache.page = bParam_page; 1403 page_cache.time = Date.now(); 1404 1405 bParam_page--; 1406 page_load = false;
1407 }); 1408 }); 1409 1410} 1411 1412function pageUpdate() { 1413 1414 in_progress = true; 1415 1416 var update_url; 1417 var update_mode; 1418 1419 if(scroll_next) { 1420 bParam_page = next_page; 1421 page_load = true; 1422 } 1423 else { 1424 bParam_page = 1; 1425 } 1426 1427 update_url = baseurl + '/' + decodeURIComponent(page_query) + '?aj=1&page=' + bParam_page + extra_args ; 1428 1429 $("#page-spinner").show(); 1430 update_mode = 'append'; 1431 1432 $.get(update_url,function(data) { 1433 page_load = false; 1434 scroll_next = false; 1435 updatePageItems(update_mode,data); 1436 $("#page-spinner").hide(); 1437 updateRelativeTime('.autotime'); 1438 in_progress = false; 1439 }); 1440} 1441 1442function justifyPhotos(id) { 1443 justifiedGalleryActive = true; 1444 $('#' + id).show(); 1445 $('#' + id).justifiedGallery({ 1446 rowHeight: 150, 1447 selector: 'a', 1448 margins: 3, 1449 border: 0 1450 }).on('jg.complete', function(e){ justifiedGalleryActive = false; }); 1451} 1452 1453function justifyPhotosAjax(id) { 1454 justifiedGalleryActive = true; 1455 $('#' + id).justifiedGallery('norewind').on('jg.complete', function(e){ justifiedGalleryActive = false; }); 1456} 1457 1458function request(id, mid, verb, parent, uuid, userClick) { 1459 1460 if (verb === 'load') { 1461 const dots = document.getElementById('load-more-dots-' + parent); 1462 dots.classList.add('jumping-dots'); 1463 1464 const parent_sub = document.getElementById('wall-item-sub-thread-wrapper-' + parent); 1465 const offset = parent_sub.children.length; 1466 1467 fetch('/request?offset=' + offset + '&verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module) 1468 .then(response => response.json()) 1469 .then(obj => { 1470 let parser = new DOMParser(); 1471 let doc = parser.parseFromString(obj.html, 'text/html'); 1472 let b64mids = []; 1473 1474 doc.querySelectorAll('.thread-wrapper').forEach(function (e) { 1475 let data = JSON.parse(e.dataset.b64mids); 1476 b64mids.push(...data); 1477 }); 1478 1479 imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () { 1480 injectWithAnimation('wall-item-sub-thread-wrapper-' + parent, doc); 1481 dots.classList.remove('jumping-dots'); 1482 1483 const loadmore_progress = document.getElementById('load-more-progress-' + parent); 1484 loadmore_progress.style.width = Math.round(100 * parent_sub.children.length / loadmore_progress.dataset.commentsTotal) + '%'; 1485 1486 if (Number(parent_sub.children.length) === Number(loadmore_progress.dataset.commentsTotal)) { 1487 const loadmore = document.getElementById('load-more-' + parent); 1488 loadmore.remove(); 1489 } 1490 1491 updateRelativeTime('.autotime'); 1492 collapseHeight(); 1493 1494 document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); 1495 document.dispatchEvent(new Event('hz:sse_bs_counts')); 1496 }); 1497 1498 }) 1499 .catch(error => { 1500 console.error('Error fetching data:', error); 1501 }); 1502 1503 return; 1504 } 1505 1506 const loading = document.getElementById('like-rotator-' + id); 1507 1508 if (userClick) { 1509 loading.style.display = 'block'; 1510 } 1511 1512 if (verb === 'comment') { 1513 if (userClick && singlethread_modules.indexOf(module) !== -1) { 1514 let stateObj = { b64mid: uuid }; 1515 history.pushState(stateObj, '', module + '/' + uuid); 1516 } 1517 1518 fetch('/request?verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module) 1519 .then(response => response.json()) 1520 .then(obj => { 1521 let parser = new DOMParser(); 1522 let doc = parser.parseFromString(obj.html, 'text/html'); 1523 1524 doc.querySelectorAll('.thread-wrapper').forEach(function (e) { 1525 let data = JSON.parse(e.dataset.b64mids); 1526 b64mids.push(...data); 1527 }); 1528 1529 imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () { 1530 injectWithAnimation('wall-item-sub-thread-wrapper-' + id, doc, true); 1531 updateRelativeTime('.autotime'); 1532 collapseHeight(); 1533 1534 if (userClick) { 1535 loading.style.display = 'none'; 1536 document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); 1537 document.dispatchEvent(new Event('hz:sse_bs_counts')); 1538 } 1539 }); 1540 1541 }) 1542 .catch(error => { 1543 console.error('Error fetching data:', error); 1544 }); 1545 } 1546 else { 1547 fetch('/request?verb=' + verb + '&mid=' + mid + '&parent=' + parent) 1548 .then(response => response.json()) 1549 .then(obj => { 1550 const modal = new bootstrap.Modal('#reactions'); 1551 const modal_content = document.getElementById('reactions_body'); 1552 const modal_title = document.getElementById('reactions_title'); 1553 const modal_action = document.getElementById('reactions_action'); 1554 modal_action.style.display = 'none'; 1555 modal_title.innerHTML = obj.title; 1556 modal_content.innerHTML = ''; 1557 if (obj.action) { 1558 modal_action.innerHTML = '<a href="#" onclick="' + obj.action + '(' + id + ',\'' + verb + '\'); return false;">' + obj.action_label + '</a>'; 1559 modal_action.style.display = 'block'; 1560 } 1561 console.log(obj) 1562 obj.result.forEach(e => { 1563 let mod = ''; 1564 if (e.item_blocked === 4) { 1565 mod = '<span onclick="moderate_approve(' + e.id + '); return false;" class="text-success pe-4 d-inline-block"><i class="bi bi-check-lg" ></i></span><span onclick="moderate_drop(' + e.id + '); return false;" class="text-danger pe-4 d-inline-block"><i class="bi bi-trash" ></i></span>'; 1566 } 1567 modal_content.innerHTML += '<a href="' + e.url + '" class="list-group-item list-group-item-action border-0">' + mod + '<img src="' + e.photo + '" class="menu-img-1" loading="lazy"> ' + e.name + '</a>'; 1568 1569 }); 1570 1571 modal.show(); 1572 loading.style.display = 'none'; 1573 }) 1574 .catch(error => { 1575 console.error('Error fetching data:', error); 1576 }); 1577 } 1578 1579} 1580
1581function injectWithAnimation(containerId, parsedDoc, overwrite = false) { 1582 const container = document.getElementById(containerId); 1583 if (!container) return; 1584 if (overwrite) container.innerHTML = ''; 1585 1586 const newElements = Array.from(parsedDoc.body.children); 1587 1588 for (let i = newElements.length - 1; i >= 0; i--) { 1589 const el = newElements[i].cloneNode(true); 1590 el.classList.add('item-fade-in'); 1591 container.insertBefore(el, container.firstChild); 1592 1593 // Remove classes after transition ends 1594 const onTransitionEnd = (event) => { 1595 el.classList.remove('item-fade-in', 'show'); 1596 el.removeEventListener('transitionend', onTransitionEnd); 1597 }; 1598 el.addEventListener('transitionend', onTransitionEnd); 1599 1600 setTimeout(() => { 1601 el.classList.add('show'); 1602 }, (newElements.length - 1 - i) * 30); 1603 } 1604} 1605 1606const autoExpand = (function () { 1607 const clickedElements = new Set(); // Stores clicked button references 1608 1609 // We wait 10 seconds for images. Set the timeout here slightly higher. 1610 function waitForElement(selector, timeout = 11000) { 1611 return new Promise((resolve, reject) => { 1612 // Check if the element already exists 1613 const element = document.querySelector(selector); 1614 1615 if (element) { 1616 resolve(element); 1617 return; 1618 } 1619 1620 // Set up a timeout to reject the promise if the element doesn't appear in time 1621 const timer = setTimeout(() => { 1622 observer.disconnect(); 1623 reject(new Error(`Element "${selector}" not found within ${timeout}ms`)); 1624 }, timeout); 1625 1626 // Create a MutationObserver to watch for DOM changes 1627 const observer = new MutationObserver(() => { 1628 const el = document.querySelector(selector); 1629 if (el) { 1630 clearTimeout(timer); 1631 observer.disconnect(); 1632 resolve(el); 1633 } 1634 }); 1635 1636 // Start observing the document for changes 1637 observer.observe(document.documentElement, { 1638 childList: true, 1639 subtree: true 1640 }); 1641 }); 1642 } 1643 1644 async function autoExpand(id) { 1645 const loading = document.getElementById('like-rotator-' + id); 1646 let iteration = 0; 1647 const maxIterations = 3; 1648 clickedElements.clear(); 1649 1650 try { 1651 // Step 1: Ensure initial button is clicked 1652 const initBtnSelector = '#wall-item-comment-' + id; 1653 const initBtn = await waitForElement(initBtnSelector); 1654 1655 if (!clickedElements.has(initBtn)) { 1656 initBtn.click(); 1657 clickedElements.add(initBtn); 1658 iteration++; 1659 } 1660 1661 // Step 2: Loop until no new buttons are found 1662 let newButtonsFound; 1663 1664 const commentSelector = `#wall-item-sub-thread-wrapper-${id} .thread-wrapper`; 1665 const commentBtnSelector = `#wall-item-sub-thread-wrapper-${id} .wall-item-comment`; 1666 1667 do { 1668 newButtonsFound = false; 1669 1670 // Wait for any comment to appear 1671 await waitForElement(commentSelector); 1672 1673 const expandButtons = document.querySelectorAll(commentBtnSelector); 1674 1675 for (const btn of expandButtons) { 1676 if (!clickedElements.has(btn)) { 1677 btn.click(); 1678 clickedElements.add(btn); 1679 newButtonsFound = true; 1680 // Optional: await waitForElement(...) to wait for new content 1681 } 1682 } 1683 1684 // Wait between iterations to allow UI to update 1685 if (newButtonsFound) { 1686 iteration++; 1687 await new Promise(res => setTimeout(res, 700)); 1688 } 1689 1690 } while (newButtonsFound && iteration < maxIterations); 1691 1692 console.log('Replies loaded!'); 1693 1694 loading.style.display = 'none'; 1695 1696 document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids })); 1697 document.dispatchEvent(new Event('hz:sse_bs_counts')); 1698 1699 } catch (error) { 1700 loading.style.display = 'none'; 1701 console.error("autoExpand failed:", error.message); 1702 } 1703 } 1704 1705 return autoExpand; 1706})(); 1707 1708 1709function stringToHexColor(str) { 1710 let hash = 0; 1711 for (let i = 0; i < str.length; i++) { 1712 hash = str.charCodeAt(i) + ((hash << 5) - hash); 1713 } 1714 let color = "#"; 1715 for (let i = 0; i < 3; i++) { 1716 const value = (hash >> (i * 8)) & 0xFF; 1717 color += value.toString(16).padStart(2, '0'); 1718 } 1719 return color; 1720} 1721 1722function stringToHslColor(str) { 1723 let stringUniqueHash = [...str].reduce((acc, char) => { 1724 return char.charCodeAt(0) + ((acc << 5) - acc); 1725 }, 0); 1726 return `hsl(${stringUniqueHash % 360}, 65%, 65%)`; 1727} 1728 1729function dolike(ident, verb) { 1730 $('#like-rotator-' + ident).show(); 1731 1732 if (typeof conv_mode == typeof undefined) { 1733 conv_mode = ''; 1734 } 1735 1736 if (typeof page_mode == typeof undefined) { 1737 page_mode = ''; 1738 } 1739 1740 let reload = 0; 1741 1742 if (module == 'photos') { 1743 reload = 1; 1744 } 1745 1746 1747 $.get('like/' + ident + '?verb=' + verb + '&conv_mode=' + conv_mode + '&page_mode=' + page_mode + '&reload=' + reload, function (data) { 1748 if (data.success) { 1749 1750 close_modal(); 1751 1752 // mod photos 1753 if (data.reload) { 1754 window.location.href = window.location.href; 1755 } 1756 1757 // this is a bit tricky since the top level thread wrapper wraps the whole thread 1758 if($('#thread-wrapper-' + data.orig_id).hasClass('toplevel_item')) {
1759 var wrapper = $('<div></div>').html( data.html ).find('#wall-item-outside-wrapper-' + data.id); 1760 $('#wall-item-outside-wrapper-' + data.orig_id).html(wrapper[0].innerHTML); 1761 // those were not replaced - swap the id 1762 $('#thread-wrapper-' + data.orig_id).attr('id', 'thread-wrapper-' + data.id); 1763 $('#wall-item-outside-wrapper-' + data.orig_id).attr('id', 'wall-item-outside-wrapper-' + data.id); 1764 } 1765 else { 1766 $('#thread-wrapper-' + data.orig_id).replaceWith(data.html); 1767 } 1768 1769 updateRelativeTime('.autotime'); 1770 collapseHeight(); 1771 liking = 0; 1772 } 1773 }); 1774 1775 liking = 1; 1776} 1777 1778function doprofilelike(ident, verb) { 1779 $.get('like/' + ident + '?verb=' + verb, function() { window.location.href=window.location.href; }); 1780} 1781 1782 1783function doreply(parent, ident, owner, hint) { 1784 const modal = new bootstrap.Modal('#reactions'); 1785 const modal_container = document.getElementById('reactions') 1786 const modal_content = document.getElementById('reactions_body'); 1787 const modal_title = document.getElementById('reactions_title'); 1788 const modal_action = document.getElementById('reactions_action'); 1789 1790 modal_action.style.display = 'none'; 1791 modal_title.innerHTML = hint; 1792 1793 const preview = document.getElementById('comment-edit-preview-' + parent.toString()); 1794 if (preview) preview.innerHTML = ''; 1795 1796 const form_container = document.getElementById('comment-edit-wrapper-' + parent.toString()); 1797 1798 // Get the form element by ID 1799 const form = document.getElementById('comment-edit-form-' + parent.toString()); 1800 if (!form) return; 1801 1802 modal_content.innerHTML = ''; 1803 modal_content.append(form); 1804 modal_content.append(preview); 1805 1806 // Set the value of the input named 'parent' 1807 const parentInput = form.querySelector('input[name=parent]'); 1808 1809 if (parentInput) { 1810 parentInput.value = ident; 1811 } 1812 1813 // Find the submit button and update its HTML 1814 const submitBtn = form.querySelector('button[type=submit]'); 1815 1816 if (submitBtn) { 1817 const btnText = submitBtn.innerHTML.replace(/<[^>]*>/g, '').trim(); 1818 submitBtn.innerHTML = '<i class="bi bi-arrow-90deg-left"></i> ' + btnText; 1819 } 1820 1821 // Prepare the quote logic 1822 const sel = 'wall-item-body-' + ident.toString(); 1823 const quote = window.getSelection().toString().trim(); 1824 1825 // Check if the selection is inside the correct element 1826 let isInSel = false; 1827 const anchorNode = window.getSelection().anchorNode; 1828 1829 if (anchorNode) { 1830 let node = anchorNode.nodeType === 3 ? anchorNode.parentNode : anchorNode; 1831 while (node) { 1832 if (node.id === sel) { 1833 isInSel = true; 1834 break; 1835 } 1836 node = node.parentNode; 1837 } 1838 } 1839 1840 modal.show(); 1841 1842 modal_container.addEventListener('hide.bs.modal', event => { 1843 // move form back to where it was 1844 form_container.append(form); 1845 form_container.append(preview); 1846 }); 1847 1848 // Set the textarea value 1849 const textarea = form.querySelector('textarea'); 1850 1851 if (textarea) { 1852 let commentBody = localStorage.getItem('comment_body-' + ident); 1853 if (commentBody) { 1854 textarea.value = commentBody; 1855 } 1856 else { 1857 textarea.value = '@{' + owner + '} '; 1858 1859 if (quote && isInSel) { 1860 textarea.value += "\n[quote]" + quote + "[/quote]\n"; 1861 } 1862 } 1863 1864 textarea.focus(); 1865 } 1866} 1867 1868 1869function doscroll(parent, hidden) { 1870 var id; 1871 var x = '#hide-comments-outer-' + hidden.toString(); 1872 var back = $('#back-to-reply'); 1873 if(back.length == 0) 1874 var pos = $(window).scrollTop(); 1875 else 1876 var pos = back.attr('href').replace(/[^\d|\.]/g,''); 1877 if($(x).length !== 0) { 1878 x = $(x).attr("onclick").replace(/\D/g,''); 1879 var c = '#collapsed-comments-' + x; 1880 if($(c).length !== 0 && (! $(c).is(':visible'))) { 1881 showHideComments(x); 1882 collapseHeight(); 1883 pos += $(c).height(); 1884 } 1885 } 1886 back.remove(); 1887 1888 $('.thread-wrapper').filter(function() { 1889 if($(this).data('b64mids').indexOf(parent) > -1) { 1890 id = $(this); 1891 } 1892 }); 1893 1894 $('html, body').animate({scrollTop:(id.offset().top) - 50}, 'slow'); 1895 $('<a href="javascript:doscrollback(' + pos + ');" id="back-to-reply" title="' + aStr['to_reply'] + '"><i class="bi bi-chevron-double-down"></i></a>').insertAfter('#wall-item-ago-' + id.attr('id').replace(/\D/g,'')); 1896} 1897 1898function doscrollback(pos) { 1899 $('#back-to-reply').remove(); 1900 $(window).scrollTop(pos); 1901} 1902 1903function dopin(id) { 1904 id = id.toString(); 1905 $('#like-rotator-' + id).show(); 1906 $.post('pin/pin', { 'id' : id }) 1907 .done(function() { 1908 var i = $('#wall-item-pinned-' + id);
1909 var me = $('#item-pinnable-' + id); 1910 var pin = $('.pinned-item'); 1911 if(pin.length) { 1912 $('html, body').animate({ scrollTop: $('#region_2').offset().top }, 'slow', function() { 1913 pin.fadeTo('fast', 0.33, function() { this.remove(); }); 1914 }); 1915 }; 1916 pin.remove(); 1917 $('.dropdown-item-pinnable').html($('.dropdown-item-pinnable').html().replace(aStr['unpin_item'],aStr['pin_item'])); 1918 $('.wall-item-pinned').remove() 1919 if(i.length == 0) { 1920 $('<span class="wall-item-pinned" title="' + aStr['pinned'] + '" id="wall-item-pinned-' + id + '"><i class="bi bi-pin"> </i></span>').insertAfter('#wall-item-ago-' + id); 1921 me.html(me.html().replace(aStr['pin_item'],aStr['unpin_item'])); 1922 }; 1923 }) 1924 .fail(function() { 1925 location.reload(); 1926 }) 1927 .always(function() { 1928 $('#like-rotator-' + id).hide(); 1929 }); 1930} 1931 1932function dropItem(url, object, b64mid) { 1933 var confirm = confirmDelete(); 1934 if(confirm) { 1935 var id = url.split('/')[2]; 1936 $('body').css('cursor', 'wait'); 1937 $(object + ', #pinned-wrapper-' + id).css('opacity', 0.33); 1938 1939 $.get(url, function() { 1940 $(object + ', #pinned-wrapper-' + id).remove(); 1941 $('body').css('cursor', 'auto'); 1942 1943 toast(aStr.itemdel, 'info'); 1944 //$.jGrowl(aStr.itemdel, { sticky: false, theme: 'info', life: 3000 }); 1945 1946 if (typeof b64mid !== typeof undefined) { 1947 $('[data-b64mid=\'' + b64mid + '\']').fadeOut(function() { 1948 this.remove(); 1949 }); 1950 } 1951 }); 1952 1953 if($('#wall-item-pinned-' + id).length) { 1954 $.post('pin/pin', { 'id' : id }); 1955 } 1956 1957 return true; 1958 } 1959 else { 1960 return false; 1961 } 1962} 1963 1964function dosubthread(ident) { 1965 $('#like-rotator-' + ident).show(); 1966 $.get('subthread/sub/' + ident, updateInit ); 1967 liking = 1; 1968} 1969 1970function dounsubthread(ident) { 1971 $('#like-rotator-' + ident).show(); 1972 $.get('subthread/unsub/' + ident, updateInit ); 1973 liking = 1; 1974} 1975 1976function moderate_approve(ident, verb) { 1977 $('#like-rotator-' + ident).show(); 1978 close_modal(); 1979 $.get('moderate/' + ident + '/approve', updateInit ); 1980 liking = 1; 1981} 1982 1983function moderate_drop(ident) { 1984 $('#like-rotator-' + ident).show(); 1985 close_modal(); 1986 $.get('moderate/' + ident + '/drop', $('#thread-wrapper-' + ident).fadeOut(function() { this.remove(); })); 1987 liking = 1; 1988} 1989 1990function dostar(ident) { 1991 ident = ident.toString(); 1992 $('#like-rotator-' + ident).show(); 1993 $.get('starred/' + ident, function(data) { 1994 if(data.result == 1) { 1995 $('#starred-' + ident).addClass('starred'); 1996 $('#starred-' + ident).removeClass('unstarred'); 1997 $('#starred-' + ident).addClass('bi-star-fill'); 1998 $('#starred-' + ident).removeClass('bi-star'); 1999 $('#star-' + ident).addClass('hidden'); 2000 $('#unstar-' + ident).removeClass('hidden'); 2001 var btn_tpl = '<div class="" id="star-button-' + ident + '"><button type="button" class="btn btn-sm btn-link link-secondary wall-item-star" onclick="dostar(' + ident + ');"><i class="bi bi-star generic-icons"></i></button></div>' 2002 $('#wall-item-tools-right-' + ident).prepend(btn_tpl); 2003 } 2004 else { 2005 $('#starred-' + ident).addClass('unstarred'); 2006 $('#starred-' + ident).removeClass('starred'); 2007 $('#starred-' + ident).addClass('bi-star'); 2008 $('#starred-' + ident).removeClass('bi-star-fill'); 2009 $('#star-' + ident).removeClass('hidden'); 2010 $('#unstar-' + ident).addClass('hidden'); 2011 $('#star-button-' + ident).remove(); 2012 } 2013 $('#like-rotator-' + ident).hide(); 2014 }); 2015} 2016 2017function getPosition(e) { 2018 var cursor = {x:0, y:0}; 2019 if ( e.pageX || e.pageY ) { 2020 cursor.x = e.pageX; 2021 cursor.y = e.pageY; 2022 } 2023 else { 2024 if( e.clientX || e.clientY ) { 2025 cursor.x = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft) - document.documentElement.clientLeft; 2026 cursor.y = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop) - document.documentElement.clientTop; 2027 } 2028 else { 2029 if( e.x || e.y ) { 2030 cursor.x = e.x; 2031 cursor.y = e.y; 2032 } 2033 } 2034 } 2035 return cursor; 2036} 2037
2038function lockview(type, id) { 2039 $.get('lockview/' + type + '/' + id, function(data) { 2040 $('#panel-' + id).html(data); 2041 }); 2042} 2043 2044function submitPoll(id) { 2045 2046 $.post('vote/' + id, 2047 $('#question-form-' + id).serialize(), 2048 function(data) { 2049 toast(data.message, ((data.success) ? 'info' : 'danger')); 2050 //$.jGrowl(data.message, { sticky: false, theme: ((data.success) ? 'info' : 'notice'), life: 10000 }); 2051 if(timer) clearTimeout(timer); 2052 timer = setTimeout(updateInit, 500); 2053 } 2054 ); 2055 2056} 2057 2058function post_comment(id) { 2059 2060 commentBusy = true; 2061 $('body').css('cursor', 'wait'); 2062 $("#comment-preview-inp-" + id).val("0"); 2063 2064 if (typeof conv_mode == typeof undefined) { 2065 conv_mode = ''; 2066 } 2067 2068 const form_data = $("#comment-edit-form-" + id).serialize(); 2069 2070 $.post( 2071 "item", 2072 form_data + '&conv_mode=' + conv_mode, 2073 function(data) { 2074 if (data.success) { 2075 2076 //mod photos 2077 if (data.reload) { 2078 window.location.href = data.reload; 2079 } 2080 2081 close_modal(); 2082 2083 localStorage.removeItem("comment_body-" + id); 2084 $("#comment-edit-preview-" + id).hide(); 2085 $("#comment-edit-text-" + id).val('').blur().attr('placeholder', aStr.comment); 2086 $('#wall-item-sub-thread-wrapper-' + data.thr_parent_id).append(data.html); 2087 2088 const comment = document.getElementById('wall-item-content-wrapper-' + data.id); 2089 comment.classList.add('item-highlight-fade'); 2090 comment.scrollIntoView({ 2091 behavior: 'smooth', 2092 block: 'center' 2093 }); 2094 2095 updateRelativeTime('.autotime'); 2096 $('body').css('cursor', 'unset'); 2097 collapseHeight(); 2098 commentBusy = false; 2099 2100 const tarea = document.getElementById("comment-edit-text-" + id); 2101 if (tarea) { 2102 commentClose(tarea, id); 2103 $(document).off( "click.commentOpen"); 2104 } 2105 } 2106 }, 2107 "json" 2108 ); 2109 2110 return false; 2111 2112} 2113 2114 2115 2116function preview_comment(id) { 2117 $("#comment-preview-inp-" + id).val("1"); 2118 $("#comment-edit-preview-" + id).show(); 2119 $.post( 2120 "item", 2121 $("#comment-edit-form-" + id).serialize(), 2122 function(data) { 2123 if(data.preview) { 2124 $("#comment-edit-preview-" + id).html(data.preview); 2125 updateRelativeTime('.autotime'); 2126 $("#comment-edit-preview-" + id + " a").click(function() { return false; }); 2127 } 2128 }, 2129 "json" 2130 ); 2131 return true; 2132} 2133 2134function importElement(elem) { 2135 $.post( 2136 "impel", 2137 { "element" : elem }, 2138 function(data) { 2139 if(timer) clearTimeout(timer); 2140 timer = setTimeout(updateInit,10); 2141 } 2142 ); 2143 return false; 2144} 2145 2146function preview_post() { 2147 $("#jot-preview").val("1"); 2148 $("#jot-preview-content").show(); 2149 $.post( 2150 "item", 2151 $("#profile-jot-form").serialize(), 2152 function(data) { 2153 if(data.preview) { 2154 $("#jot-preview-content").html(data.preview); 2155 updateRelativeTime('.autotime'); 2156 $("#jot-preview-content" + " a").click(function() { return false; }); 2157 } 2158 }, 2159 "json" 2160 ); 2161 $("#jot-preview").val("0"); 2162 return true; 2163} 2164 2165function bin2hex(s) { 2166 // UTF-8 encoding to hex is supported 2167 var bytes = new TextEncoder().encode(s); 2168 return Array.from( 2169 bytes, 2170 byte => byte.toString(16).padStart(2, "0") 2171 ).join(""); 2172} 2173 2174function hex2bin(hex) { 2175 // UTF-8 decoding from hex is supported 2176 var bytes = new Uint8Array(hex.length / 2); 2177 for (i = 0; i !== bytes.length; i++) { 2178 bytes[i] = parseInt(hex.substr(i * 2, 2), 16); 2179 } 2180 return new TextDecoder().decode(bytes); 2181} 2182 2183function groupChangeMember(gid, cid, sec_token) { 2184 $('body .fakelink').css('cursor', 'wait'); 2185 $.get('group/' + gid + '/' + cid + "?t=" + sec_token, function(data) { 2186 $('#group-update-wrapper').html(data); 2187 $('body .fakelink').css('cursor', 'auto'); 2188 }); 2189} 2190 2191function profChangeMember(gid, cid) { 2192 $('body .fakelink').css('cursor', 'wait'); 2193 $.get('profperm/' + gid + '/' + cid, function(data) { 2194 $('#prof-update-wrapper').html(data); 2195 $('body .fakelink').css('cursor', 'auto'); 2196 }); 2197} 2198 2199function contactgroupChangeMember(gid, cid) { 2200 $('body').css('cursor', 'wait'); 2201 $.get('contactgroup/' + gid + '/' + cid, function(data) { 2202 $('body').css('cursor', 'auto'); 2203 $('#group-' + gid).toggleClass('bi-check-square bi-square'); 2204 }); 2205} 2206 2207function checkboxhighlight(box) { 2208 if($(box).is(':checked')) { 2209 $(box).addClass('checkeditem'); 2210 } else { 2211 $(box).removeClass('checkeditem'); 2212 } 2213} 2214 2215/** 2216 * sprintf in javascript 2217 * "{0} and {1}".format('zero','uno'); 2218 */ 2219String.prototype.format = function() { 2220 var formatted = this; 2221 for (var i = 0; i < arguments.length; i++) { 2222 var regexp = new RegExp('\\{'+i+'\\}', 'gi'); 2223 formatted = formatted.replace(regexp, ((typeof arguments[i] !== 'undefined') ? arguments[i] : '')); 2224 } 2225 return formatted; 2226}; 2227 2228// Array Remove 2229Array.prototype.remove = function(item) {
2230 to = undefined; 2231 from = this.indexOf(item); 2232 var rest = this.slice((to || from) + 1 || this.length); 2233 this.length = from < 0 ? this.length + from : from; 2234 return this.push.apply(this, rest); 2235}; 2236 2237function zFormError(elm,x) { 2238 if(x) { 2239 $(elm).addClass("zform-error"); 2240 $(elm).removeClass("zform-ok"); 2241 } else { 2242 $(elm).addClass("zform-ok"); 2243 $(elm).removeClass("zform-error"); 2244 } 2245} 2246 2247$(window).scroll(function () { 2248 if(typeof buildCmd == 'function') { 2249 // This is a content page with items and/or conversations 2250 if($('#conversation-end').length && ($(window).scrollTop() + $(window).height()) > $('#conversation-end').position().top) { 2251 if((pageHasMoreContent) && (! loadingPage)) { 2252 next_page++; 2253 scroll_next = true; 2254 loadingPage = true; 2255 liveUpdate(); 2256 } 2257 } 2258 } 2259 else { 2260 // This is some other kind of page - perhaps a directory 2261 if($('#page-end').length && ($(window).scrollTop() + $(window).height()) > $('#page-end').position().top) { 2262 if((pageHasMoreContent) && (! loadingPage) && (! justifiedGalleryActive)) { 2263 next_page++; 2264 scroll_next = true; 2265 loadingPage = true; 2266 pageUpdate(); 2267 } 2268 } 2269 } 2270}); 2271 2272function loadText(textRegion,data) { 2273 var currentText = $(textRegion).val(); 2274 $(textRegion).val(currentText + data); 2275} 2276 2277function makeid(length) { 2278 var result = ''; 2279 var characters = 'abcdef0123456789'; 2280 var charactersLength = characters.length; 2281 for ( var i = 0; i < length; i++ ) { 2282 result += characters.charAt(Math.floor(Math.random() * charactersLength)); 2283 } 2284 return result; 2285} 2286 2287function push_notification_request(e) { 2288 if (!('Notification' in window)) { 2289 alert('This browser does not support push notifications'); 2290 } 2291 else if (Notification.permission !== 'granted') { 2292 Notification.requestPermission(function(permission) { 2293 if(permission === 'granted') { 2294 $(e.target).closest('div').hide(); 2295 } 2296 }); 2297 } 2298} 2299 2300 2301function push_notification(title, body, b64mid) { 2302 let options = { 2303 body: body, 2304 data: b64mid, 2305 icon: '/images/app/hz-96.png', 2306 silent: false 2307 } 2308 2309 let n = new Notification(title, options); 2310 n.onclick = function (e) { 2311 if(module === 'hq') { 2312 prepareLiveUpdate(e.target.data); 2313 } 2314 else { 2315 window.location.href = baseurl + '/hq/' + e.target.data; 2316 } 2317 } 2318} 2319 2320function toggleAside() { 2321 if ($('main.region_1-on').length) { 2322 $('#expand-aside-icon').addClass('bi-arrow-right-circle').removeClass('bi-arrow-left-circle'); 2323 $('html, body').css({ 'position': '', 'left': '' }); 2324 $('main').removeClass('region_1-on'); 2325 $('#region_1').addClass('d-none'); 2326 $('#region_2').css({ 'min-width': '' }); 2327 $('#overlay').remove(); 2328 } 2329 else { 2330 $('#expand-aside-icon').removeClass('bi-arrow-right-circle').addClass('bi-arrow-left-circle'); 2331 $('html, body').css({ 'position': 'sticky', 'left': '0px'}); 2332 $('main').addClass('region_1-on'); 2333 $('#region_1').removeClass('d-none'); 2334 $('#region_2').css({ 'min-width': window.outerWidth }); 2335 $('<div id="overlay"></div>').appendTo('body').one('click', function() { toggleAside(); }); 2336 } 2337} 2338 2339function toast(string, severity) { 2340 let id = bin2hex(string); 2341 let container = document.getElementById('toast-container'); 2342 let toast = document.getElementById(id); 2343 2344 if (!toast) { 2345 toast = document.createElement('div'); 2346 toast.setAttribute('id', id); 2347 toast.innerHTML = '<div class="d-flex"><div class="toast-body">' + string + '</div><button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button></div>'; 2348 toast.classList.add('toast', 'p-2', 'bg-' + severity + '-subtle', 'text-' + severity + '-emphasis', 'border-' + severity); 2349 } 2350 2351 container.prepend(toast); 2352 2353 let toastInstance = bootstrap.Toast.getOrCreateInstance(toast); 2354 2355 if (severity === 'danger') { 2356 toastInstance._config.autohide = false;
2357 } 2358 2359 toastInstance.show(); 2360} 2361 2362function close_modal() { 2363 let modal = bootstrap.Modal.getInstance(document.querySelector('.modal.show')); 2364 2365 if (modal) { 2366 modal.hide(); 2367 } 2368}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.