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.
+
+ - 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