janitor-unminified.js
1 /** 2 * Janitor Extension 3 */ 4 5 (function() { 6 /** 7 * Admin tools 8 */ 9 var AdminTools = { 10 cacheTTL: 60000, 11 autoRefreshDelay: 120000, 12 autoRefreshTimeout: null 13 }; 14 15 // FIXME, put it as a helper in extension.js 16 AdminTools.initVisibilityAPI = function() { 17 this.hidden = 'hidden'; 18 this.visibilitychange = 'visibilitychange'; 19 20 if (typeof document.hidden === 'undefined') { 21 if ('mozHidden' in document) { 22 this.hidden = 'mozHidden'; 23 this.visibilitychange = 'mozvisibilitychange'; 24 } 25 else if ('webkitHidden' in document) { 26 this.hidden = 'webkitHidden'; 27 this.visibilitychange = 'webkitvisibilitychange'; 28 } 29 else if ('msHidden' in document) { 30 this.hidden = 'msHidden'; 31 this.visibilitychange = 'msvisibilitychange'; 32 } 33 } 34 35 document.addEventListener(this.visibilitychange, this.onVisibilityChange, false); 36 }; 37 38 AdminTools.init = function() { 39 var cnt, html; 40 41 AdminTools.initVisibilityAPI(); 42 43 cnt = document.createElement('div'); 44 cnt.className = 'extPanel reply'; 45 cnt.id = 'adminToolbox'; 46 cnt.setAttribute('data-trackpos', 'AT-position'); 47 48 if( Config['AT-position'] ) { 49 cnt.style.cssText = Config['AT-position']; 50 } else { 51 cnt.style.right = '10px'; 52 cnt.style.top = '380px'; 53 } 54 55 cnt.style.position = Config.fixedAdminToolbox ? 'fixed' : ''; 56 57 html = '<div class="drag" id="atHeader">Janitor Tools' 58 + '<img alt="Refresh" title="Refresh" src="' + Main.icons.refresh 59 + '" id="atRefresh" data-cmd="at-refresh" class="pointer right"></div>' 60 + '<h4><a href="https://' + J.reportsSubDomain + '.4chan.org/" target="_blank">Reports</a>: ' 61 + '<span title="Total" id="at-total">?</span> (' 62 + '<span title="Illegal" id="at-illegal">?</span>)</h4>' 63 + '<h4 id="at-msg-cnt"><a data-cmd="at-msg" href="https://' + J.reportsSubDomain 64 + '.4chan.org/?action=staffmessages" target="_blank">Messages</a>: <span id="at-msg">?</span></h4>'; 65 66 cnt.innerHTML = html; 67 document.body.appendChild(cnt); 68 AdminTools.refreshReportCount(); 69 70 Draggable.set($.id('atHeader')); 71 }; 72 73 AdminTools.onVisibilityChange = function() { 74 var self; 75 76 self = AdminTools; 77 78 if (document[AdminTools.hidden]) { 79 clearInterval(self.autoRefreshTimeout); 80 self.autoRefreshTimeout = null; 81 } 82 else { 83 self.refreshReportCount(); 84 self.autoRefreshTimeout = setInterval(self.refreshReportCount, self.autoRefreshDelay); 85 } 86 }; 87 88 AdminTools.refreshReportCount = function(force) { 89 var xhr, cache; 90 91 if (force !== true && (cache = localStorage.getItem('4chan-cache-rc'))) { 92 cache = JSON.parse(cache); 93 94 if (cache.ts > Date.now() - AdminTools.cacheTTL) { 95 $.id('at-total').textContent = cache.data[0]; 96 $.id('at-illegal').textContent = cache.data[1]; 97 98 $.id('at-msg-cnt').style.display = cache.data[2] ? 'block' : ''; 99 $.id('at-msg').textContent = cache.data[2] || 0; 100 101 return; 102 } 103 } 104 105 xhr = new XMLHttpRequest(); 106 107 xhr.open('GET', 'https://' + J.reportsSubDomain + '.4chan.org/H429f6uIsUqU.php', true); 108 109 xhr.withCredentials = true; 110 111 xhr.onload = function() { 112 var cache, resp, data, msg_count; 113 114 if (this.status == 200) { 115 try { 116 resp = JSON.parse(this.responseText); 117 } 118 catch (e) { 119 console.log(e); 120 return; 121 } 122 123 if (resp.status !== 'success') { 124 console.log(resp.message); // FIXME, use global message 125 return; 126 } 127 128 data = resp.data; 129 130 msg_count = data.msg || 0; 131 132 $.id('at-msg-cnt').style.display = msg_count ? 'block' : ''; 133 $.id('at-msg').textContent = msg_count; 134 $.id('at-total').textContent = data.total; 135 $.id('at-illegal').textContent = data.illegal; 136 137 cache = { 138 ts: Date.now(), 139 data: [ data.total, data.illegal, msg_count ] 140 }; 141 142 cache = JSON.stringify(cache); 143 144 localStorage.setItem('4chan-cache-rc', cache); 145 146 document.dispatchEvent(new CustomEvent('4chanATUpdated')); 147 } 148 else { 149 this.onerror(); 150 } 151 }; 152 153 xhr.onerror = function() { 154 console.log('Error while refreshing the report count (Status: ' + this.status + ').'); 155 }; 156 157 xhr.onloadend = function() { 158 $.id('atRefresh').src = Main.icons.refresh; 159 }; 160 161 $.id('atRefresh').src = Main.icons.rotate; 162 163 xhr.send(null); 164 }; 165 166 AdminTools.resetMsgCount = function() { 167 var cache; 168 169 $.id('at-msg').textContent = 0; 170 171 if (cache = localStorage.getItem('4chan-cache-rc')) { 172 cache = JSON.parse(cache); 173 cache.data[2] = 0; 174 cache = JSON.stringify(cache); 175 localStorage.setItem('4chan-cache-rc', cache); 176 } 177 }; 178 179 var J = { 180 nextChunkIndex: 0, 181 nextChunk: null, 182 chunkSize: 100, 183 184 reportsSubDomain: 'reports' 185 }; 186 187 J.initIconsCatalog = function() { 188 var key, paths, url; 189 190 Main.icons = { 191 up: 'arrow_up.png', 192 down: 'arrow_down.png', 193 right: 'arrow_right.png', 194 download: 'arrow_down2.png', 195 refresh: 'refresh.png', 196 cross: 'cross.png', 197 gis: 'gis.png', 198 iqdb: 'iqdb.png', 199 minus: 'post_expand_minus.png', 200 plus: 'post_expand_plus.png', 201 rotate: 'post_expand_rotate.gif', 202 quote: 'quote.png', 203 report: 'report.png', 204 notwatched: 'watch_thread_off.png', 205 watched: 'watch_thread_on.png', 206 help: 'question.png' 207 }; 208 209 paths = { 210 yotsuba_new: 'futaba/', 211 futaba_new: 'futaba/', 212 yotsuba_b_new: 'burichan/', 213 burichan_new: 'burichan/', 214 tomorrow: 'tomorrow/', 215 photon: 'photon/' 216 }; 217 218 url = '//s.4cdn.org/image/'; 219 220 if (window.devicePixelRatio >= 2) { 221 for (key in Main.icons) { 222 Main.icons[key] = Main.icons[key].replace('.', '@2x.'); 223 } 224 } 225 226 url += 'buttons/' + paths[Main.stylesheet]; 227 for (key in Main.icons) { 228 Main.icons[key] = url + Main.icons[key]; 229 } 230 }; 231 232 J.apiUrlFilter = function(url) { 233 return url + '?' + Math.round(Date.now() / 1000 / 3); 234 }; 235 236 J.openDeletePrompt = function(id) { 237 var html, cnt; 238 239 id = id.getAttribute('data-id'); 240 html = '<div class="extPanel reply"><div class="panelHeader">Delete Post No.' + id 241 + '<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="close-delete-prompt" src="' 242 + Main.icons.cross + '"></a>' 243 + '</span></div><span id="delete-prompt-inner">' 244 + '<input type="button" value="Delete Post" tabindex="-1" data-cmd="delete-post" data-id="' + id + '"> ' 245 + '<input type="button" value="Delete Image Only" data-cmd="delete-image" data-id="' + id + '">'; 246 247 cnt = document.createElement('div'); 248 cnt.className = 'UIPanel'; 249 cnt.id = 'delete-prompt'; 250 251 cnt.innerHTML = html; 252 cnt.addEventListener('click', J.closeDeletePrompt, false); 253 document.body.appendChild(cnt); 254 255 $.id('delete-prompt-inner').firstElementChild.focus(); 256 }; 257 258 J.closeDeletePrompt = function(e) { 259 var prompt; 260 261 if (!e || e.target.id == 'delete-prompt') { 262 if (prompt = $.id('delete-prompt')) { 263 prompt.removeEventListener('click', J.closeDeletePrompt, false); 264 document.body.removeChild(prompt); 265 } 266 } 267 }; 268 269 J.deletePost = function(btn, imageOnly) { 270 var id, xhr, form, msg, el, url, mode, del, isOp; 271 272 id = btn.getAttribute('data-id'); 273 274 isOp = $.id('t' + id); 275 276 form = new FormData(); 277 msg = 'Delete Post No.'; 278 url = 'https://sys.' + $L.d(Main.board) + '/' + Main.board + '/post'; 279 mode = window.thread_archived ? 'arcdel' : 'usrdel'; 280 281 if(imageOnly) { 282 msg = 'Delete Image No.'; 283 form.append('onlyimgdel', 'on'); 284 } 285 286 form.append(id, 'delete'); 287 form.append('mode', mode); 288 form.append('pwd', 'janitorise'); 289 290 (del = $.id('delete-prompt-inner')).textContent = 'Deleting...'; 291 292 xhr = new XMLHttpRequest(); 293 xhr.open('POST', url); 294 xhr.withCredentials = true; 295 xhr.onload = function() { 296 btn.src = Main.icons.cross; 297 if (this.status == 200) { 298 if (this.responseText.indexOf('Updating') != -1) { 299 if (!imageOnly) { 300 if (id == Main.tid) { 301 location.href = '//boards.' + $L.d(Main.board) + '/' + Main.board + '/'; 302 return; 303 } 304 else { 305 if (isOp) { 306 el = isOp.parentNode; 307 el.removeChild(isOp.nextElementSibling); 308 el.removeChild(isOp); 309 } 310 else { 311 el = $.id('pc' + id); 312 el.parentNode.removeChild(el); 313 } 314 } 315 } 316 else { 317 el = $.id('f' + id); 318 el.innerHTML = '<span class="fileThumb"><img alt="File deleted."' 319 + ' src="//s.4cdn.org/image/filedeleted' + (isOp ? '' : '-res') + '.gif"></span>'; 320 } 321 322 J.closeDeletePrompt(); 323 } 324 else { 325 del.textContent = 'Error: Post might have already been deleted, or is a sticky.'; 326 } 327 } 328 else { 329 del.textContent = 'Error: Wrong status while deleting No.' + id + ' (Status: ' + this.status + ').'; 330 } 331 }; 332 xhr.onerror = function() { 333 del.textContent = 'Error: Error while deleting No.' + id + ' (Status: ' + this.status + ').'; 334 }; 335 336 xhr.send(form); 337 }; 338 339 J.openBanReqWindow = function(btn) 340 { 341 var id; 342 343 id = btn.getAttribute('data-id'); 344 window.open('https://sys.' + $L.d(Main.board) + '/' + Main.board + '/admin?mode=admin&admin=banreq&id=' + id, '_blank', 'scrollBars=yes,resizable=no,toolbar=no,menubar=no,location=no,directories=no,width=400,height=245'); 345 }; 346 347 J.openBanReqFrame = function(btn) { 348 var id; 349 350 if (this.banReqCnt) { 351 this.close(); 352 } 353 354 id = btn.getAttribute('data-id'); 355 356 this.banReqCnt = document.createElement('div'); 357 this.banReqCnt.id = 'banReq'; 358 this.banReqCnt.className = 'extPanel reply'; 359 this.banReqCnt.setAttribute('data-trackpos', 'banReq-position'); 360 361 if (Config['banReq-position']) { 362 this.banReqCnt.style.cssText = Config['banReq-position']; 363 } 364 else { 365 this.banReqCnt.style.right = '0px'; 366 this.banReqCnt.style.top = '50px'; 367 } 368 369 this.banReqCnt.innerHTML = 370 '<div id="banReqHeader" class="drag postblock">Ban Request No.' + id 371 + '<img alt="X" src="' + Main.icons.cross + '" id="banReqClose" ' 372 + 'class="extButton" title="Close Window"></div>' 373 + '<iframe src="https://sys.' + $L.d(Main.board) + '/' 374 + Main.board + '/admin?mode=admin&admin=banreq&id=' + id 375 + '&noheader=true" width="400" height="230" frameborder="0"></iframe>'; 376 377 document.body.appendChild(this.banReqCnt); 378 379 window.addEventListener('message', J.onMessage, false); 380 document.addEventListener('keydown', J.onKeyDown, false); 381 382 $.id('banReqClose').addEventListener('click', J.closeBanReqFrame, false); 383 Draggable.set($.id('banReqHeader')); 384 }; 385 386 J.closeBanReqFrame = function() { 387 window.removeEventListener('message', J.onMessage, false); 388 document.removeEventListener('keydown', J.onKeyDown, false); 389 Draggable.unset($.id('banReqHeader')); 390 $.id('banReqClose').removeEventListener('click', J.closeBanReqFrame, false); 391 document.body.removeChild(J.banReqCnt); 392 J.banReqCnt = null; 393 }; 394 395 J.processMessage = function(data) { 396 if (!data) { 397 return {}; 398 } 399 400 data = data.split('-'); 401 402 return { 403 cmd: data[0], 404 type: data[1], 405 id: data.slice(2).join('-') 406 }; 407 }; 408 409 J.onKeyDown = function(e) { 410 if (e.keyCode == 27 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { 411 J.closeBanReqFrame(); 412 } 413 }; 414 415 J.onMessage = function(e) { 416 var msg; 417 418 if (e.origin !== 'https://sys.' + $L.d(Main.board)) { 419 return; 420 } 421 422 msg = J.processMessage(e.data); 423 424 if (msg.type !== 'ban') { 425 return; 426 } 427 428 if (msg.cmd === 'done' || msg.cmd === 'cancel') { 429 J.closeBanReqFrame(); 430 } 431 }; 432 433 /** 434 * Click handler 435 */ 436 J.onClick = function(e) { 437 var t, cmd; 438 439 if ((t = e.target) == document) { 440 return; 441 } 442 443 if (cmd = t.getAttribute('data-cmd')) { 444 switch (cmd) { 445 case 'at-refresh': 446 AdminTools.refreshReportCount(true); 447 break; 448 case 'delete-post': 449 case 'delete-image': 450 J.deletePost(t, (cmd === 'delete-image')); 451 break; 452 case 'open-delete-prompt': 453 J.openDeletePrompt(t); 454 break; 455 case 'close-delete-prompt': 456 J.closeDeletePrompt(); 457 break; 458 case 'open-banreq-prompt': 459 if (Config.inlinePopups) { 460 J.openBanReqFrame(t); 461 } 462 else { 463 J.openBanReqWindow(t); 464 } 465 break; 466 case 'at-msg': 467 AdminTools.resetMsgCount(); 468 break; 469 case 'toggle-file-spoiler': 470 J.setFileSpoiler(t); 471 break; 472 473 case 'prompt-spoiler': 474 if (confirm('Toggle spoiler?')) { 475 J.setFileSpoiler(t); 476 } 477 break; 478 479 case 'boardlist-open': 480 J.openBoardList(); 481 break; 482 case 'boardlist-close': 483 J.closeBoardList(); 484 break; 485 case 'boardlist-save': 486 J.saveBoardList(); 487 J.closeBoardList(); 488 break; 489 } 490 } 491 }; 492 493 J.onScroll = function() { 494 var end; 495 496 while (J.nextChunk.offsetTop < (document.documentElement.clientHeight + window.scrollY)) { 497 end = J.nextChunkIndex + J.chunkSize; 498 if (end >= J.postCount) { 499 J.parseRange(J.nextChunkIndex, J.postCount); 500 window.removeEventListener('scroll', J.onScroll, false); 501 return false; 502 } 503 else { 504 J.parseRange(J.nextChunkIndex, end); 505 } 506 } 507 508 return true; 509 }; 510 511 J.parseRange = function(start, end) { 512 var i, j, posts; 513 514 posts = document.getElementById('t' + Main.tid).getElementsByClassName('postInfo'); 515 516 for (i = start; i < end; ++i) { 517 j = posts[i]; 518 519 if (!j) { 520 break; 521 } 522 523 J.parsePost(j); 524 } 525 526 J.nextChunkIndex = i; 527 J.nextChunk = posts[i]; 528 }; 529 530 J.onParsingDone = function(e) { 531 var i, tid, offset, limit, posts; 532 533 if (Config.useIconButtons) { 534 if (e) { 535 tid = e.detail.threadId; 536 offset = e.detail.offset; 537 limit = e.detail.limit; 538 posts = document.getElementById('t' + tid).getElementsByClassName('postInfo'); 539 } 540 else { 541 offset = 0; 542 posts = document.getElementsByClassName('postInfo'); 543 limit = posts.length; 544 } 545 546 for (i = offset; i < limit; ++i) { 547 J.parsePost(posts[i]); 548 } 549 } 550 }; 551 552 J.onPostMenuReady = function(e) { 553 var el, pid, menu, flag; 554 555 pid = e.detail.postId; 556 menu = e.detail.node; 557 558 el = document.createElement('li'); 559 el.className = 'dd-admin'; 560 el.setAttribute('data-cmd', 'open-delete-prompt'); 561 el.setAttribute('data-id', pid); 562 el.textContent = 'Delete'; 563 menu.appendChild(el); 564 565 if (window.spoilers && (el = $.id('fT' + pid))) { 566 flag = $.cls('imgspoiler', el.parentNode)[0] ? 0 : 1; 567 el = document.createElement('li'); 568 el.className = 'dd-admin'; 569 el.setAttribute('data-cmd', 'toggle-file-spoiler'); 570 el.setAttribute('data-id', pid); 571 el.setAttribute('data-flag', flag); 572 el.textContent = (flag ? 'Set' : 'Unset') + ' Spoiler'; 573 menu.appendChild(el); 574 } 575 576 if (window.thread_archived) { 577 return; 578 } 579 580 el = document.createElement('li'); 581 el.className = 'dd-admin'; 582 el.setAttribute('data-cmd', 'open-banreq-prompt'); 583 el.setAttribute('data-id', pid); 584 el.textContent = 'Ban request'; 585 menu.appendChild(el); 586 }; 587 588 J.parsePost = function(postInfo) { 589 var pid, html, cnt, tail; 590 591 pid = postInfo.id.slice(2); 592 593 html = '<img class="extButton" alt="X" data-cmd="open-delete-prompt" data-id="' 594 + pid + '" src="' + Main.icons.cross 595 + '" title="Delete">'; 596 597 if (window.spoilers && (el = $.id('fT' + pid))) { 598 html += '<img class="extButton" alt="S" data-cmd="prompt-spoiler" data-id="' 599 + pid + '" src="' + J.icons.spoiler 600 + '" title="Toggle Spoiler">'; 601 } 602 603 if (!window.thread_archived) { 604 html += '<img class="extButton" alt="B" data-cmd="open-banreq-prompt" data-id="' 605 + pid + '" src="' + J.icons.ban 606 + '" title="Ban Request">'; 607 } 608 609 cnt = document.createElement('div'); 610 cnt.className = 'extControls'; 611 cnt.innerHTML = html; 612 613 tail = postInfo.getElementsByClassName('postMenuBtn')[0]; 614 615 postInfo.insertBefore(cnt, tail); 616 }; 617 618 J.displayJCount = function(jLink, jLinkBot, no, delta) { 619 var msg; 620 621 $.addClass(jLink, 'j-newposts'); 622 $.addClass(jLinkBot, 'j-newposts'); 623 jLink.setAttribute('data-no', no); 624 jLinkBot.setAttribute('data-no', no); 625 jLink.textContent = jLinkBot.textContent = 'j +' + delta; 626 627 msg = delta + ' new post' + (delta > 1 ? 's' : ''); 628 629 Main.addTooltip(jLink, msg, 'j-tooltip'); 630 Main.addTooltip(jLinkBot, msg, 'j-tooltip-bot'); 631 }; 632 633 J.refreshJCount = function() { 634 var stored, jLink, jLinkBot, xhr; 635 636 jLink = $.id('j-link'); 637 jLinkBot = $.id('j-link-bot'); 638 639 if (!jLink || !jLinkBot) { 640 return; 641 } 642 643 jLink = jLink.firstElementChild; 644 jLinkBot = jLinkBot.firstElementChild; 645 646 if (stored = localStorage.getItem('4chan-j-count')) { 647 stored = JSON.parse(stored); 648 } 649 650 if (!stored || (Date.now() - stored.time) >= 10000) { 651 xhr = new XMLHttpRequest(); 652 xhr.open('GET', 'https://sys.4chan.org/j/1mcQTXbjW5WO.php?&' + Date.now()); 653 xhr.withCredentials = true; 654 xhr.onloadend = function() { 655 var data, obj, delta; 656 if (this.status == 200 || this.status == 304) { 657 data = JSON.parse(this.responseText); 658 if (!stored || Main.board == 'j') { 659 obj = { time: Date.now(), no: data.no }; 660 } 661 else if (data.no > stored.no) { 662 delta = data.no - stored.no; 663 J.displayJCount(jLink, jLinkBot, data.no, delta); 664 obj = { time: Date.now(), no: stored.no, delta: delta }; 665 } 666 if (obj) { 667 localStorage.setItem('4chan-j-count', JSON.stringify(obj)); 668 } 669 } 670 else { 671 console.log('Error: Could not load /j/ post count (Status: ' + this.status + ').'); 672 } 673 }; 674 xhr.send(null); 675 } 676 else if (stored.delta) { 677 J.displayJCount(jLink, jLinkBot, stored.no, stored.delta); 678 } 679 }; 680 681 J.clearJCount = function() { 682 var obj, no, tt, ttbot; 683 684 tt = $.id('j-tooltip'); 685 ttbot = $.id('j-tooltip-bot'); 686 687 if (!tt) { 688 return; 689 } 690 691 no = this.getAttribute('data-no'); 692 obj = { time: Date.now(), no: no }; 693 localStorage.setItem('4chan-j-count', JSON.stringify(obj)); 694 695 tt.parentNode.removeChild(tt); 696 ttbot.parentNode.removeChild(ttbot); 697 698 setTimeout(function() { 699 var nodes = $.cls('j-newposts'); 700 if (nodes[0]) { 701 nodes[0].textContent = 'j'; 702 $.removeClass(nodes[0], 'j-newposts'); 703 nodes[0].textContent = 'j'; 704 $.removeClass(nodes[0], 'j-newposts'); 705 } 706 }, 10); 707 }; 708 709 J.icons = { 710 ban: 'ban.png', 711 spoiler: 's.png' 712 }; 713 714 J.initIcons = function() { 715 var key, paths, url; 716 717 paths = { 718 yotsuba_new: 'futaba/', 719 futaba_new: 'futaba/', 720 yotsuba_b_new: 'burichan/', 721 burichan_new: 'burichan/', 722 tomorrow: 'tomorrow/', 723 photon: 'photon/' 724 }; 725 726 url = '//s.4cdn.org/image/buttons/' + paths[Main.stylesheet]; 727 728 if (window.devicePixelRatio >= 2) { 729 for (key in J.icons) { 730 J.icons[key] = J.icons[key].replace('.', '@2x.'); 731 } 732 } 733 734 for (key in J.icons) { 735 J.icons[key] = url + J.icons[key]; 736 } 737 }; 738 739 J.initNavLinks = function() { 740 var el, nav, navbot; 741 742 nav = $.id('navtopright'); 743 navbot = $.id('navbotright'); 744 745 // [j] link 746 el = document.createElement('span'); 747 el.id = 'j-link'; 748 el.innerHTML = '[<a href="https://sys.4chan.org/j/" title="Janitor & Moderator Discussion">j</a>]'; 749 el.firstElementChild.addEventListener('mouseup', J.clearJCount, false); 750 nav.parentNode.insertBefore(el, nav); 751 752 // [j] bottom link 753 el = el.cloneNode(true); 754 el.id = 'j-link-bot'; 755 el.firstElementChild.addEventListener('mouseup', J.clearJCount, false); 756 navbot.parentNode.insertBefore(el, navbot); 757 758 J.refreshJCount(); 759 }; 760 761 J.openBoardList = function() { 762 var cnt; 763 764 if ($.id('boardList')) { 765 return; 766 } 767 768 cnt = document.createElement('div'); 769 cnt.id = 'boardList'; 770 cnt.className = 'UIPanel'; 771 cnt.setAttribute('data-cmd', 'boardlist-close'); 772 cnt.innerHTML = '\ 773 <div class="extPanel reply"><div class="panelHeader">Boards\ 774 <span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="boardlist-close" src="' 775 + Main.icons.cross + '"></a></span></div>\ 776 <input placeholder="Example: jp tg mu or all" id="boardListBox" type="text" value="' 777 + (localStorage.getItem('4chan-boardlist') || '') + '">\ 778 <div class="center"><button id="boardListSave" data-cmd="boardlist-save">Save</button></div>\ 779 </td></tr></tfoot></table></div>'; 780 781 document.body.appendChild(cnt); 782 cnt.addEventListener('click', this.onClick, false); 783 }; 784 785 J.saveBoardList = function() { 786 var input; 787 788 if (input = $.id('boardListBox')) { 789 localStorage.setItem('4chan-boardlist', input.value); 790 } 791 }; 792 793 J.closeBoardList = function() { 794 var cnt; 795 796 if (cnt = $.id('boardList')) { 797 cnt.removeEventListener('click', this.onClick, false); 798 document.body.removeChild(cnt); 799 } 800 }; 801 802 J.setFileSpoiler = function(t) { 803 var xhr, pid, flag, el; 804 805 pid = t.getAttribute('data-id'); 806 flag = t.getAttribute('data-flag'); 807 808 if (!pid) { 809 return; 810 } 811 812 el = $.id('f' + pid); 813 814 if (!flag) { 815 flag = $.cls('imgspoiler', el.parentNode)[0] ? 0 : 1; 816 } 817 818 if (!el || el.hasAttribute('data-processing')) { 819 return; 820 } 821 822 xhr = new XMLHttpRequest(); 823 xhr.open('GET', 'https://sys.' + $L.d(Main.board) + '/' + Main.board 824 + '/admin.php?admin=spoiler&pid=' + pid + '&flag=' + flag, true); 825 xhr.withCredentials = true; 826 xhr.onload = J.onFileSpoilerLoad; 827 xhr.onerror = J.onFileSpoilerError; 828 xhr._pid = +pid; 829 xhr._flag = +flag; 830 831 Feedback.notify('Processing...', null); 832 833 el.setAttribute('data-processing', '1'); 834 835 xhr.send(null); 836 }; 837 838 J.onFileSpoilerLoad = function() { 839 var el, el2; 840 841 Feedback.hideMessage(); 842 843 if (this.responseText !== '1') { 844 if (this.responseText === '-1') { 845 Feedback.error('You are not logged in'); 846 } 847 else { 848 Feedback.error("Couldn't set spoiler flag for post No." + this._pid); 849 } 850 851 return; 852 } 853 854 if (!(el = $.id('f' + this._pid))) { 855 return; 856 } 857 858 el.removeAttribute('data-processing'); 859 860 if (!(el = $.cls('fileThumb', el)[0])) { 861 return; 862 } 863 864 if (this._flag) { 865 $.addClass(el, 'imgspoiler'); 866 867 el2 = el.previousElementSibling; 868 el2.setAttribute('title', el2.firstElementChild.textContent); 869 870 if (!Config.revealSpoilers) { 871 el = $.tag('img', el)[0]; 872 el.style.width = el.style.height = '100px'; 873 el.src = '//s.4cdn.org/image/spoiler-' + Main.board + '.png'; 874 } 875 } 876 else { 877 if (!Config.revealSpoilers) { 878 Parser.revealImageSpoiler(el); 879 } 880 $.removeClass(el, 'imgspoiler'); 881 } 882 }; 883 884 J.onFileSpoilerError = function() { 885 var el; 886 887 if (!(el = $.id('f' + this._pid))) { 888 return; 889 } 890 el.removeAttribute('data-processing'); 891 Feedback.error("Couldn't update the spoiler flag for post No." + this.pid); 892 }; 893 894 J.initCatalog = function() { 895 var storage; 896 897 window.Main = { 898 board: location.pathname.split(/\//)[1] 899 }; 900 901 window.Main.addTooltip = function(link, message, id) { 902 var el, pos; 903 904 el = document.createElement('div'); 905 el.className = 'click-me'; 906 if (id) { 907 el.id = id; 908 } 909 el.innerHTML = message || 'Change your settings'; 910 link.parentNode.appendChild(el); 911 912 pos = (link.offsetWidth - el.offsetWidth + link.offsetLeft - el.offsetLeft) / 2; 913 el.style.marginLeft = pos + 'px'; 914 915 return el; 916 }; 917 918 if (J.stylesheet = J.getCookie(window.style_group)) { 919 J.stylesheet = J.stylesheet.toLowerCase().replace(/ /g, '_'); 920 } 921 else { 922 J.stylesheet = 923 style_group == 'nws_style' ? 'yotsuba_new' : 'yotsuba_b_new'; 924 } 925 926 Main.stylesheet = J.stylesheet; 927 928 J.initIconsCatalog(); 929 930 J.addCss(); // fixme 931 932 document.addEventListener('click', J.onClick, false); 933 934 J.runCatalog(); 935 }; 936 937 J.runCatalog = function () { 938 var threads; 939 //J.addCss(); // fixme 940 //document.removeEventListener('4chanMainInit', J.runCatalog, false); 941 942 J.initNavLinks(); 943 944 if (!FC.hasMobileLayout) { 945 AdminTools.init(); 946 } 947 948 threads = $.id('threads'); 949 950 $.on(threads, 'mouseover', J.onThreadMouseOver); 951 //$.on(threads, 'mouseout', J.onThreadMouseOut); 952 }; 953 954 J.init = function() { 955 var ts; 956 957 Config.boardList = true; 958 959 SettingsMenu.options['Janitor'] = { 960 boardList: [ 'Janitor Boards [<a href="javascript:;" data-cmd="boardlist-open">Select</a>]', 'Select boards to enable janitor buttons on', true ], 961 useIconButtons: [ 'Use icon buttons', 'Display old-style buttons instead of using drop-down' ], 962 changeUpdateDelay: [ 'Reduce auto-update delay', 'Reduce the thread updater delay', true ], 963 fixedAdminToolbox: [ 'Pin Janitor Tools to the page', 'Janitor Tools will scroll with you' ], 964 inlinePopups: [ 'Inline ban request panel', 'Open ban request panel in browser window, instead of a popup' ], 965 disableMngExt: [ 'Disable janitor extension', 'Completely disable the janitor extension (overrides any checked boxes)', true ] 966 }; 967 968 if (Config.disableMngExt) { 969 return; 970 } 971 972 J.addCss(); 973 974 if (Config.useIconButtons) { 975 J.initIcons(); 976 } 977 978 QR.noCooldown = QR.noCaptcha = true; 979 980 document.addEventListener('click', J.onClick, false); 981 document.addEventListener('DOMContentLoaded', J.run, false); 982 }; 983 984 J.run = function() { 985 var boards, posts, nav, el; 986 987 document.removeEventListener('DOMContentLoaded', J.run, false); 988 989 J.initNavLinks(); 990 991 if (!Main.hasMobileLayout) { 992 AdminTools.init(); 993 } 994 995 if (Config.revealSpoilers) { 996 $.addClass(document.body, 'reveal-img-spoilers'); 997 } 998 999 if (Config.threadUpdater && Main.tid) { 1000 if (Config.changeUpdateDelay) { 1001 ThreadUpdater.delayIdHidden = 3; 1002 ThreadUpdater.delayRange = [ 5, 10, 15, 20, 30, 60 ]; 1003 ThreadUpdater.apiUrlFilter = J.apiUrlFilter; 1004 } 1005 } 1006 1007 boards = localStorage.getItem('4chan-boardlist') || ''; 1008 1009 if (Main.board != 'j' && (boards == 'all' || boards.split(/[, ]+/).indexOf(Main.board) != -1)) { 1010 if (Config.useIconButtons && !Main.hasMobileLayout) { 1011 if (Main.tid) { 1012 posts = document.getElementById('t' + Main.tid).getElementsByClassName('postInfo'); 1013 J.postCount = posts.length; 1014 if (J.postCount > J.chunkSize) { 1015 J.nextChunk = posts[0]; 1016 window.addEventListener('scroll', J.onScroll, false); 1017 J.onScroll(); 1018 } 1019 else { 1020 J.onParsingDone(); 1021 } 1022 } 1023 else { 1024 J.onParsingDone(); 1025 } 1026 1027 document.addEventListener('4chanParsingDone', J.onParsingDone, false); 1028 } 1029 1030 document.addEventListener('4chanPostMenuReady', J.onPostMenuReady, false); 1031 } 1032 1033 if (nav = $.id('boardSelectMobile')) { 1034 el = document.createElement('option'); 1035 el.value = 'j'; 1036 el.textContent = '/j/ - Janitors & Moderators'; 1037 nav.insertBefore(el, nav.firstElementChild); 1038 } 1039 }; 1040 1041 J.getCookie = function(name) { 1042 var i, c, ca, key; 1043 1044 key = name + "="; 1045 ca = document.cookie.split(';'); 1046 1047 for (i = 0; c = ca[i]; ++i) { 1048 while (c.charAt(0) == ' ') { 1049 c = c.substring(1, c.length); 1050 } 1051 if (c.indexOf(key) === 0) { 1052 return decodeURIComponent(c.substring(key.length, c.length)); 1053 } 1054 } 1055 return null; 1056 }; 1057 1058 J.addCss = function() { 1059 var style, css; 1060 1061 css = '\ 1062 #adminToolbox {\ 1063 max-width: 256px;\ 1064 display: block;\ 1065 position: absolute;\ 1066 padding: 3px;\ 1067 }\ 1068 #adminToolbox h4 {\ 1069 font-size: 12px;\ 1070 margin: 2px 0 0;\ 1071 padding: 0;\ 1072 font-weight: normal;\ 1073 }\ 1074 #adminToolbox li {\ 1075 list-style: none;\ 1076 }\ 1077 #adminToolbox ul {\ 1078 padding: 0;\ 1079 margin: 2px 0 0 10px;\ 1080 }\ 1081 #atHeader {\ 1082 height: 17px;\ 1083 font-weight: bold;\ 1084 padding-bottom: 2px;\ 1085 }\ 1086 #atRefresh {\ 1087 margin: -1px 0 0 3px;\ 1088 }\ 1089 .post-ip {\ 1090 margin-left: 5px;\ 1091 }\ 1092 #delete-prompt > div {\ 1093 text-align: center;\ 1094 }\ 1095 #watchList li:first-child {\ 1096 margin-top: 3px;\ 1097 padding-top: 2px;\ 1098 border-top: 1px solid rgba(0, 0, 0, 0.20);\ 1099 }\ 1100 .photon #atHeader {\ 1101 border-bottom: 1px solid #ccc;\ 1102 }\ 1103 .yotsuba_new #atHeader {\ 1104 border-bottom: 1px solid #d9bfb7;\ 1105 }\ 1106 .yotsuba_b_new #atHeader {\ 1107 border-bottom: 1px solid #b7c5d9;\ 1108 }\ 1109 .tomorrow #atHeader {\ 1110 border-bottom: 1px solid #111;\ 1111 }\ 1112 #at-illegal {\ 1113 color: red;\ 1114 }\ 1115 #at-msg-cnt {\ 1116 display: none;\ 1117 }\ 1118 .j-newposts {\ 1119 font-weight: bold !important;\ 1120 }\ 1121 #j-link,\ 1122 #j-link-bot {\ 1123 margin-right: 3px;\ 1124 display: inline-block;\ 1125 margin-left: 3px;\ 1126 }\ 1127 #boardList input {\ 1128 width: 385px;\ 1129 margin: auto;\ 1130 display: block;\ 1131 }\ 1132 #boardListSave {\ 1133 margin-top: 5px;\ 1134 }\ 1135 #banReqClose {\ 1136 float: right;\ 1137 }\ 1138 #banReq iframe {\ 1139 overflow: hidden;\ 1140 }\ 1141 #banReq {\ 1142 display: block;\ 1143 position: fixed;\ 1144 padding: 2px;\ 1145 font-size: 10pt;\ 1146 height: 250px;\ 1147 }\ 1148 #banReqHeader {\ 1149 text-align: center;\ 1150 margin-bottom: 1px;\ 1151 padding: 0;\ 1152 height: 18px;\ 1153 line-height: 18px;\ 1154 }\ 1155 #captchaFormPart {\ 1156 display: none;\ 1157 }\ 1158 .mobileExtControls {\ 1159 float: right;\ 1160 font-size: 11px;\ 1161 margin-bottom: 3px;\ 1162 }\ 1163 .ws .mobileExtControls {\ 1164 color: #34345C;\ 1165 }\ 1166 .nws .mobileExtControls {\ 1167 color: #0000EE;\ 1168 }\ 1169 .reply .mobileExtControls {\ 1170 margin-right: 5px;\ 1171 }\ 1172 .mobileExtControls span {\ 1173 margin-left: 10px;\ 1174 }\ 1175 .mobileExtControls span:after {\ 1176 content: "]";\ 1177 }\ 1178 .mobileExtControls span:before {\ 1179 content: "[";\ 1180 }\ 1181 .nws .mobileExtControls span:after {\ 1182 color: #800000;\ 1183 }\ 1184 .nws .mobileExtControls span:before {\ 1185 color: #800000;\ 1186 }\ 1187 .ws .mobileExtControls span:after {\ 1188 color: #000;\ 1189 }\ 1190 .ws .mobileExtControls span:before {\ 1191 color: #000;\ 1192 }\ 1193 .m-dark .mobileExtControls,\ 1194 .m-dark .mobileExtControls span:after,\ 1195 .m-dark .mobileExtControls span:before {\ 1196 color: #707070 !important;\ 1197 }\ 1198 .dd-admin {\ 1199 text-indent: 5px;\ 1200 }\ 1201 .dd-admin:before {\ 1202 color: #FF0000;\ 1203 content: "•";\ 1204 left: -3px;\ 1205 position: absolute;\ 1206 }\ 1207 .extPanel {\ 1208 border: 1px solid rgba(0, 0, 0, 0.2);\ 1209 }\ 1210 .extPanel img.pointer { width: 18px; height: 18px }\ 1211 .drag {\ 1212 -moz-user-select: none !important;\ 1213 cursor: move !important;\ 1214 }\ 1215 .reveal-img-spoilers .imgspoiler::before {\ 1216 content: " ";\ 1217 width:0.75em;\ 1218 height:0.75em;\ 1219 border-radius: 0.5em;\ 1220 position: absolute;\ 1221 display: block;\ 1222 background: red;\ 1223 margin-top: 1px;\ 1224 margin-left: 1px;\ 1225 pointer-events: none;\ 1226 }\ 1227 .reveal-img-spoilers.is_catalog .imgspoiler::before { margin-top: 4px; margin-left: 12px;}\ 1228 .reveal-img-spoilers .imgspoiler:hover::before { background: #fff; }'; 1229 1230 style = document.createElement('style'); 1231 style.setAttribute('type', 'text/css'); 1232 style.textContent = css; 1233 document.head.appendChild(style); 1234 }; 1235 1236 if (/https?:\/\/boards\.(?:4chan|4channel)\.org\/[a-z0-9]+\/catalog($|#.*$)/.test(location.href)) { 1237 J.initCatalog(); 1238 } 1239 else { 1240 J.init(); 1241 //J.run(); 1242 } 1243 1244 })();