diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6af5acc..192f39e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,12 @@ - It is now possible to open all watched threads via the `Open all threads` button in the Thread Watcher's menu. - Added the `Current Board` setting to switch between showing watched threads from the current board or all boards, disabled by default. - About dead (404'd) threads: - - Dead threads will be typographically indicated with a strikethrough. - - Dead threads will directly link to the corresponding archive when available. - - A button to prune all 404'd threads from the list is now available. - - Added the `Auto Prune` setting to automatically prune 404'd threads, disabled by default. + - The current thread is now highlighted in the list of watched threads. - Watching the current thread can be done in the Header's menu too. - Removed the `Check for Updates` setting: diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index ed4013ae5..5278a3576 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1415,10 +1415,7 @@ })(); Polyfill = { - init: function() { - Polyfill.toBlob(); - return Polyfill.visibility(); - }, + init: function() {}, toBlob: function() { var _base; @@ -1460,11 +1457,11 @@ Header = { init: function() { - var barFixedToggler, barPositionToggler, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, + var barFixedToggler, barPositionToggler, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, _this = this; this.menu = new UI.Menu('header'); - this.menuButton = $.el('span', { + menuButton = $.el('span', { className: 'menu-button', innerHTML: '' }); @@ -1496,7 +1493,7 @@ this.headerToggler = headerToggler.firstElementChild; this.footerToggler = footerToggler.firstElementChild; this.customNavToggler = customNavToggler.firstElementChild; - $.on(this.menuButton, 'click', this.menuToggle); + $.on(menuButton, 'click', this.menuToggle); $.on(this.barFixedToggler, 'change', this.toggleBarFixed); $.on(this.barPositionToggler, 'change', this.toggleBarPosition); $.on(this.linkJustifyToggler, 'change', this.toggleLinkJustify); @@ -1511,7 +1508,7 @@ $.sync('Bottom Header', Header.setBarPosition); $.sync('Header auto-hide', Header.setBarVisibility); $.sync('Centered links', Header.setLinkJustify); - this.addShortcut(Header.menuButton); + this.addShortcut(menuButton); $.event('AddMenuEntry', { type: 'header', el: $.el('span', { @@ -2030,6 +2027,9 @@ threadFromRoot: function(root) { return g.threads["" + g.BOARD + "." + root.id.slice(1)]; }, + threadFromNode: function(node) { + return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node)); + }, postFromRoot: function(root) { var boardID, index, link, post, postID; @@ -2047,8 +2047,8 @@ postFromNode: function(root) { return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root)); }, - contextFromNode: function(quotelink) { - return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', quotelink)); + contextFromNode: function(node) { + return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', node)); }, postDataFromLink: function(link) { var boardID, path, postID, threadID, _ref; @@ -2496,7 +2496,7 @@ }; Menu.prototype.focus = function(entry) { - var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; + var cHeight, cWidth, eRect, focused, sRect, submenu, _i, _len, _ref; while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { $.rmClass(focused, 'focused'); @@ -2514,13 +2514,20 @@ eRect = entry.getBoundingClientRect(); cHeight = doc.clientHeight; cWidth = doc.clientWidth; - _ref1 = eRect.top + sRect.height < cHeight ? ['0px', 'auto'] : ['auto', '0px'], top = _ref1[0], bottom = _ref1[1]; - _ref2 = eRect.right + sRect.width < cWidth ? ['100%', 'auto'] : ['auto', '100%'], left = _ref2[0], right = _ref2[1]; - style = submenu.style; - style.top = top; - style.bottom = bottom; - style.left = left; - return style.right = right; + if (eRect.top + sRect.height < cHeight) { + $.addClass(submenu, 'top'); + $.rmClass(submenu, 'bottom'); + } else { + $.addClass(submenu, 'bottom'); + $.rmClass(submenu, 'top'); + } + if (eRect.right + sRect.width < cWidth) { + $.addClass(submenu, 'left'); + return $.rmClass(submenu, 'right'); + } else { + $.addClass(submenu, 'right'); + return $.rmClass(submenu, 'left'); + } }; Menu.prototype.addEntry = function(e) { @@ -3315,7 +3322,7 @@ post.nodes.stub = $.el('div', { className: 'stub' }); - $.add(post.nodes.stub, !Conf['Menu'] ? a : [a, $.tn(' '), button = Menu.makeButton(post)]); + $.add(post.nodes.stub, Conf['Menu'] ? [a, $.tn(' '), button = Menu.makeButton(post)] : a); return $.prepend(post.nodes.root, post.nodes.stub); }, show: function(post, showRecursively) { @@ -3646,7 +3653,7 @@ return ThreadHiding.saveHiddenState(thread); }, hide: function(thread, makeStub) { - var OP, a, button, numReplies, opInfo, span, threadRoot; + var OP, a, numReplies, opInfo, span, threadRoot; if (makeStub == null) { makeStub = Conf['Stubs']; @@ -3666,7 +3673,7 @@ thread.stub = $.el('div', { className: 'stub' }); - $.add(thread.stub, !Conf['Menu'] ? a : [a, $.tn(' '), button = Menu.makeButton(OP)]); + $.add(thread.stub, Conf['Menu'] ? [a, $.tn(' '), Menu.makeButton()] : a); return $.prepend(threadRoot, thread.stub); }, show: function(thread) { @@ -3776,26 +3783,23 @@ }); }, node: function() { - var board, boardID, quotelink, quotelinks, quotes, thread, threadID, _i, _len, _ref, _ref1; + var board, boardID, quotelink, thread, threadID, _i, _len, _ref, _ref1, _ref2; if (this.isClone && this.thread === this.context.thread) { return; } - if (!(quotes = this.quotes).length) { - return; - } - quotelinks = this.nodes.quotelinks; _ref = this.isClone ? this.context : this, board = _ref.board, thread = _ref.thread; - for (_i = 0, _len = quotelinks.length; _i < _len; _i++) { - quotelink = quotelinks[_i]; - _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, threadID = _ref1.threadID; + _ref1 = this.nodes.quotelinks; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + quotelink = _ref1[_i]; + _ref2 = Get.postDataFromLink(quotelink), boardID = _ref2.boardID, threadID = _ref2.threadID; if (!threadID) { continue; } if (this.isClone) { quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, ''); } - if (boardID === this.board.ID && threadID !== thread.ID) { + if (boardID === board.ID && threadID !== thread.ID) { $.add(quotelink, $.tn(QuoteCT.text)); } } @@ -3942,7 +3946,7 @@ }); }, node: function() { - var boardID, op, postID, quotelink, quotelinks, quotes, _i, _j, _len, _len1, _ref; + var boardID, fullID, i, postID, quotelink, quotelinks, quotes, _ref; if (this.isClone && this.thread === this.context.thread) { return; @@ -3952,19 +3956,19 @@ } quotelinks = this.nodes.quotelinks; if (this.isClone && quotes.contains(this.thread.fullID)) { - for (_i = 0, _len = quotelinks.length; _i < _len; _i++) { - quotelink = quotelinks[_i]; + i = 0; + while (quotelink = quotelinks[i++]) { quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, ''); } } - op = (this.isClone ? this.context : this).thread.fullID; - if (!quotes.contains(op)) { + fullID = (this.isClone ? this.context : this).thread.fullID; + if (!quotes.contains(fullID)) { return; } - for (_j = 0, _len1 = quotelinks.length; _j < _len1; _j++) { - quotelink = quotelinks[_j]; + i = 0; + while (quotelink = quotelinks[i++]) { _ref = Get.postDataFromLink(quotelink), boardID = _ref.boardID, postID = _ref.postID; - if (("" + boardID + "." + postID) === op) { + if (("" + boardID + "." + postID) === fullID) { $.add(quotelink, $.tn(QuoteOP.text)); } } @@ -5363,7 +5367,7 @@ } _ref = QR.nodes, com = _ref.com, thread = _ref.thread; if (!com.value) { - thread.value = Get.contextFromNode(this).thread; + thread.value = Get.threadFromNode(this); } caretPos = com.selectionStart; com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd); @@ -6341,7 +6345,7 @@ href: 'javascript:;' }); $.on(this.EAI, 'click', ImageExpand.cb.toggleAll); - Header.addShortcut(this.EAI); + Header.addShortcut(this.EAI, 2); return Post.prototype.callbacks.push({ name: 'Image Expansion', cb: this.node @@ -7089,48 +7093,47 @@ } }; - Menu = (function() { - var a; + Menu = { + init: function() { + if (g.VIEW === 'catalog' || !Conf['Menu']) { + return; + } + this.menu = new UI.Menu('post'); + return Post.prototype.callbacks.push({ + name: 'Menu', + cb: this.node + }); + }, + node: function() { + if (this.isClone) { + return $.on($('.menu-button', this.nodes.info), 'click', Menu.toggle); + } else { + return $.add(this.nodes.info, [$.tn('\u00A0'), Menu.makeButton()]); + } + }, + makeButton: (function() { + var a; - a = $.el('a', { - className: 'menu-button brackets-wrap', - innerHTML: '', - href: 'javascript:;' - }); - return { - init: function() { - if (g.VIEW === 'catalog' || !Conf['Menu']) { - return; - } - this.menu = new UI.Menu('post'); - return Post.prototype.callbacks.push({ - name: 'Menu', - cb: this.node - }); - }, - node: function() { + a = $.el('a', { + className: 'menu-button brackets-wrap', + innerHTML: '', + href: 'javascript:;' + }); + return function() { var button; - if (this.isClone) { - button = $('.menu-button', this.nodes.info); - } else { - button = a.cloneNode(true); - $.add(this.nodes.info, [$.tn('\u00A0'), button]); - } - return $.on(button, 'click', Menu.toggle); - }, - makeButton: function() { - var el; + button = a.cloneNode(true); + $.on(button, 'click', Menu.toggle); + return button; + }; + })(), + toggle: function(e) { + var post; - el = a.cloneNode(true); - $.on(el, 'click', Menu.toggle); - return el; - }, - toggle: function(e) { - return Menu.menu.toggle(e, this, Get.postFromNode(this)); - } - }; - })(); + post = Get.postFromNode(this); + return Menu.menu.toggle(e, this, post); + } + }; ReportLink = { init: function() { @@ -7915,12 +7918,14 @@ fetching: 0 }, fetchAllStatus: function() { - var thread, _i, _len, _ref; + var thread, threads, _i, _len; + if (!(threads = ThreadWatcher.getAll()).length) { + return; + } ThreadWatcher.status.textContent = '...'; - _ref = ThreadWatcher.getAll(); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - thread = _ref[_i]; + for (_i = 0, _len = threads.length; _i < _len; _i++) { + thread = threads[_i]; ThreadWatcher.fetchStatus(thread); } }, @@ -9113,7 +9118,7 @@ return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); }, cbToggle: function() { - return ExpandThread.toggle(Get.threadFromRoot(this.parentNode)); + return ExpandThread.toggle(Get.threadFromNode(this)); }, toggle: function(thread) { var a, files, filesCount, inlined, num, post, posts, postsCount, reply, threadRoot, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3, _ref4; @@ -10748,9 +10753,27 @@ }; Main = { - init: function(items) { - var db, flatten, _i, _len; + init: function() { + var db, flatten, pathname, _i, _len, _ref; + pathname = location.pathname.split('/'); + g.BOARD = new Board(pathname[1]); + if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { + return; + } + g.VIEW = (function() { + switch (pathname[2]) { + case 'res': + return 'thread'; + case 'catalog': + return 'catalog'; + default: + return 'index'; + } + })(); + if (g.VIEW === 'thread') { + g.THREADID = +pathname[3]; + } flatten = function(parent, obj) { var key, val; @@ -10774,16 +10797,19 @@ } Conf['selectedArchives'] = {}; Conf['CachedTitles'] = []; - $.get(Conf, Main.initFeatures); + $.get(Conf, function(items) { + $.extend(Conf, items); + return Main.initFeatures(); + }); $.on(d, '4chanMainInit', Main.initStyle); return $.asap((function() { return d.head && $('link[rel="shortcut icon"]', d.head) || d.readyState !== 'loading'; }), Main.initStyle); }, - initFeatures: function(items) { + initFeatures: function() { + Conf; var init, pathname, _ref; - Conf = items; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { @@ -10950,7 +10976,7 @@ initReady: function() { var board, err, errors, href, passLink, postRoot, posts, styleSelector, thread, threadRoot, threads, _i, _j, _len, _len1, _ref, _ref1; - if (d.title === '4chan - 404 Not Found') { + if (['4chan - Temporarily Offline', '4chan - 404 Not Found'].contains(d.title)) { if (Conf['404 Redirect'] && g.VIEW === 'thread') { href = Redirect.to('thread', { boardID: g.BOARD.ID, @@ -10961,9 +10987,7 @@ } return; } - if (!$.hasClass(doc, 'fourchan-x')) { - Main.initStyle(); - } + Main.initStyle(); if (board = $('.board')) { threads = []; posts = []; diff --git a/builds/crx/script.js b/builds/crx/script.js index 3f1a9fe66..e4316e09f 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1473,11 +1473,11 @@ Header = { init: function() { - var barFixedToggler, barPositionToggler, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, + var barFixedToggler, barPositionToggler, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, _this = this; this.menu = new UI.Menu('header'); - this.menuButton = $.el('span', { + menuButton = $.el('span', { className: 'menu-button', innerHTML: '' }); @@ -1509,7 +1509,7 @@ this.headerToggler = headerToggler.firstElementChild; this.footerToggler = footerToggler.firstElementChild; this.customNavToggler = customNavToggler.firstElementChild; - $.on(this.menuButton, 'click', this.menuToggle); + $.on(menuButton, 'click', this.menuToggle); $.on(this.barFixedToggler, 'change', this.toggleBarFixed); $.on(this.barPositionToggler, 'change', this.toggleBarPosition); $.on(this.linkJustifyToggler, 'change', this.toggleLinkJustify); @@ -1524,7 +1524,7 @@ $.sync('Bottom Header', Header.setBarPosition); $.sync('Header auto-hide', Header.setBarVisibility); $.sync('Centered links', Header.setLinkJustify); - this.addShortcut(Header.menuButton); + this.addShortcut(menuButton); $.event('AddMenuEntry', { type: 'header', el: $.el('span', { @@ -2043,6 +2043,9 @@ threadFromRoot: function(root) { return g.threads["" + g.BOARD + "." + root.id.slice(1)]; }, + threadFromNode: function(node) { + return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node)); + }, postFromRoot: function(root) { var boardID, index, link, post, postID; @@ -2060,8 +2063,8 @@ postFromNode: function(root) { return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root)); }, - contextFromNode: function(quotelink) { - return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', quotelink)); + contextFromNode: function(node) { + return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', node)); }, postDataFromLink: function(link) { var boardID, path, postID, threadID, _ref; @@ -2509,7 +2512,7 @@ }; Menu.prototype.focus = function(entry) { - var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; + var cHeight, cWidth, eRect, focused, sRect, submenu, _i, _len, _ref; while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { $.rmClass(focused, 'focused'); @@ -2527,13 +2530,20 @@ eRect = entry.getBoundingClientRect(); cHeight = doc.clientHeight; cWidth = doc.clientWidth; - _ref1 = eRect.top + sRect.height < cHeight ? ['0px', 'auto'] : ['auto', '0px'], top = _ref1[0], bottom = _ref1[1]; - _ref2 = eRect.right + sRect.width < cWidth ? ['100%', 'auto'] : ['auto', '100%'], left = _ref2[0], right = _ref2[1]; - style = submenu.style; - style.top = top; - style.bottom = bottom; - style.left = left; - return style.right = right; + if (eRect.top + sRect.height < cHeight) { + $.addClass(submenu, 'top'); + $.rmClass(submenu, 'bottom'); + } else { + $.addClass(submenu, 'bottom'); + $.rmClass(submenu, 'top'); + } + if (eRect.right + sRect.width < cWidth) { + $.addClass(submenu, 'left'); + return $.rmClass(submenu, 'right'); + } else { + $.addClass(submenu, 'right'); + return $.rmClass(submenu, 'left'); + } }; Menu.prototype.addEntry = function(e) { @@ -3321,7 +3331,7 @@ post.nodes.stub = $.el('div', { className: 'stub' }); - $.add(post.nodes.stub, !Conf['Menu'] ? a : [a, $.tn(' '), button = Menu.makeButton(post)]); + $.add(post.nodes.stub, Conf['Menu'] ? [a, $.tn(' '), button = Menu.makeButton(post)] : a); return $.prepend(post.nodes.root, post.nodes.stub); }, show: function(post, showRecursively) { @@ -3652,7 +3662,7 @@ return ThreadHiding.saveHiddenState(thread); }, hide: function(thread, makeStub) { - var OP, a, button, numReplies, opInfo, span, threadRoot; + var OP, a, numReplies, opInfo, span, threadRoot; if (makeStub == null) { makeStub = Conf['Stubs']; @@ -3672,7 +3682,7 @@ thread.stub = $.el('div', { className: 'stub' }); - $.add(thread.stub, !Conf['Menu'] ? a : [a, $.tn(' '), button = Menu.makeButton(OP)]); + $.add(thread.stub, Conf['Menu'] ? [a, $.tn(' '), Menu.makeButton()] : a); return $.prepend(threadRoot, thread.stub); }, show: function(thread) { @@ -3782,26 +3792,23 @@ }); }, node: function() { - var board, boardID, quotelink, quotelinks, quotes, thread, threadID, _i, _len, _ref, _ref1; + var board, boardID, quotelink, thread, threadID, _i, _len, _ref, _ref1, _ref2; if (this.isClone && this.thread === this.context.thread) { return; } - if (!(quotes = this.quotes).length) { - return; - } - quotelinks = this.nodes.quotelinks; _ref = this.isClone ? this.context : this, board = _ref.board, thread = _ref.thread; - for (_i = 0, _len = quotelinks.length; _i < _len; _i++) { - quotelink = quotelinks[_i]; - _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, threadID = _ref1.threadID; + _ref1 = this.nodes.quotelinks; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + quotelink = _ref1[_i]; + _ref2 = Get.postDataFromLink(quotelink), boardID = _ref2.boardID, threadID = _ref2.threadID; if (!threadID) { continue; } if (this.isClone) { quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, ''); } - if (boardID === this.board.ID && threadID !== thread.ID) { + if (boardID === board.ID && threadID !== thread.ID) { $.add(quotelink, $.tn(QuoteCT.text)); } } @@ -3948,7 +3955,7 @@ }); }, node: function() { - var boardID, op, postID, quotelink, quotelinks, quotes, _i, _j, _len, _len1, _ref; + var boardID, fullID, i, postID, quotelink, quotelinks, quotes, _ref; if (this.isClone && this.thread === this.context.thread) { return; @@ -3958,19 +3965,19 @@ } quotelinks = this.nodes.quotelinks; if (this.isClone && quotes.contains(this.thread.fullID)) { - for (_i = 0, _len = quotelinks.length; _i < _len; _i++) { - quotelink = quotelinks[_i]; + i = 0; + while (quotelink = quotelinks[i++]) { quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, ''); } } - op = (this.isClone ? this.context : this).thread.fullID; - if (!quotes.contains(op)) { + fullID = (this.isClone ? this.context : this).thread.fullID; + if (!quotes.contains(fullID)) { return; } - for (_j = 0, _len1 = quotelinks.length; _j < _len1; _j++) { - quotelink = quotelinks[_j]; + i = 0; + while (quotelink = quotelinks[i++]) { _ref = Get.postDataFromLink(quotelink), boardID = _ref.boardID, postID = _ref.postID; - if (("" + boardID + "." + postID) === op) { + if (("" + boardID + "." + postID) === fullID) { $.add(quotelink, $.tn(QuoteOP.text)); } } @@ -5370,7 +5377,7 @@ } _ref = QR.nodes, com = _ref.com, thread = _ref.thread; if (!com.value) { - thread.value = Get.contextFromNode(this).thread; + thread.value = Get.threadFromNode(this); } caretPos = com.selectionStart; com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd); @@ -6323,7 +6330,7 @@ href: 'javascript:;' }); $.on(this.EAI, 'click', ImageExpand.cb.toggleAll); - Header.addShortcut(this.EAI); + Header.addShortcut(this.EAI, 2); return Post.prototype.callbacks.push({ name: 'Image Expansion', cb: this.node @@ -7071,48 +7078,47 @@ } }; - Menu = (function() { - var a; + Menu = { + init: function() { + if (g.VIEW === 'catalog' || !Conf['Menu']) { + return; + } + this.menu = new UI.Menu('post'); + return Post.prototype.callbacks.push({ + name: 'Menu', + cb: this.node + }); + }, + node: function() { + if (this.isClone) { + return $.on($('.menu-button', this.nodes.info), 'click', Menu.toggle); + } else { + return $.add(this.nodes.info, [$.tn('\u00A0'), Menu.makeButton()]); + } + }, + makeButton: (function() { + var a; - a = $.el('a', { - className: 'menu-button brackets-wrap', - innerHTML: '', - href: 'javascript:;' - }); - return { - init: function() { - if (g.VIEW === 'catalog' || !Conf['Menu']) { - return; - } - this.menu = new UI.Menu('post'); - return Post.prototype.callbacks.push({ - name: 'Menu', - cb: this.node - }); - }, - node: function() { + a = $.el('a', { + className: 'menu-button brackets-wrap', + innerHTML: '', + href: 'javascript:;' + }); + return function() { var button; - if (this.isClone) { - button = $('.menu-button', this.nodes.info); - } else { - button = a.cloneNode(true); - $.add(this.nodes.info, [$.tn('\u00A0'), button]); - } - return $.on(button, 'click', Menu.toggle); - }, - makeButton: function() { - var el; + button = a.cloneNode(true); + $.on(button, 'click', Menu.toggle); + return button; + }; + })(), + toggle: function(e) { + var post; - el = a.cloneNode(true); - $.on(el, 'click', Menu.toggle); - return el; - }, - toggle: function(e) { - return Menu.menu.toggle(e, this, Get.postFromNode(this)); - } - }; - })(); + post = Get.postFromNode(this); + return Menu.menu.toggle(e, this, post); + } + }; ReportLink = { init: function() { @@ -7897,12 +7903,14 @@ fetching: 0 }, fetchAllStatus: function() { - var thread, _i, _len, _ref; + var thread, threads, _i, _len; + if (!(threads = ThreadWatcher.getAll()).length) { + return; + } ThreadWatcher.status.textContent = '...'; - _ref = ThreadWatcher.getAll(); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - thread = _ref[_i]; + for (_i = 0, _len = threads.length; _i < _len; _i++) { + thread = threads[_i]; ThreadWatcher.fetchStatus(thread); } }, @@ -9100,7 +9108,7 @@ return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); }, cbToggle: function() { - return ExpandThread.toggle(Get.threadFromRoot(this.parentNode)); + return ExpandThread.toggle(Get.threadFromNode(this)); }, toggle: function(thread) { var a, files, filesCount, inlined, num, post, posts, postsCount, reply, threadRoot, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3, _ref4; @@ -10733,9 +10741,27 @@ }; Main = { - init: function(items) { - var db, flatten, _i, _len; + init: function() { + var db, flatten, pathname, _i, _len, _ref; + pathname = location.pathname.split('/'); + g.BOARD = new Board(pathname[1]); + if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { + return; + } + g.VIEW = (function() { + switch (pathname[2]) { + case 'res': + return 'thread'; + case 'catalog': + return 'catalog'; + default: + return 'index'; + } + })(); + if (g.VIEW === 'thread') { + g.THREADID = +pathname[3]; + } flatten = function(parent, obj) { var key, val; @@ -10759,16 +10785,28 @@ } Conf['selectedArchives'] = {}; Conf['CachedTitles'] = []; - $.get(Conf, Main.initFeatures); + $.get(Conf, function(items) { + $.extend(Conf, items); + if (!items) { + new Notification('error', $.el('span', { + innerHTML: "It seems like your 4chan X settings became corrupted due to a Chrome bug.
\nUnfortunately, you'll have to fix it yourself." + })); + Main.logError({ + message: 'Chrome Storage API bug', + error: new Error(chrome.runtime.lastError.message || 'no lastError.message') + }); + } + return Main.initFeatures(); + }); $.on(d, '4chanMainInit', Main.initStyle); return $.asap((function() { return d.head && $('link[rel="shortcut icon"]', d.head) || d.readyState !== 'loading'; }), Main.initStyle); }, - initFeatures: function(items) { + initFeatures: function() { + Conf; var init, pathname, _ref; - Conf = items; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { @@ -10935,7 +10973,7 @@ initReady: function() { var board, err, errors, href, passLink, postRoot, posts, styleSelector, thread, threadRoot, threads, _i, _j, _len, _len1, _ref, _ref1; - if (d.title === '4chan - 404 Not Found') { + if (['4chan - Temporarily Offline', '4chan - 404 Not Found'].contains(d.title)) { if (Conf['404 Redirect'] && g.VIEW === 'thread') { href = Redirect.to('thread', { boardID: g.BOARD.ID, @@ -10946,9 +10984,7 @@ } return; } - if (!$.hasClass(doc, 'fourchan-x')) { - Main.initStyle(); - } + Main.initStyle(); if (board = $('.board')) { threads = []; posts = []; diff --git a/css/style.css b/css/style.css index 32819ef69..e9777b123 100644 --- a/css/style.css +++ b/css/style.css @@ -189,9 +189,6 @@ a[href="javascript:;"] { text-decoration: none; padding: 1px; } -#shortcuts:empty { - display: none; -} .shortcut:not(:last-child)::after { content: " / "; } @@ -888,6 +885,18 @@ a.hide-announcement { position: absolute; outline: none; } +#menu.top { + top: 100%; +} +#menu.bottom { + bottom: 100%; +} +#menu.left { + left: 0; +} +#menu.right { + right: 0; +} .entry { cursor: pointer; outline: none; @@ -924,6 +933,18 @@ a.hide-announcement { position: absolute; margin: -1px 0; } +.submenu.top { + top: 0; +} +.submenu.bottom { + bottom: 0; +} +.submenu.left { + left: 100%; +} +.submenu.right { + right: 100%; +} .entry input { margin: 0; } diff --git a/html/General/Header.html b/html/General/Header.html new file mode 100644 index 000000000..2e0088f42 --- /dev/null +++ b/html/General/Header.html @@ -0,0 +1,9 @@ +
+ + + + + +
+
+
diff --git a/json/archives.json b/json/archives.json index 647889b55..2f1ac503c 100644 --- a/json/archives.json +++ b/json/archives.json @@ -105,6 +105,6 @@ "https": true, "withCredentials": true, "software": "foolfuuka", - "boards": ["a", "co", "gd", "h", "jp", "m", "mlp", "q", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], - "files": ["a", "gd", "h", "jp", "m", "q", "tg", "u", "vg", "vp", "vr", "wsg"] + "boards": ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "q", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], + "files": ["a", "d", "gd", "h", "jp", "m", "q", "tg", "u", "vg", "vp", "vr", "wsg"] }] diff --git a/package.json b/package.json index 49e729ff9..96bad6cdc 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "4chan-X", "version": "1.2.25", "description": "Cross-browser userscript for maximum lurking on 4chan.", + "meta": { "name": "4chan X", "repo": "https://github.com/seaweedchan/4chan-x/", diff --git a/src/Filtering/PostHiding.coffee b/src/Filtering/PostHiding.coffee index 8c7be2ae3..2ee2c6716 100644 --- a/src/Filtering/PostHiding.coffee +++ b/src/Filtering/PostHiding.coffee @@ -186,10 +186,10 @@ PostHiding = $.add a, $.tn " #{postInfo}" post.nodes.stub = $.el 'div', className: 'stub' - $.add post.nodes.stub, unless Conf['Menu'] - a + $.add post.nodes.stub, if Conf['Menu'] + [a, $.tn(' '), button = Menu.makeButton post] else - [a, $.tn(' '), button = Menu.makeButton post] + a $.prepend post.nodes.root, post.nodes.stub show: (post, showRecursively=Conf['Recursive Hiding']) -> @@ -204,4 +204,4 @@ PostHiding = Recursive.rm PostHiding.hide, post for quotelink in Get.allQuotelinksLinkingTo post $.rmClass quotelink, 'filtered' - return \ No newline at end of file + return diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index 724210f6d..3774bff4b 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -191,10 +191,10 @@ ThreadHiding = $.add a, $.tn " #{opInfo} (#{numReplies})" thread.stub = $.el 'div', className: 'stub' - $.add thread.stub, unless Conf['Menu'] - a + $.add thread.stub, if Conf['Menu'] + [a, $.tn(' '), Menu.makeButton()] else - [a, $.tn(' '), button = Menu.makeButton OP] + a $.prepend threadRoot, thread.stub show: (thread) -> diff --git a/src/General/Get.coffee b/src/General/Get.coffee index fbc5cb37a..4c5643d59 100644 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -10,6 +10,8 @@ Get = "/#{thread.board}/ - #{excerpt}" threadFromRoot: (root) -> g.threads["#{g.BOARD}.#{root.id[1..]}"] + threadFromNode: (node) -> + Get.threadFromRoot $.x 'ancestor::div[@class="thread"]', node postFromRoot: (root) -> link = $ 'a[title="Highlight this post"]', root boardID = link.pathname.split('/')[1] @@ -19,8 +21,8 @@ Get = if index then post.clones[index] else post postFromNode: (root) -> Get.postFromRoot $.x '(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root - contextFromNode: (quotelink) -> - Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', quotelink + contextFromNode: (node) -> + Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', node postDataFromLink: (link) -> if link.hostname is 'boards.4chan.org' path = link.pathname.split '/' diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 947f084e4..0af1aa9e2 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -1,7 +1,8 @@ Header = init: -> @menu = new UI.Menu 'header' - @menuButton = $.el 'span', + + menuButton = $.el 'span', className: 'menu-button' innerHTML: '' @@ -28,7 +29,7 @@ Header = @footerToggler = footerToggler.firstElementChild @customNavToggler = customNavToggler.firstElementChild - $.on @menuButton, 'click', @menuToggle + $.on menuButton, 'click', @menuToggle $.on @barFixedToggler, 'change', @toggleBarFixed $.on @barPositionToggler, 'change', @toggleBarPosition $.on @linkJustifyToggler, 'change', @toggleLinkJustify @@ -46,7 +47,7 @@ Header = $.sync 'Header auto-hide', Header.setBarVisibility $.sync 'Centered links', Header.setLinkJustify - @addShortcut Header.menuButton + @addShortcut menuButton $.event 'AddMenuEntry', type: 'header' diff --git a/src/General/Main.coffee b/src/General/Main.coffee index f79be109b..4b93e1f34 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -1,5 +1,19 @@ Main = - init: (items) -> + init: -> + pathname = location.pathname.split '/' + g.BOARD = new Board pathname[1] + return if g.BOARD.ID in ['z', 'fk'] + g.VIEW = + switch pathname[2] + when 'res' + 'thread' + when 'catalog' + 'catalog' + else + 'index' + if g.VIEW is 'thread' + g.THREADID = +pathname[3] + # flatten Config into Conf # and get saved or default values flatten = (parent, obj) -> @@ -16,15 +30,29 @@ Main = Conf[db] = boards: {} Conf['selectedArchives'] = {} Conf['CachedTitles'] = [] - - $.get Conf, Main.initFeatures + + $.get Conf, (items) -> + $.extend Conf, items + <% if (type === 'crx') { %> + unless items + new Notification 'error', $.el 'span', + innerHTML: """ + It seems like your <%= meta.name %> settings became corrupted due to a Chrome bug.
+ Unfortunately, you'll have to fix it yourself. + """ + # Track resolution of this bug. + Main.logError + message: 'Chrome Storage API bug' + error: new Error chrome.runtime.lastError.message or 'no lastError.message' + <% } %> + Main.initFeatures() $.on d, '4chanMainInit', Main.initStyle $.asap (-> d.head and $('link[rel="shortcut icon"]', d.head) or d.readyState isnt 'loading'), Main.initStyle - initFeatures: (items) -> - Conf = items + initFeatures: -> + Conf pathname = location.pathname.split '/' g.BOARD = new Board pathname[1] @@ -171,7 +199,7 @@ Main = attributeFilter: ['href'] initReady: -> - if d.title is '4chan - 404 Not Found' + if ['4chan - Temporarily Offline', '4chan - 404 Not Found'].contains d.title if Conf['404 Redirect'] and g.VIEW is 'thread' href = Redirect.to 'thread', boardID: g.BOARD.ID @@ -180,9 +208,8 @@ Main = location.replace href or "/#{g.BOARD}/" return - unless $.hasClass doc, 'fourchan-x' - # Something might have gone wrong! - Main.initStyle() + # Something might have gone wrong! + Main.initStyle() if board = $ '.board' threads = [] diff --git a/src/General/UI.coffee b/src/General/UI.coffee index b016b7d5e..1f38b62d4 100644 --- a/src/General/UI.coffee +++ b/src/General/UI.coffee @@ -91,7 +91,7 @@ UI = do -> $.addClass menu, 'left' entry = $ '.entry', menu - # We've removed flexbox, so we don't user order anymore. + # We've removed flexbox, so we don't use order anymore. # while prevEntry = @findNextEntry entry, -1 # entry = prevEntry @focus entry @@ -171,19 +171,18 @@ UI = do -> eRect = entry.getBoundingClientRect() cHeight = doc.clientHeight cWidth = doc.clientWidth - [top, bottom] = if eRect.top + sRect.height < cHeight - ['0px', 'auto'] + if eRect.top + sRect.height < cHeight + $.addClass submenu, 'top' + $.rmClass submenu, 'bottom' else - ['auto', '0px'] - [left, right] = if eRect.right + sRect.width < cWidth - ['100%', 'auto'] + $.addClass submenu, 'bottom' + $.rmClass submenu, 'top' + if eRect.right + sRect.width < cWidth + $.addClass submenu, 'left' + $.rmClass submenu, 'right' else - ['auto', '100%'] - {style} = submenu - style.top = top - style.bottom = bottom - style.left = left - style.right = right + $.addClass submenu, 'right' + $.rmClass submenu, 'left' addEntry: (e) -> entry = e.detail diff --git a/src/General/lib/polyfill.coffee b/src/General/lib/polyfill.coffee index 3cec1905a..6bab699cb 100644 --- a/src/General/lib/polyfill.coffee +++ b/src/General/lib/polyfill.coffee @@ -1,7 +1,9 @@ Polyfill = init: -> + <% if (type === 'crx') { %> Polyfill.toBlob() Polyfill.visibility() + <% } %> toBlob: -> HTMLCanvasElement::toBlob or= (cb) -> data = atob @toDataURL()[22..] diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index 3d7751853..c5a2ba94a 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -8,7 +8,7 @@ ImageExpand = title: 'Expand All Images' href: 'javascript:;' $.on @EAI, 'click', ImageExpand.cb.toggleAll - Header.addShortcut @EAI + Header.addShortcut @EAI, 2 Post::callbacks.push name: 'Image Expansion' diff --git a/src/Menu/Menu.coffee b/src/Menu/Menu.coffee index 2d7076f1b..a02e56ee4 100644 --- a/src/Menu/Menu.coffee +++ b/src/Menu/Menu.coffee @@ -1,9 +1,4 @@ -Menu = do -> - a = $.el 'a', - className: 'menu-button brackets-wrap' - innerHTML: '' - href: 'javascript:;' - +Menu = init: -> return if g.VIEW is 'catalog' or !Conf['Menu'] @@ -14,16 +9,20 @@ Menu = do -> node: -> if @isClone - button = $ '.menu-button', @nodes.info + $.on $('.menu-button', @nodes.info), 'click', Menu.toggle else - button = a.cloneNode true - $.add @nodes.info, [$.tn('\u00A0'), button] - $.on button, 'click', Menu.toggle + $.add @nodes.info, [$.tn('\u00A0'), Menu.makeButton()] - makeButton: -> - el = a.cloneNode true - $.on el, 'click', Menu.toggle - el + makeButton: do -> + a = $.el 'a', + className: 'menu-button brackets-wrap' + innerHTML: '' + href: 'javascript:;' + -> + button = a.cloneNode true + $.on button, 'click', Menu.toggle + button toggle: (e) -> - Menu.menu.toggle e, @, Get.postFromNode @ + post = Get.postFromNode @ + Menu.menu.toggle e, @, post diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index 3f3a7c19a..504fb1125 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -22,7 +22,7 @@ ExpandThread = " #{if status is '-' then 'shown' else 'omitted'}." cbToggle: -> - ExpandThread.toggle Get.threadFromRoot @parentNode + ExpandThread.toggle Get.threadFromNode @ toggle: (thread) -> threadRoot = thread.OP.nodes.root.parentNode diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 9a48fa021..4f45ce930 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -107,8 +107,9 @@ ThreadWatcher = fetched: 0 fetching: 0 fetchAllStatus: -> + return unless (threads = ThreadWatcher.getAll()).length ThreadWatcher.status.textContent = '...' - for thread in ThreadWatcher.getAll() + for thread in threads ThreadWatcher.fetchStatus thread return fetchStatus: ({boardID, threadID, data}) -> diff --git a/src/Posting/QuickReply.coffee b/src/Posting/QuickReply.coffee index 78d4b12be..85039176b 100644 --- a/src/Posting/QuickReply.coffee +++ b/src/Posting/QuickReply.coffee @@ -385,7 +385,7 @@ QR = $.addClass QR.nodes.el, 'dump' QR.cooldown.auto = true {com, thread} = QR.nodes - thread.value = Get.contextFromNode(@).thread unless com.value + thread.value = Get.threadFromNode @ unless com.value caretPos = com.selectionStart # Replace selection for text. diff --git a/src/Quotelinks/QuoteCT.coffee b/src/Quotelinks/QuoteCT.coffee index 97dbc0190..b0618d487 100644 --- a/src/Quotelinks/QuoteCT.coffee +++ b/src/Quotelinks/QuoteCT.coffee @@ -13,16 +13,13 @@ QuoteCT = node: -> # Stop there if it's a clone of a post in the same thread. return if @isClone and @thread is @context.thread - # Stop there if there's no quotes in that post. - return unless (quotes = @quotes).length - {quotelinks} = @nodes {board, thread} = if @isClone then @context else @ - for quotelink in quotelinks + for quotelink in @nodes.quotelinks {boardID, threadID} = Get.postDataFromLink quotelink continue unless threadID # deadlink if @isClone quotelink.textContent = quotelink.textContent.replace QuoteCT.text, '' - if boardID is @board.ID and threadID isnt thread.ID + if boardID is board.ID and threadID isnt thread.ID $.add quotelink, $.tn QuoteCT.text - return \ No newline at end of file + return diff --git a/src/Quotelinks/QuoteOP.coffee b/src/Quotelinks/QuoteOP.coffee index 5aaa854a0..ef045aecc 100644 --- a/src/Quotelinks/QuoteOP.coffee +++ b/src/Quotelinks/QuoteOP.coffee @@ -10,6 +10,7 @@ QuoteOP = Post::callbacks.push name: 'Mark OP Quotes' cb: @node + node: -> # Stop there if it's a clone of a post in the same thread. return if @isClone and @thread is @context.thread @@ -19,14 +20,16 @@ QuoteOP = # rm (OP) from cross-thread quotes. if @isClone and quotes.contains @thread.fullID - for quotelink in quotelinks + i = 0 + while quotelink = quotelinks[i++] quotelink.textContent = quotelink.textContent.replace QuoteOP.text, '' - op = (if @isClone then @context else @).thread.fullID + {fullID} = (if @isClone then @context else @).thread # add (OP) to quotes quoting this context's OP. - return unless quotes.contains op - for quotelink in quotelinks + return unless quotes.contains fullID + i = 0 + while quotelink = quotelinks[i++] {boardID, postID} = Get.postDataFromLink quotelink - if "#{boardID}.#{postID}" is op + if "#{boardID}.#{postID}" is fullID $.add quotelink, $.tn QuoteOP.text - return \ No newline at end of file + return