Finish Miscellaneous features (for now), almost finish Monitoring
This commit is contained in:
parent
108129ca78
commit
3d3fc0a868
@ -115,7 +115,7 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
|
||||
var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
|
||||
__slice = [].slice,
|
||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
@ -3207,6 +3207,7 @@
|
||||
this.isPinned = false;
|
||||
this.isSticky = false;
|
||||
this.isClosed = false;
|
||||
this.isArchived = false;
|
||||
this.postLimit = false;
|
||||
this.fileLimit = false;
|
||||
this.ipCount = void 0;
|
||||
@ -12747,6 +12748,19 @@
|
||||
|
||||
Favicon = {
|
||||
init: function() {
|
||||
return $.asap((function() {
|
||||
return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head));
|
||||
}), Favicon.initAsap);
|
||||
},
|
||||
initAsap: function() {
|
||||
var href;
|
||||
Favicon.el.type = 'image/x-icon';
|
||||
href = Favicon.el.href;
|
||||
Favicon.SFW = /ws\.ico$/.test(href);
|
||||
Favicon["default"] = href;
|
||||
return Favicon["switch"]();
|
||||
},
|
||||
"switch": function() {
|
||||
var f, funreadDeadY, i, items, t;
|
||||
items = {
|
||||
ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='],
|
||||
@ -12862,7 +12876,7 @@
|
||||
|
||||
ThreadExcerpt = {
|
||||
init: function() {
|
||||
if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
|
||||
if ((g.BOARD.ID !== 'f' && g.BOARD.ID !== 'pol') || g.VIEW !== 'thread' || !Conf['Thread Excerpt'] || Conf['Remove Thread Excerpt']) {
|
||||
return;
|
||||
}
|
||||
return Thread.callbacks.push({
|
||||
@ -12897,7 +12911,10 @@
|
||||
});
|
||||
Header.addShortcut(sc);
|
||||
} else {
|
||||
this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', "<div class=move title='" + title + "'>" + html + "</div>");
|
||||
this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', {
|
||||
innerHTML: "<div class=\"move\" title=\"" + E(statsTitle) + "\">" + statsHTML.innerHTML + "</div>"
|
||||
});
|
||||
$.addClass(doc, 'float');
|
||||
$.ready(function() {
|
||||
return $.add(d.body, sc);
|
||||
});
|
||||
@ -12918,7 +12935,10 @@
|
||||
this.posts.forEach(function(post) {
|
||||
postCount++;
|
||||
if (post.file) {
|
||||
return fileCount++;
|
||||
fileCount++;
|
||||
}
|
||||
if (Conf["Page Count in Stats"]) {
|
||||
return ThreadStats.lastPost = post.info.date;
|
||||
}
|
||||
});
|
||||
ThreadStats.thread = this;
|
||||
@ -12977,6 +12997,7 @@
|
||||
if (!Conf["Page Count in Stats"]) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(ThreadStats.timeout);
|
||||
if (ThreadStats.thread.isDead) {
|
||||
ThreadStats.pageCountEl.textContent = 'Dead';
|
||||
$.addClass(ThreadStats.pageCountEl, 'warning');
|
||||
@ -13005,6 +13026,7 @@
|
||||
}
|
||||
ThreadStats.pageCountEl.textContent = page.page;
|
||||
(page.page === this.response.length ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning');
|
||||
ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -13019,15 +13041,18 @@
|
||||
}
|
||||
if (Conf['Updater and Stats in Header']) {
|
||||
this.dialog = sc = $.el('span', {
|
||||
innerHTML: "[<span id=update-status></span><span id=update-timer title='Update now'></span>]\u00A0",
|
||||
id: 'updater'
|
||||
});
|
||||
$.extend(sc, {
|
||||
innerHTML: "[<span id=\"update-status\"></span><span id=\"update-timer\" title=\"Update now\"></span>]"
|
||||
});
|
||||
$.ready(function() {
|
||||
return Header.addShortcut(sc);
|
||||
});
|
||||
} else {
|
||||
this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', "<div class=move><span id=update-status></span><span id=update-timer title='Update now'></span></div>");
|
||||
$.addClass(doc, 'float');
|
||||
this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', {
|
||||
innerHTML: "<div class=\"move\"></div><span id=\"update-status\"></span><span id=\"update-timer\" title=\"Update now\"></span>"
|
||||
});
|
||||
$.ready(function() {
|
||||
$.addClass(doc, 'float');
|
||||
return $.add(d.body, sc);
|
||||
@ -13044,24 +13069,21 @@
|
||||
for (name in _ref) {
|
||||
conf = _ref[name];
|
||||
checked = Conf[name] ? 'checked' : '';
|
||||
el = $.el('label', {
|
||||
title: "" + conf[1],
|
||||
innerHTML: "<input name='" + name + "' type=checkbox " + checked + "> " + name
|
||||
});
|
||||
el = UI.checkbox(name, " " + name);
|
||||
input = el.firstElementChild;
|
||||
$.on(input, 'change', $.cb.checked);
|
||||
if (input.name === 'Scroll BG') {
|
||||
$.on(input, 'change', this.cb.scrollBG);
|
||||
this.cb.scrollBG();
|
||||
} else if (input.name === 'Auto Update') {
|
||||
$.on(input, 'change', this.cb.update);
|
||||
$.on(input, 'change', this.cb.autoUpdate);
|
||||
}
|
||||
subEntries.push({
|
||||
el: el
|
||||
});
|
||||
}
|
||||
this.settings = $.el('span', {
|
||||
innerHTML: '<a href=javascript:;>Interval</a>'
|
||||
innerHTML: "<a href=\"javascript:;\">Interval</a>"
|
||||
});
|
||||
$.on(this.settings, 'click', this.intervalShortcut);
|
||||
subEntries.push({
|
||||
@ -13122,6 +13144,7 @@
|
||||
ThreadUpdater.thread = this;
|
||||
ThreadUpdater.root = this.OP.nodes.root.parentNode;
|
||||
ThreadUpdater.lastPost = +this.posts.keys[this.posts.keys.length - 1];
|
||||
ThreadUpdater.outdateCount = 0;
|
||||
ThreadUpdater.cb.interval.call($.el('input', {
|
||||
value: Conf['Interval'],
|
||||
name: 'Interval'
|
||||
@ -13134,7 +13157,7 @@
|
||||
} else {
|
||||
ThreadUpdater.cb.online();
|
||||
}
|
||||
Rice.nodes(ThreadUpdater.dialog);
|
||||
return Rice.nodes(ThreadUpdater.dialog);
|
||||
},
|
||||
|
||||
/*
|
||||
@ -13144,14 +13167,18 @@
|
||||
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA',
|
||||
cb: {
|
||||
online: function() {
|
||||
if (ThreadUpdater.thread.isDead) {
|
||||
return;
|
||||
}
|
||||
if (ThreadUpdater.online = navigator.onLine) {
|
||||
ThreadUpdater.outdateCount = 0;
|
||||
ThreadUpdater.setInterval();
|
||||
ThreadUpdater.set('status', null, null);
|
||||
ThreadUpdater.set('status', '', '');
|
||||
return;
|
||||
}
|
||||
ThreadUpdater.set('timer', null);
|
||||
return ThreadUpdater.set('status', 'Offline', 'warning');
|
||||
ThreadUpdater.set('timer', '');
|
||||
ThreadUpdater.set('status', 'Offline', 'warning');
|
||||
return clearTimeout(ThreadUpdater.timeoutID);
|
||||
},
|
||||
post: function(e) {
|
||||
if (!(ThreadUpdater.isUpdating && e.detail.threadID === ThreadUpdater.thread.ID)) {
|
||||
@ -13164,14 +13191,14 @@
|
||||
},
|
||||
checkpost: function(e) {
|
||||
if (!ThreadUpdater.checkPostCount) {
|
||||
if (e.detail.threadID !== ThreadUpdater.thread.ID) {
|
||||
if (e && e.detail.threadID !== ThreadUpdater.thread.ID) {
|
||||
return;
|
||||
}
|
||||
ThreadUpdater.seconds = 0;
|
||||
ThreadUpdater.outdateCount = 0;
|
||||
ThreadUpdater.set('timer', '...');
|
||||
}
|
||||
if (!(g.DEAD || ThreadUpdater.foundPost || ThreadUpdater.checkPostCount >= 5)) {
|
||||
if (!(ThreadUpdater.thread.isDead || ThreadUpdater.foundPost || ThreadUpdater.checkPostCount >= 5)) {
|
||||
return setTimeout(ThreadUpdater.update, ++ThreadUpdater.checkPostCount * $.SECOND);
|
||||
}
|
||||
ThreadUpdater.setInterval();
|
||||
@ -13195,6 +13222,9 @@
|
||||
return !d.hidden;
|
||||
};
|
||||
},
|
||||
autoUpdate: function(e) {
|
||||
return ThreadUpdater.count(ThreadUpdater.isUpdating = this.checked);
|
||||
},
|
||||
interval: function(e) {
|
||||
var val;
|
||||
val = parseInt(this.value, 10);
|
||||
@ -13211,7 +13241,6 @@
|
||||
req = ThreadUpdater.req;
|
||||
switch (req.status) {
|
||||
case 200:
|
||||
g.DEAD = false;
|
||||
ThreadUpdater.parse(req.response.posts);
|
||||
if (ThreadUpdater.thread.isArchived) {
|
||||
ThreadUpdater.set('status', 'Archived', 'warning');
|
||||
@ -13316,9 +13345,10 @@
|
||||
var n;
|
||||
ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
|
||||
if (!(n = --ThreadUpdater.seconds)) {
|
||||
ThreadUpdater.outdateCount++;
|
||||
return ThreadUpdater.update();
|
||||
} else if (n <= -60) {
|
||||
ThreadUpdater.set('status', 'Retrying', null);
|
||||
ThreadUpdater.set('status', 'Retrying', '');
|
||||
return ThreadUpdater.update();
|
||||
} else if (n > 0) {
|
||||
return ThreadUpdater.set('timer', n);
|
||||
@ -13393,18 +13423,27 @@
|
||||
ThreadUpdater.thread.posts.forEach(function(post) {
|
||||
var ID;
|
||||
ID = +post.ID;
|
||||
if (__indexOf.call(index, ID) < 0) {
|
||||
post.kill();
|
||||
} else if (post.isDead) {
|
||||
post.resurrect();
|
||||
} else if (post.file && !post.file.isDead && __indexOf.call(files, ID) < 0) {
|
||||
post.kill(true);
|
||||
if (!(post.info.date > Date.now() - 60 * $.SECOND)) {
|
||||
if (__indexOf.call(index, ID) < 0) {
|
||||
post.kill();
|
||||
} else if (post.isDead) {
|
||||
post.resurrect();
|
||||
} else if (post.file && !(post.file.isDead || __indexOf.call(files, ID) >= 0)) {
|
||||
post.kill(true);
|
||||
}
|
||||
}
|
||||
if (ThreadUpdater.postID && ThreadUpdater.postID === ID) {
|
||||
return ThreadUpdater.foundPost = true;
|
||||
}
|
||||
});
|
||||
sendEvent = function() {
|
||||
var ipCountEl;
|
||||
if ((OP.unique_ips != null) && (ipCountEl = $.id('unique-ips'))) {
|
||||
ipCountEl.textContent = OP.unique_ips;
|
||||
ipCountEl.previousSibling.textContent = ipCountEl.previousSibling.textContent.replace(/\b(?:is|are)\b/, OP.unique_ips === 1 ? 'is' : 'are');
|
||||
ipCountEl.nextSibling.textContent = ipCountEl.nextSibling.textContent.replace(/\bposters?\b/, OP.unique_ips === 1 ? 'poster' : 'posters');
|
||||
}
|
||||
ThreadUpdater.postIDs = index;
|
||||
return $.event('ThreadUpdate', {
|
||||
404: false,
|
||||
threadID: ThreadUpdater.thread.fullID,
|
||||
@ -13417,7 +13456,7 @@
|
||||
});
|
||||
};
|
||||
if (!count) {
|
||||
ThreadUpdater.set('status', null, null);
|
||||
ThreadUpdater.set('status', '', '');
|
||||
ThreadUpdater.outdateCount++;
|
||||
sendEvent();
|
||||
return;
|
||||
@ -13464,13 +13503,17 @@
|
||||
return;
|
||||
}
|
||||
this.db = new DataBoard('watchedThreads', this.refresh, true);
|
||||
this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', "<div><span class=\"move\">Thread Watcher <span id=\"watcher-status\"></span></span><a class=\"menu-button\" href=\"javascript:;\"><i class=\"fa\">\uf107</i></a></div><div id=\"watched-threads\"></div>");
|
||||
this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', {
|
||||
innerHTML: "<div>\r<span class=\"move\">\rThread Watcher \r<a class=\"refresh fa\" title=\"Check threads\" href=\"javascript:;\">\\uf021</a>\r<span id=\"watcher-status\"></span>\r</span>\r<a class=\"menu-button\" href=\"javascript:;\"><i class=\"fa\">\\uf107</i></a>\r</div>\r<div id=\"watched-threads\"></div>"
|
||||
});
|
||||
this.status = $('#watcher-status', this.dialog);
|
||||
this.list = this.dialog.lastElementChild;
|
||||
this.refreshButton = $('.refresh', this.dialog);
|
||||
$.on(d, 'QRPostSuccessful', this.cb.post);
|
||||
if (g.VIEW === 'thread') {
|
||||
$.on(d, 'ThreadUpdate', this.cb.threadUpdate);
|
||||
}
|
||||
$.on(this.refreshButton, 'click', this.fetchAllStatus);
|
||||
$.on(d, '4chanXInitFinished', this.ready);
|
||||
switch (g.VIEW) {
|
||||
case 'index':
|
||||
@ -13485,18 +13528,75 @@
|
||||
ThreadWatcher.fetchAllStatus();
|
||||
this.db.save();
|
||||
}
|
||||
return Thread.callbacks.push({
|
||||
if (Conf['JSON Navigation'] && Conf['Menu'] && g.BOARD.ID !== 'f') {
|
||||
Menu.menu.addEntry({
|
||||
el: $.el('a', {
|
||||
href: 'javascript:;'
|
||||
}),
|
||||
order: 6,
|
||||
open: function(_arg) {
|
||||
var thread;
|
||||
thread = _arg.thread;
|
||||
if (!(Conf['Index Mode'] === 'catalog' && g.VIEW === 'index')) {
|
||||
return false;
|
||||
}
|
||||
this.el.textContent = ThreadWatcher.isWatched(thread) ? 'Unwatch thread' : 'Watch thread';
|
||||
if (this.cb) {
|
||||
$.off(this.el, 'click', this.cb);
|
||||
}
|
||||
this.cb = function() {
|
||||
$.event('CloseMenu');
|
||||
return ThreadWatcher.toggle(thread);
|
||||
};
|
||||
$.on(this.el, 'click', this.cb);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
Post.callbacks.push({
|
||||
name: 'Thread Watcher',
|
||||
cb: this.node
|
||||
});
|
||||
return CatalogThread.callbacks.push({
|
||||
name: 'Thread Watcher',
|
||||
cb: this.catalogNode
|
||||
});
|
||||
},
|
||||
isWatched: function(thread) {
|
||||
var _ref;
|
||||
return (_ref = ThreadWatcher.db) != null ? _ref.get({
|
||||
boardID: thread.board.ID,
|
||||
threadID: thread.ID
|
||||
}) : void 0;
|
||||
},
|
||||
node: function() {
|
||||
var toggler;
|
||||
toggler = $.el('img', {
|
||||
className: 'watch-thread-link'
|
||||
});
|
||||
$.on(toggler, 'click', ThreadWatcher.cb.toggle);
|
||||
return $.before($('input', this.OP.nodes.post), toggler);
|
||||
if (this.isReply) {
|
||||
return;
|
||||
}
|
||||
if (this.isClone) {
|
||||
toggler = $('.watch-thread-link', this.nodes.post);
|
||||
} else {
|
||||
toggler = $.el('img', {
|
||||
className: 'watch-thread-link'
|
||||
});
|
||||
$.before($('input', this.nodes.post), toggler);
|
||||
}
|
||||
return $.on(toggler, 'click', ThreadWatcher.cb.toggle);
|
||||
},
|
||||
catalogNode: function() {
|
||||
if (ThreadWatcher.isWatched(this.thread)) {
|
||||
$.addClass(this.nodes.root, 'watched');
|
||||
}
|
||||
return $.on(this.nodes.thumb.parentNode, 'click', (function(_this) {
|
||||
return function(e) {
|
||||
if (!(e.button === 0 && e.altKey)) {
|
||||
return;
|
||||
}
|
||||
ThreadWatcher.toggle(_this.thread);
|
||||
return e.preventDefault();
|
||||
};
|
||||
})(this));
|
||||
},
|
||||
ready: function() {
|
||||
var el;
|
||||
@ -13541,12 +13641,6 @@
|
||||
}
|
||||
return $.event('CloseMenu');
|
||||
},
|
||||
checkThreads: function() {
|
||||
if ($.hasClass(this, 'disabled')) {
|
||||
return;
|
||||
}
|
||||
return ThreadWatcher.fetchAllStatus();
|
||||
},
|
||||
pruneDeads: function() {
|
||||
var boardID, data, threadID, _i, _len, _ref, _ref1;
|
||||
if ($.hasClass(this, 'disabled')) {
|
||||
@ -13568,7 +13662,10 @@
|
||||
return $.event('CloseMenu');
|
||||
},
|
||||
toggle: function() {
|
||||
return ThreadWatcher.toggle(Get.threadFromNode(this));
|
||||
ThreadWatcher.toggle(Get.threadFromNode(this));
|
||||
Index.followedThreadID = thread.ID;
|
||||
ThreadWatcher.toggle(thread);
|
||||
return delete Index.followedThreadID;
|
||||
},
|
||||
rm: function() {
|
||||
var boardID, threadID, _ref;
|
||||
@ -13587,9 +13684,11 @@
|
||||
}
|
||||
},
|
||||
onIndexRefresh: function() {
|
||||
var boardID, data, threadID, _ref;
|
||||
var boardID, data, db, threadID, _ref;
|
||||
db = ThreadWatcher.db;
|
||||
boardID = g.BOARD.ID;
|
||||
_ref = ThreadWatcher.db.data.boards[boardID];
|
||||
db.forceSync();
|
||||
_ref = db.data.boards[boardID];
|
||||
for (threadID in _ref) {
|
||||
data = _ref[threadID];
|
||||
if (!data.isDead && !(threadID in g.BOARD.threads)) {
|
||||
@ -13628,10 +13727,12 @@
|
||||
},
|
||||
fetchAllStatus: function() {
|
||||
var thread, threads, _i, _len;
|
||||
ThreadWatcher.db.forceSync();
|
||||
ThreadWatcher.unreaddb.forceSync();
|
||||
QR.db.forceSync();
|
||||
if (!(threads = ThreadWatcher.getAll()).length) {
|
||||
return;
|
||||
}
|
||||
ThreadWatcher.status.textContent = '...';
|
||||
for (_i = 0, _len = threads.length; _i < _len; _i++) {
|
||||
thread = threads[_i];
|
||||
ThreadWatcher.fetchStatus(thread);
|
||||
@ -13640,43 +13741,103 @@
|
||||
fetchStatus: function(_arg) {
|
||||
var boardID, data, fetchCount, threadID;
|
||||
boardID = _arg.boardID, threadID = _arg.threadID, data = _arg.data;
|
||||
if (data.isDead) {
|
||||
if (data.isDead && !Conf['Show Unread Count']) {
|
||||
return;
|
||||
}
|
||||
fetchCount = ThreadWatcher.fetchCount;
|
||||
if (fetchCount.fetching === 0) {
|
||||
ThreadWatcher.status.textContent = '...';
|
||||
$.addClass(ThreadWatcher.refreshButton, 'fa-spin');
|
||||
}
|
||||
fetchCount.fetching++;
|
||||
return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
|
||||
onloadend: function() {
|
||||
var status;
|
||||
var isDead, lastReadPost, match, postObj, quotingYou, regexp, status, unread, _i, _len, _ref, _ref1;
|
||||
fetchCount.fetched++;
|
||||
if (fetchCount.fetched === fetchCount.fetching) {
|
||||
fetchCount.fetched = 0;
|
||||
fetchCount.fetching = 0;
|
||||
status = '';
|
||||
$.rmClass(ThreadWatcher.refreshButton, 'fa-spin');
|
||||
} else {
|
||||
status = "" + (Math.round(fetchCount.fetched / fetchCount.fetching * 100)) + "%";
|
||||
}
|
||||
ThreadWatcher.status.textContent = status;
|
||||
if (this.status !== 404) {
|
||||
return;
|
||||
}
|
||||
if (Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
} else {
|
||||
data.isDead = true;
|
||||
ThreadWatcher.db.set({
|
||||
if (this.status === 200 && this.response) {
|
||||
isDead = !!this.response.posts[0].archived;
|
||||
if (isDead && Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
ThreadWatcher.refresh();
|
||||
return;
|
||||
}
|
||||
lastReadPost = ThreadWatcher.unreaddb.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
defaultValue: 0
|
||||
});
|
||||
unread = quotingYou = 0;
|
||||
_ref = this.response.posts;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
postObj = _ref[_i];
|
||||
if (!(postObj.no > lastReadPost)) {
|
||||
continue;
|
||||
}
|
||||
if ((_ref1 = QR.db) != null ? _ref1.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
postID: postObj.no
|
||||
}) : void 0) {
|
||||
continue;
|
||||
}
|
||||
unread++;
|
||||
if (!(QR.db && postObj.com)) {
|
||||
continue;
|
||||
}
|
||||
regexp = /<a [^>]*\bhref="(?:\/([^\/]+)\/thread\/(\d+))?(?:#p(\d+))?"/g;
|
||||
while (match = regexp.exec(postObj.com)) {
|
||||
if (QR.db.get({
|
||||
boardID: match[1] || boardID,
|
||||
threadID: match[2] || threadID,
|
||||
postID: match[3] || match[2] || threadID
|
||||
})) {
|
||||
quotingYou++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) {
|
||||
data.isDead = isDead;
|
||||
data.unread = unread;
|
||||
data.quotingYou = quotingYou;
|
||||
ThreadWatcher.db.set({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
} else if (this.status === 404) {
|
||||
if (Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
} else {
|
||||
data.isDead = true;
|
||||
delete data.unread;
|
||||
delete data.quotingYou;
|
||||
ThreadWatcher.db.set({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
}
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
}, {
|
||||
type: 'head'
|
||||
});
|
||||
},
|
||||
getAll: function() {
|
||||
@ -13700,24 +13861,31 @@
|
||||
return all;
|
||||
},
|
||||
makeLine: function(boardID, threadID, data) {
|
||||
var div, fullID, href, link, x;
|
||||
var count, div, fullID, link, title, x;
|
||||
x = $.el('a', {
|
||||
className: 'fa',
|
||||
href: 'javascript:;',
|
||||
textContent: '\uf00d'
|
||||
});
|
||||
$.on(x, 'click', ThreadWatcher.cb.rm);
|
||||
if (data.isDead) {
|
||||
href = Redirect.to('thread', {
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
}
|
||||
link = $.el('a', {
|
||||
href: href || ("/" + boardID + "/thread/" + threadID),
|
||||
href: "/" + boardID + "/thread/" + threadID,
|
||||
textContent: data.excerpt,
|
||||
title: data.excerpt
|
||||
title: data.excerpt,
|
||||
className: 'watcher-link'
|
||||
});
|
||||
if (Conf['Show Unread Count'] && (data.unread != null)) {
|
||||
count = $.el('span', {
|
||||
textContent: "(" + data.unread + ")",
|
||||
className: 'watcher-unread'
|
||||
});
|
||||
$.add(link, count);
|
||||
}
|
||||
title = $.el('span', {
|
||||
textContent: data.excerpt,
|
||||
className: 'watcher-title'
|
||||
});
|
||||
$.add(link, title);
|
||||
div = $.el('div');
|
||||
fullID = "" + boardID + "." + threadID;
|
||||
div.dataset.fullID = fullID;
|
||||
@ -13727,11 +13895,19 @@
|
||||
if (data.isDead) {
|
||||
$.addClass(div, 'dead-thread');
|
||||
}
|
||||
if (Conf['Show Unread Count']) {
|
||||
if (data.unread) {
|
||||
$.addClass(div, 'replies-unread');
|
||||
}
|
||||
if (data.quotingYou) {
|
||||
$.addClass(div, 'replies-quoting-you');
|
||||
}
|
||||
}
|
||||
$.add(div, [x, $.tn(' '), link]);
|
||||
return div;
|
||||
},
|
||||
refresh: function() {
|
||||
var boardID, data, helper, list, nodes, refresher, thread, threadID, threads, toggler, watched, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3;
|
||||
var boardID, data, list, nodes, refresher, threadID, _i, _j, _len, _len1, _ref, _ref1, _ref2;
|
||||
nodes = [];
|
||||
_ref = ThreadWatcher.getAll();
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
@ -13741,24 +13917,76 @@
|
||||
list = ThreadWatcher.list;
|
||||
$.rmAll(list);
|
||||
$.add(list, nodes);
|
||||
threads = g.BOARD.threads;
|
||||
_ref2 = threads.keys;
|
||||
g.threads.forEach(function(thread) {
|
||||
var helper, post, toggler, _j, _len1, _ref2;
|
||||
helper = ThreadWatcher.isWatched(thread) ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
|
||||
if (thread.OP) {
|
||||
_ref2 = [thread.OP].concat(__slice.call(thread.OP.clones));
|
||||
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
|
||||
post = _ref2[_j];
|
||||
toggler = $('.watch-thread-link', post.nodes.post);
|
||||
$[helper[0]](toggler, 'watched');
|
||||
toggler.title = "" + helper[1] + " Thread";
|
||||
}
|
||||
}
|
||||
if (thread.catalogView) {
|
||||
return $[helper[0]](thread.catalogView.nodes.root, 'watched');
|
||||
}
|
||||
});
|
||||
_ref2 = ThreadWatcher.menu.refreshers;
|
||||
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
|
||||
threadID = _ref2[_j];
|
||||
thread = threads[threadID];
|
||||
toggler = $('.watch-thread-link', thread.OP.nodes.post);
|
||||
watched = ThreadWatcher.db.get({
|
||||
boardID: thread.board.ID,
|
||||
refresher = _ref2[_j];
|
||||
refresher();
|
||||
}
|
||||
if (Index.nodes && Conf['Pin Watched Threads']) {
|
||||
Index.sort();
|
||||
return Index.buildIndex();
|
||||
}
|
||||
},
|
||||
update: function(boardID, threadID, newData) {
|
||||
var data, key, line, n, newLine, val, _ref;
|
||||
if (!(data = (_ref = ThreadWatcher.db) != null ? _ref.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
}) : void 0)) {
|
||||
return;
|
||||
}
|
||||
if (newData.isDead && Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
helper = watched ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
|
||||
$[helper[0]](toggler, 'watched');
|
||||
toggler.title = "" + helper[1] + " Thread";
|
||||
ThreadWatcher.refresh();
|
||||
return;
|
||||
}
|
||||
_ref3 = ThreadWatcher.menu.refreshers;
|
||||
for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
|
||||
refresher = _ref3[_k];
|
||||
refresher();
|
||||
n = 0;
|
||||
for (key in newData) {
|
||||
val = newData[key];
|
||||
if (data[key] !== val) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
ThreadWatcher.db.forceSync();
|
||||
if (!(data = ThreadWatcher.db.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
}))) {
|
||||
return;
|
||||
}
|
||||
$.extend(data, newData);
|
||||
ThreadWatcher.db.set({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
if (line = $("#watched-threads > [data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog)) {
|
||||
newLine = ThreadWatcher.makeLine(boardID, threadID, data);
|
||||
return $.replace(line, newLine);
|
||||
} else {
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
},
|
||||
toggle: function(thread) {
|
||||
@ -13795,7 +14023,14 @@
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
return ThreadWatcher.refresh();
|
||||
ThreadWatcher.refresh();
|
||||
if (Conf['Show Unread Count']) {
|
||||
return ThreadWatcher.fetchStatus({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
},
|
||||
rm: function(boardID, threadID) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
@ -13825,12 +14060,12 @@
|
||||
if (!Conf['Thread Watcher']) {
|
||||
return;
|
||||
}
|
||||
menu = new UI.Menu();
|
||||
menu = this.menu = new UI.Menu('thread watcher');
|
||||
$.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) {
|
||||
return menu.toggle(e, this, ThreadWatcher);
|
||||
});
|
||||
this.addHeaderMenuEntry();
|
||||
return this.addMenuEntries(menu);
|
||||
return this.addMenuEntries;
|
||||
},
|
||||
addHeaderMenuEntry: function() {
|
||||
var entryEl;
|
||||
@ -13855,7 +14090,7 @@
|
||||
return entryEl.textContent = text;
|
||||
});
|
||||
},
|
||||
addMenuEntries: function(menu) {
|
||||
addMenuEntries: function() {
|
||||
var cb, conf, entries, entry, name, refresh, subEntries, _i, _len, _ref, _ref1;
|
||||
entries = [];
|
||||
entries.push({
|
||||
@ -13869,22 +14104,11 @@
|
||||
return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled');
|
||||
}
|
||||
});
|
||||
entries.push({
|
||||
cb: ThreadWatcher.cb.checkThreads,
|
||||
entry: {
|
||||
el: $.el('a', {
|
||||
textContent: 'Check 404\'d threads'
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
return ($('div:not(.dead-thread)', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled');
|
||||
}
|
||||
});
|
||||
entries.push({
|
||||
cb: ThreadWatcher.cb.pruneDeads,
|
||||
entry: {
|
||||
el: $.el('a', {
|
||||
textContent: 'Prune 404\'d threads'
|
||||
textContent: 'Prune dead threads'
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
@ -13916,22 +14140,18 @@
|
||||
if (refresh) {
|
||||
this.refreshers.push(refresh.bind(entry));
|
||||
}
|
||||
menu.addEntry(entry);
|
||||
this.menu.addEntry(entry);
|
||||
}
|
||||
},
|
||||
createSubEntry: function(name, desc) {
|
||||
var entry, input;
|
||||
entry = {
|
||||
type: 'thread watcher',
|
||||
el: $.el('label', {
|
||||
innerHTML: "<input type=checkbox name='" + name + "'> " + name,
|
||||
title: desc
|
||||
})
|
||||
el: UI.checkbox(name, " " + name)
|
||||
};
|
||||
input = entry.el.firstElementChild;
|
||||
input.checked = Conf[name];
|
||||
$.on(input, 'change', $.cb.checked);
|
||||
if (name === 'Current Board') {
|
||||
if (name === 'Current Board' || name === 'Show Unread Count') {
|
||||
$.on(input, 'change', ThreadWatcher.refresh);
|
||||
}
|
||||
return entry;
|
||||
@ -15757,10 +15977,14 @@
|
||||
}
|
||||
},
|
||||
getThread: function() {
|
||||
var threadRoot, _i, _len, _ref;
|
||||
var thread, threadRoot, _i, _len, _ref;
|
||||
_ref = $$('.thread');
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
threadRoot = _ref[_i];
|
||||
thread = Get.threadFromRoot(threadRoot);
|
||||
if (thread.isHidden && !thread.stub) {
|
||||
continue;
|
||||
}
|
||||
if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) {
|
||||
return threadRoot;
|
||||
}
|
||||
@ -15768,7 +15992,10 @@
|
||||
return $('.board');
|
||||
},
|
||||
scroll: function(delta) {
|
||||
var axis, next, thread, top;
|
||||
var axis, extra, next, thread, top, _ref;
|
||||
if ((_ref = d.activeElement) != null) {
|
||||
_ref.blur();
|
||||
}
|
||||
thread = Nav.getThread();
|
||||
axis = delta === +1 ? 'following' : 'preceding';
|
||||
if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) {
|
||||
@ -15777,48 +16004,64 @@
|
||||
thread = next;
|
||||
}
|
||||
}
|
||||
return Header.scrollTo(thread);
|
||||
extra = Header.getTopOf(thread) + doc.clientHeight - d.body.getBoundingClientRect().bottom;
|
||||
if (extra > 0) {
|
||||
d.body.style.marginBottom = "" + extra + "px";
|
||||
}
|
||||
Header.scrollTo(thread);
|
||||
if (extra > 0 && !Nav.haveExtra) {
|
||||
Nav.haveExtra = true;
|
||||
return $.on(d, 'scroll', Nav.removeExtra);
|
||||
}
|
||||
},
|
||||
removeExtra: function() {
|
||||
var extra;
|
||||
extra = doc.clientHeight - d.body.getBoundingClientRect().bottom;
|
||||
if (extra > 0) {
|
||||
return d.body.style.marginBottom = "" + extra + "px";
|
||||
} else {
|
||||
d.body.style.marginBottom = null;
|
||||
delete Nav.haveExtra;
|
||||
return $.off(d, 'scroll', Nav.removeExtra);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RelativeDates = {
|
||||
INTERVAL: $.MINUTE / 2,
|
||||
init: function() {
|
||||
switch (g.VIEW) {
|
||||
case 'index':
|
||||
this.flush();
|
||||
$.on(d, 'visibilitychange', this.flush);
|
||||
if (!Conf['Relative Post Dates']) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'thread':
|
||||
if (!Conf['Relative Post Dates']) {
|
||||
return;
|
||||
}
|
||||
this.flush();
|
||||
$.on(d, 'visibilitychange ThreadUpdate', this.flush);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
var _ref;
|
||||
if (((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || g.VIEW === 'index' && Conf['JSON Navigation'] && g.BOARD.ID !== 'f') {
|
||||
this.flush();
|
||||
$.on(d, 'visibilitychange ThreadUpdate', this.flush);
|
||||
}
|
||||
if (Conf['Relative Post Dates']) {
|
||||
return Post.callbacks.push({
|
||||
name: 'Relative Post Dates',
|
||||
cb: this.node
|
||||
});
|
||||
}
|
||||
return Post.callbacks.push({
|
||||
name: 'Relative Post Dates',
|
||||
cb: this.node
|
||||
});
|
||||
},
|
||||
node: function() {
|
||||
var dateEl;
|
||||
dateEl = this.nodes.date;
|
||||
if (Conf['Relative Date Title']) {
|
||||
$.on(dateEl, 'mouseover', (function(_this) {
|
||||
return function() {
|
||||
return RelativeDates.hover(_this);
|
||||
};
|
||||
})(this));
|
||||
return;
|
||||
}
|
||||
if (this.isClone) {
|
||||
return;
|
||||
}
|
||||
dateEl = this.nodes.date;
|
||||
dateEl.title = dateEl.textContent;
|
||||
return RelativeDates.update(this);
|
||||
},
|
||||
relative: function(diff, now, date) {
|
||||
var days, months, number, rounded, unit, years;
|
||||
unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second');
|
||||
unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second');
|
||||
rounded = Math.round(number);
|
||||
if (rounded !== 1) {
|
||||
unit += 's';
|
||||
@ -15841,6 +16084,13 @@
|
||||
clearTimeout(RelativeDates.timeout);
|
||||
return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL);
|
||||
},
|
||||
hover: function(post) {
|
||||
var date, diff, now;
|
||||
date = post.info.date;
|
||||
now = new Date();
|
||||
diff = now - date;
|
||||
return post.nodes.date.title = RelativeDates.relative(diff, now, date);
|
||||
},
|
||||
update: function(data, now) {
|
||||
var date, diff, isPost, relative, singlePost, _i, _len, _ref;
|
||||
isPost = data instanceof Post;
|
||||
@ -15880,44 +16130,45 @@
|
||||
if (Conf['Reveal Spoilers']) {
|
||||
$.addClass(doc, 'reveal-spoilers');
|
||||
}
|
||||
if (Conf['Remove Spoilers']) {
|
||||
return $.addClass(doc, 'remove-spoilers');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Report = {
|
||||
init: function() {
|
||||
if (!/report/.test(location.search)) {
|
||||
if (!Conf['Remove Spoilers']) {
|
||||
return;
|
||||
}
|
||||
return $.asap((function() {
|
||||
return $.id('recaptcha_response_field');
|
||||
}), Report.ready);
|
||||
$.addClass(doc, 'remove-spoilers');
|
||||
Post.callbacks.push({
|
||||
name: 'Reveal Spoilers',
|
||||
cb: this.node
|
||||
});
|
||||
CatalogThread.callbacks.push({
|
||||
name: 'Reveal Spoilers',
|
||||
cb: this.node
|
||||
});
|
||||
if (g.VIEW === 'archive') {
|
||||
return $.ready(function() {
|
||||
return RemoveSpoilers.unspoiler($.id('arc-list'));
|
||||
});
|
||||
}
|
||||
},
|
||||
ready: function() {
|
||||
var field;
|
||||
field = $.id('recaptcha_response_field');
|
||||
$.on(field, 'keydown', function(e) {
|
||||
if (e.keyCode === 8 && !field.value) {
|
||||
return $.globalEval('Recaptcha.reload("t")');
|
||||
}
|
||||
});
|
||||
return $.on($('form'), 'submit', function(e) {
|
||||
var response;
|
||||
e.preventDefault();
|
||||
response = field.value.trim();
|
||||
if (!/\s|^\d+$/.test(response)) {
|
||||
field.value = "" + response + " " + response;
|
||||
}
|
||||
return this.submit();
|
||||
});
|
||||
node: function(post) {
|
||||
return RemoveSpoilers.unspoiler(this.nodes.comment);
|
||||
},
|
||||
unspoiler: function(el) {
|
||||
var span, spoiler, spoilers, _i, _len;
|
||||
spoilers = $$('s', el);
|
||||
for (_i = 0, _len = spoilers.length; _i < _len; _i++) {
|
||||
spoiler = spoilers[_i];
|
||||
span = $.el('span', {
|
||||
className: 'removed-spoiler'
|
||||
});
|
||||
$.replace(spoiler, span);
|
||||
$.add(span, __slice.call(spoiler.childNodes));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Time = {
|
||||
init: function() {
|
||||
if (!Conf['Time Formatting']) {
|
||||
var _ref;
|
||||
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Time Formatting'])) {
|
||||
return;
|
||||
}
|
||||
return Post.callbacks.push({
|
||||
@ -15932,7 +16183,7 @@
|
||||
return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date);
|
||||
},
|
||||
format: function(formatString, date) {
|
||||
return formatString.replace(/%([A-Za-z])/g, function(s, c) {
|
||||
return formatString.replace(/%(.)/g, function(s, c) {
|
||||
if (c in Time.formatters) {
|
||||
return Time.formatters[c].call(date);
|
||||
} else {
|
||||
@ -16008,6 +16259,9 @@
|
||||
},
|
||||
Y: function() {
|
||||
return this.getFullYear();
|
||||
},
|
||||
'%': function() {
|
||||
return '%';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -88,7 +88,7 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
|
||||
var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
|
||||
__slice = [].slice,
|
||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
@ -3233,6 +3233,7 @@
|
||||
this.isPinned = false;
|
||||
this.isSticky = false;
|
||||
this.isClosed = false;
|
||||
this.isArchived = false;
|
||||
this.postLimit = false;
|
||||
this.fileLimit = false;
|
||||
this.ipCount = void 0;
|
||||
@ -12769,6 +12770,19 @@
|
||||
|
||||
Favicon = {
|
||||
init: function() {
|
||||
return $.asap((function() {
|
||||
return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head));
|
||||
}), Favicon.initAsap);
|
||||
},
|
||||
initAsap: function() {
|
||||
var href;
|
||||
Favicon.el.type = 'image/x-icon';
|
||||
href = Favicon.el.href;
|
||||
Favicon.SFW = /ws\.ico$/.test(href);
|
||||
Favicon["default"] = href;
|
||||
return Favicon["switch"]();
|
||||
},
|
||||
"switch": function() {
|
||||
var f, funreadDeadY, i, items, t;
|
||||
items = {
|
||||
ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='],
|
||||
@ -12884,7 +12898,7 @@
|
||||
|
||||
ThreadExcerpt = {
|
||||
init: function() {
|
||||
if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
|
||||
if ((g.BOARD.ID !== 'f' && g.BOARD.ID !== 'pol') || g.VIEW !== 'thread' || !Conf['Thread Excerpt'] || Conf['Remove Thread Excerpt']) {
|
||||
return;
|
||||
}
|
||||
return Thread.callbacks.push({
|
||||
@ -12919,7 +12933,10 @@
|
||||
});
|
||||
Header.addShortcut(sc);
|
||||
} else {
|
||||
this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', "<div class=move title='" + title + "'>" + html + "</div>");
|
||||
this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', {
|
||||
innerHTML: "<div class=\"move\" title=\"" + E(statsTitle) + "\">" + statsHTML.innerHTML + "</div>"
|
||||
});
|
||||
$.addClass(doc, 'float');
|
||||
$.ready(function() {
|
||||
return $.add(d.body, sc);
|
||||
});
|
||||
@ -12940,7 +12957,10 @@
|
||||
this.posts.forEach(function(post) {
|
||||
postCount++;
|
||||
if (post.file) {
|
||||
return fileCount++;
|
||||
fileCount++;
|
||||
}
|
||||
if (Conf["Page Count in Stats"]) {
|
||||
return ThreadStats.lastPost = post.info.date;
|
||||
}
|
||||
});
|
||||
ThreadStats.thread = this;
|
||||
@ -12999,6 +13019,7 @@
|
||||
if (!Conf["Page Count in Stats"]) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(ThreadStats.timeout);
|
||||
if (ThreadStats.thread.isDead) {
|
||||
ThreadStats.pageCountEl.textContent = 'Dead';
|
||||
$.addClass(ThreadStats.pageCountEl, 'warning');
|
||||
@ -13027,6 +13048,7 @@
|
||||
}
|
||||
ThreadStats.pageCountEl.textContent = page.page;
|
||||
(page.page === this.response.length ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning');
|
||||
ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -13041,15 +13063,18 @@
|
||||
}
|
||||
if (Conf['Updater and Stats in Header']) {
|
||||
this.dialog = sc = $.el('span', {
|
||||
innerHTML: "[<span id=update-status></span><span id=update-timer title='Update now'></span>]\u00A0",
|
||||
id: 'updater'
|
||||
});
|
||||
$.extend(sc, {
|
||||
innerHTML: "[<span id=\"update-status\"></span><span id=\"update-timer\" title=\"Update now\"></span>]"
|
||||
});
|
||||
$.ready(function() {
|
||||
return Header.addShortcut(sc);
|
||||
});
|
||||
} else {
|
||||
this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', "<div class=move><span id=update-status></span><span id=update-timer title='Update now'></span></div>");
|
||||
$.addClass(doc, 'float');
|
||||
this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', {
|
||||
innerHTML: "<div class=\"move\"></div><span id=\"update-status\"></span><span id=\"update-timer\" title=\"Update now\"></span>"
|
||||
});
|
||||
$.ready(function() {
|
||||
$.addClass(doc, 'float');
|
||||
return $.add(d.body, sc);
|
||||
@ -13066,24 +13091,21 @@
|
||||
for (name in _ref) {
|
||||
conf = _ref[name];
|
||||
checked = Conf[name] ? 'checked' : '';
|
||||
el = $.el('label', {
|
||||
title: "" + conf[1],
|
||||
innerHTML: "<input name='" + name + "' type=checkbox " + checked + "> " + name
|
||||
});
|
||||
el = UI.checkbox(name, " " + name);
|
||||
input = el.firstElementChild;
|
||||
$.on(input, 'change', $.cb.checked);
|
||||
if (input.name === 'Scroll BG') {
|
||||
$.on(input, 'change', this.cb.scrollBG);
|
||||
this.cb.scrollBG();
|
||||
} else if (input.name === 'Auto Update') {
|
||||
$.on(input, 'change', this.cb.update);
|
||||
$.on(input, 'change', this.cb.autoUpdate);
|
||||
}
|
||||
subEntries.push({
|
||||
el: el
|
||||
});
|
||||
}
|
||||
this.settings = $.el('span', {
|
||||
innerHTML: '<a href=javascript:;>Interval</a>'
|
||||
innerHTML: "<a href=\"javascript:;\">Interval</a>"
|
||||
});
|
||||
$.on(this.settings, 'click', this.intervalShortcut);
|
||||
subEntries.push({
|
||||
@ -13144,6 +13166,7 @@
|
||||
ThreadUpdater.thread = this;
|
||||
ThreadUpdater.root = this.OP.nodes.root.parentNode;
|
||||
ThreadUpdater.lastPost = +this.posts.keys[this.posts.keys.length - 1];
|
||||
ThreadUpdater.outdateCount = 0;
|
||||
ThreadUpdater.cb.interval.call($.el('input', {
|
||||
value: Conf['Interval'],
|
||||
name: 'Interval'
|
||||
@ -13156,7 +13179,7 @@
|
||||
} else {
|
||||
ThreadUpdater.cb.online();
|
||||
}
|
||||
Rice.nodes(ThreadUpdater.dialog);
|
||||
return Rice.nodes(ThreadUpdater.dialog);
|
||||
},
|
||||
|
||||
/*
|
||||
@ -13166,14 +13189,18 @@
|
||||
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA',
|
||||
cb: {
|
||||
online: function() {
|
||||
if (ThreadUpdater.thread.isDead) {
|
||||
return;
|
||||
}
|
||||
if (ThreadUpdater.online = navigator.onLine) {
|
||||
ThreadUpdater.outdateCount = 0;
|
||||
ThreadUpdater.setInterval();
|
||||
ThreadUpdater.set('status', null, null);
|
||||
ThreadUpdater.set('status', '', '');
|
||||
return;
|
||||
}
|
||||
ThreadUpdater.set('timer', null);
|
||||
return ThreadUpdater.set('status', 'Offline', 'warning');
|
||||
ThreadUpdater.set('timer', '');
|
||||
ThreadUpdater.set('status', 'Offline', 'warning');
|
||||
return clearTimeout(ThreadUpdater.timeoutID);
|
||||
},
|
||||
post: function(e) {
|
||||
if (!(ThreadUpdater.isUpdating && e.detail.threadID === ThreadUpdater.thread.ID)) {
|
||||
@ -13186,14 +13213,14 @@
|
||||
},
|
||||
checkpost: function(e) {
|
||||
if (!ThreadUpdater.checkPostCount) {
|
||||
if (e.detail.threadID !== ThreadUpdater.thread.ID) {
|
||||
if (e && e.detail.threadID !== ThreadUpdater.thread.ID) {
|
||||
return;
|
||||
}
|
||||
ThreadUpdater.seconds = 0;
|
||||
ThreadUpdater.outdateCount = 0;
|
||||
ThreadUpdater.set('timer', '...');
|
||||
}
|
||||
if (!(g.DEAD || ThreadUpdater.foundPost || ThreadUpdater.checkPostCount >= 5)) {
|
||||
if (!(ThreadUpdater.thread.isDead || ThreadUpdater.foundPost || ThreadUpdater.checkPostCount >= 5)) {
|
||||
return setTimeout(ThreadUpdater.update, ++ThreadUpdater.checkPostCount * $.SECOND);
|
||||
}
|
||||
ThreadUpdater.setInterval();
|
||||
@ -13217,6 +13244,9 @@
|
||||
return !d.hidden;
|
||||
};
|
||||
},
|
||||
autoUpdate: function(e) {
|
||||
return ThreadUpdater.count(ThreadUpdater.isUpdating = this.checked);
|
||||
},
|
||||
interval: function(e) {
|
||||
var val;
|
||||
val = parseInt(this.value, 10);
|
||||
@ -13233,7 +13263,6 @@
|
||||
req = ThreadUpdater.req;
|
||||
switch (req.status) {
|
||||
case 200:
|
||||
g.DEAD = false;
|
||||
ThreadUpdater.parse(req.response.posts);
|
||||
if (ThreadUpdater.thread.isArchived) {
|
||||
ThreadUpdater.set('status', 'Archived', 'warning');
|
||||
@ -13338,9 +13367,10 @@
|
||||
var n;
|
||||
ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
|
||||
if (!(n = --ThreadUpdater.seconds)) {
|
||||
ThreadUpdater.outdateCount++;
|
||||
return ThreadUpdater.update();
|
||||
} else if (n <= -60) {
|
||||
ThreadUpdater.set('status', 'Retrying', null);
|
||||
ThreadUpdater.set('status', 'Retrying', '');
|
||||
return ThreadUpdater.update();
|
||||
} else if (n > 0) {
|
||||
return ThreadUpdater.set('timer', n);
|
||||
@ -13415,18 +13445,27 @@
|
||||
ThreadUpdater.thread.posts.forEach(function(post) {
|
||||
var ID;
|
||||
ID = +post.ID;
|
||||
if (__indexOf.call(index, ID) < 0) {
|
||||
post.kill();
|
||||
} else if (post.isDead) {
|
||||
post.resurrect();
|
||||
} else if (post.file && !post.file.isDead && __indexOf.call(files, ID) < 0) {
|
||||
post.kill(true);
|
||||
if (!(post.info.date > Date.now() - 60 * $.SECOND)) {
|
||||
if (__indexOf.call(index, ID) < 0) {
|
||||
post.kill();
|
||||
} else if (post.isDead) {
|
||||
post.resurrect();
|
||||
} else if (post.file && !(post.file.isDead || __indexOf.call(files, ID) >= 0)) {
|
||||
post.kill(true);
|
||||
}
|
||||
}
|
||||
if (ThreadUpdater.postID && ThreadUpdater.postID === ID) {
|
||||
return ThreadUpdater.foundPost = true;
|
||||
}
|
||||
});
|
||||
sendEvent = function() {
|
||||
var ipCountEl;
|
||||
if ((OP.unique_ips != null) && (ipCountEl = $.id('unique-ips'))) {
|
||||
ipCountEl.textContent = OP.unique_ips;
|
||||
ipCountEl.previousSibling.textContent = ipCountEl.previousSibling.textContent.replace(/\b(?:is|are)\b/, OP.unique_ips === 1 ? 'is' : 'are');
|
||||
ipCountEl.nextSibling.textContent = ipCountEl.nextSibling.textContent.replace(/\bposters?\b/, OP.unique_ips === 1 ? 'poster' : 'posters');
|
||||
}
|
||||
ThreadUpdater.postIDs = index;
|
||||
return $.event('ThreadUpdate', {
|
||||
404: false,
|
||||
threadID: ThreadUpdater.thread.fullID,
|
||||
@ -13439,7 +13478,7 @@
|
||||
});
|
||||
};
|
||||
if (!count) {
|
||||
ThreadUpdater.set('status', null, null);
|
||||
ThreadUpdater.set('status', '', '');
|
||||
ThreadUpdater.outdateCount++;
|
||||
sendEvent();
|
||||
return;
|
||||
@ -13486,13 +13525,17 @@
|
||||
return;
|
||||
}
|
||||
this.db = new DataBoard('watchedThreads', this.refresh, true);
|
||||
this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', "<div><span class=\"move\">Thread Watcher <span id=\"watcher-status\"></span></span><a class=\"menu-button\" href=\"javascript:;\"><i class=\"fa\">\uf107</i></a></div><div id=\"watched-threads\"></div>");
|
||||
this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', {
|
||||
innerHTML: "<div>\r<span class=\"move\">\rThread Watcher \r<a class=\"refresh fa\" title=\"Check threads\" href=\"javascript:;\">\\uf021</a>\r<span id=\"watcher-status\"></span>\r</span>\r<a class=\"menu-button\" href=\"javascript:;\"><i class=\"fa\">\\uf107</i></a>\r</div>\r<div id=\"watched-threads\"></div>"
|
||||
});
|
||||
this.status = $('#watcher-status', this.dialog);
|
||||
this.list = this.dialog.lastElementChild;
|
||||
this.refreshButton = $('.refresh', this.dialog);
|
||||
$.on(d, 'QRPostSuccessful', this.cb.post);
|
||||
if (g.VIEW === 'thread') {
|
||||
$.on(d, 'ThreadUpdate', this.cb.threadUpdate);
|
||||
}
|
||||
$.on(this.refreshButton, 'click', this.fetchAllStatus);
|
||||
$.on(d, '4chanXInitFinished', this.ready);
|
||||
switch (g.VIEW) {
|
||||
case 'index':
|
||||
@ -13507,18 +13550,75 @@
|
||||
ThreadWatcher.fetchAllStatus();
|
||||
this.db.save();
|
||||
}
|
||||
return Thread.callbacks.push({
|
||||
if (Conf['JSON Navigation'] && Conf['Menu'] && g.BOARD.ID !== 'f') {
|
||||
Menu.menu.addEntry({
|
||||
el: $.el('a', {
|
||||
href: 'javascript:;'
|
||||
}),
|
||||
order: 6,
|
||||
open: function(_arg) {
|
||||
var thread;
|
||||
thread = _arg.thread;
|
||||
if (!(Conf['Index Mode'] === 'catalog' && g.VIEW === 'index')) {
|
||||
return false;
|
||||
}
|
||||
this.el.textContent = ThreadWatcher.isWatched(thread) ? 'Unwatch thread' : 'Watch thread';
|
||||
if (this.cb) {
|
||||
$.off(this.el, 'click', this.cb);
|
||||
}
|
||||
this.cb = function() {
|
||||
$.event('CloseMenu');
|
||||
return ThreadWatcher.toggle(thread);
|
||||
};
|
||||
$.on(this.el, 'click', this.cb);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
Post.callbacks.push({
|
||||
name: 'Thread Watcher',
|
||||
cb: this.node
|
||||
});
|
||||
return CatalogThread.callbacks.push({
|
||||
name: 'Thread Watcher',
|
||||
cb: this.catalogNode
|
||||
});
|
||||
},
|
||||
isWatched: function(thread) {
|
||||
var _ref;
|
||||
return (_ref = ThreadWatcher.db) != null ? _ref.get({
|
||||
boardID: thread.board.ID,
|
||||
threadID: thread.ID
|
||||
}) : void 0;
|
||||
},
|
||||
node: function() {
|
||||
var toggler;
|
||||
toggler = $.el('img', {
|
||||
className: 'watch-thread-link'
|
||||
});
|
||||
$.on(toggler, 'click', ThreadWatcher.cb.toggle);
|
||||
return $.before($('input', this.OP.nodes.post), toggler);
|
||||
if (this.isReply) {
|
||||
return;
|
||||
}
|
||||
if (this.isClone) {
|
||||
toggler = $('.watch-thread-link', this.nodes.post);
|
||||
} else {
|
||||
toggler = $.el('img', {
|
||||
className: 'watch-thread-link'
|
||||
});
|
||||
$.before($('input', this.nodes.post), toggler);
|
||||
}
|
||||
return $.on(toggler, 'click', ThreadWatcher.cb.toggle);
|
||||
},
|
||||
catalogNode: function() {
|
||||
if (ThreadWatcher.isWatched(this.thread)) {
|
||||
$.addClass(this.nodes.root, 'watched');
|
||||
}
|
||||
return $.on(this.nodes.thumb.parentNode, 'click', (function(_this) {
|
||||
return function(e) {
|
||||
if (!(e.button === 0 && e.altKey)) {
|
||||
return;
|
||||
}
|
||||
ThreadWatcher.toggle(_this.thread);
|
||||
return e.preventDefault();
|
||||
};
|
||||
})(this));
|
||||
},
|
||||
ready: function() {
|
||||
var el;
|
||||
@ -13563,12 +13663,6 @@
|
||||
}
|
||||
return $.event('CloseMenu');
|
||||
},
|
||||
checkThreads: function() {
|
||||
if ($.hasClass(this, 'disabled')) {
|
||||
return;
|
||||
}
|
||||
return ThreadWatcher.fetchAllStatus();
|
||||
},
|
||||
pruneDeads: function() {
|
||||
var boardID, data, threadID, _i, _len, _ref, _ref1;
|
||||
if ($.hasClass(this, 'disabled')) {
|
||||
@ -13590,7 +13684,10 @@
|
||||
return $.event('CloseMenu');
|
||||
},
|
||||
toggle: function() {
|
||||
return ThreadWatcher.toggle(Get.threadFromNode(this));
|
||||
ThreadWatcher.toggle(Get.threadFromNode(this));
|
||||
Index.followedThreadID = thread.ID;
|
||||
ThreadWatcher.toggle(thread);
|
||||
return delete Index.followedThreadID;
|
||||
},
|
||||
rm: function() {
|
||||
var boardID, threadID, _ref;
|
||||
@ -13609,9 +13706,11 @@
|
||||
}
|
||||
},
|
||||
onIndexRefresh: function() {
|
||||
var boardID, data, threadID, _ref;
|
||||
var boardID, data, db, threadID, _ref;
|
||||
db = ThreadWatcher.db;
|
||||
boardID = g.BOARD.ID;
|
||||
_ref = ThreadWatcher.db.data.boards[boardID];
|
||||
db.forceSync();
|
||||
_ref = db.data.boards[boardID];
|
||||
for (threadID in _ref) {
|
||||
data = _ref[threadID];
|
||||
if (!data.isDead && !(threadID in g.BOARD.threads)) {
|
||||
@ -13650,10 +13749,12 @@
|
||||
},
|
||||
fetchAllStatus: function() {
|
||||
var thread, threads, _i, _len;
|
||||
ThreadWatcher.db.forceSync();
|
||||
ThreadWatcher.unreaddb.forceSync();
|
||||
QR.db.forceSync();
|
||||
if (!(threads = ThreadWatcher.getAll()).length) {
|
||||
return;
|
||||
}
|
||||
ThreadWatcher.status.textContent = '...';
|
||||
for (_i = 0, _len = threads.length; _i < _len; _i++) {
|
||||
thread = threads[_i];
|
||||
ThreadWatcher.fetchStatus(thread);
|
||||
@ -13662,43 +13763,103 @@
|
||||
fetchStatus: function(_arg) {
|
||||
var boardID, data, fetchCount, threadID;
|
||||
boardID = _arg.boardID, threadID = _arg.threadID, data = _arg.data;
|
||||
if (data.isDead) {
|
||||
if (data.isDead && !Conf['Show Unread Count']) {
|
||||
return;
|
||||
}
|
||||
fetchCount = ThreadWatcher.fetchCount;
|
||||
if (fetchCount.fetching === 0) {
|
||||
ThreadWatcher.status.textContent = '...';
|
||||
$.addClass(ThreadWatcher.refreshButton, 'fa-spin');
|
||||
}
|
||||
fetchCount.fetching++;
|
||||
return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
|
||||
onloadend: function() {
|
||||
var status;
|
||||
var isDead, lastReadPost, match, postObj, quotingYou, regexp, status, unread, _i, _len, _ref, _ref1;
|
||||
fetchCount.fetched++;
|
||||
if (fetchCount.fetched === fetchCount.fetching) {
|
||||
fetchCount.fetched = 0;
|
||||
fetchCount.fetching = 0;
|
||||
status = '';
|
||||
$.rmClass(ThreadWatcher.refreshButton, 'fa-spin');
|
||||
} else {
|
||||
status = "" + (Math.round(fetchCount.fetched / fetchCount.fetching * 100)) + "%";
|
||||
}
|
||||
ThreadWatcher.status.textContent = status;
|
||||
if (this.status !== 404) {
|
||||
return;
|
||||
}
|
||||
if (Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
} else {
|
||||
data.isDead = true;
|
||||
ThreadWatcher.db.set({
|
||||
if (this.status === 200 && this.response) {
|
||||
isDead = !!this.response.posts[0].archived;
|
||||
if (isDead && Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
ThreadWatcher.refresh();
|
||||
return;
|
||||
}
|
||||
lastReadPost = ThreadWatcher.unreaddb.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
defaultValue: 0
|
||||
});
|
||||
unread = quotingYou = 0;
|
||||
_ref = this.response.posts;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
postObj = _ref[_i];
|
||||
if (!(postObj.no > lastReadPost)) {
|
||||
continue;
|
||||
}
|
||||
if ((_ref1 = QR.db) != null ? _ref1.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
postID: postObj.no
|
||||
}) : void 0) {
|
||||
continue;
|
||||
}
|
||||
unread++;
|
||||
if (!(QR.db && postObj.com)) {
|
||||
continue;
|
||||
}
|
||||
regexp = /<a [^>]*\bhref="(?:\/([^\/]+)\/thread\/(\d+))?(?:#p(\d+))?"/g;
|
||||
while (match = regexp.exec(postObj.com)) {
|
||||
if (QR.db.get({
|
||||
boardID: match[1] || boardID,
|
||||
threadID: match[2] || threadID,
|
||||
postID: match[3] || match[2] || threadID
|
||||
})) {
|
||||
quotingYou++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) {
|
||||
data.isDead = isDead;
|
||||
data.unread = unread;
|
||||
data.quotingYou = quotingYou;
|
||||
ThreadWatcher.db.set({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
} else if (this.status === 404) {
|
||||
if (Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
} else {
|
||||
data.isDead = true;
|
||||
delete data.unread;
|
||||
delete data.quotingYou;
|
||||
ThreadWatcher.db.set({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
}
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
}, {
|
||||
type: 'head'
|
||||
});
|
||||
},
|
||||
getAll: function() {
|
||||
@ -13722,24 +13883,31 @@
|
||||
return all;
|
||||
},
|
||||
makeLine: function(boardID, threadID, data) {
|
||||
var div, fullID, href, link, x;
|
||||
var count, div, fullID, link, title, x;
|
||||
x = $.el('a', {
|
||||
className: 'fa',
|
||||
href: 'javascript:;',
|
||||
textContent: '\uf00d'
|
||||
});
|
||||
$.on(x, 'click', ThreadWatcher.cb.rm);
|
||||
if (data.isDead) {
|
||||
href = Redirect.to('thread', {
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
}
|
||||
link = $.el('a', {
|
||||
href: href || ("/" + boardID + "/thread/" + threadID),
|
||||
href: "/" + boardID + "/thread/" + threadID,
|
||||
textContent: data.excerpt,
|
||||
title: data.excerpt
|
||||
title: data.excerpt,
|
||||
className: 'watcher-link'
|
||||
});
|
||||
if (Conf['Show Unread Count'] && (data.unread != null)) {
|
||||
count = $.el('span', {
|
||||
textContent: "(" + data.unread + ")",
|
||||
className: 'watcher-unread'
|
||||
});
|
||||
$.add(link, count);
|
||||
}
|
||||
title = $.el('span', {
|
||||
textContent: data.excerpt,
|
||||
className: 'watcher-title'
|
||||
});
|
||||
$.add(link, title);
|
||||
div = $.el('div');
|
||||
fullID = "" + boardID + "." + threadID;
|
||||
div.dataset.fullID = fullID;
|
||||
@ -13749,11 +13917,19 @@
|
||||
if (data.isDead) {
|
||||
$.addClass(div, 'dead-thread');
|
||||
}
|
||||
if (Conf['Show Unread Count']) {
|
||||
if (data.unread) {
|
||||
$.addClass(div, 'replies-unread');
|
||||
}
|
||||
if (data.quotingYou) {
|
||||
$.addClass(div, 'replies-quoting-you');
|
||||
}
|
||||
}
|
||||
$.add(div, [x, $.tn(' '), link]);
|
||||
return div;
|
||||
},
|
||||
refresh: function() {
|
||||
var boardID, data, helper, list, nodes, refresher, thread, threadID, threads, toggler, watched, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3;
|
||||
var boardID, data, list, nodes, refresher, threadID, _i, _j, _len, _len1, _ref, _ref1, _ref2;
|
||||
nodes = [];
|
||||
_ref = ThreadWatcher.getAll();
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
@ -13763,24 +13939,76 @@
|
||||
list = ThreadWatcher.list;
|
||||
$.rmAll(list);
|
||||
$.add(list, nodes);
|
||||
threads = g.BOARD.threads;
|
||||
_ref2 = threads.keys;
|
||||
g.threads.forEach(function(thread) {
|
||||
var helper, post, toggler, _j, _len1, _ref2;
|
||||
helper = ThreadWatcher.isWatched(thread) ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
|
||||
if (thread.OP) {
|
||||
_ref2 = [thread.OP].concat(__slice.call(thread.OP.clones));
|
||||
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
|
||||
post = _ref2[_j];
|
||||
toggler = $('.watch-thread-link', post.nodes.post);
|
||||
$[helper[0]](toggler, 'watched');
|
||||
toggler.title = "" + helper[1] + " Thread";
|
||||
}
|
||||
}
|
||||
if (thread.catalogView) {
|
||||
return $[helper[0]](thread.catalogView.nodes.root, 'watched');
|
||||
}
|
||||
});
|
||||
_ref2 = ThreadWatcher.menu.refreshers;
|
||||
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
|
||||
threadID = _ref2[_j];
|
||||
thread = threads[threadID];
|
||||
toggler = $('.watch-thread-link', thread.OP.nodes.post);
|
||||
watched = ThreadWatcher.db.get({
|
||||
boardID: thread.board.ID,
|
||||
refresher = _ref2[_j];
|
||||
refresher();
|
||||
}
|
||||
if (Index.nodes && Conf['Pin Watched Threads']) {
|
||||
Index.sort();
|
||||
return Index.buildIndex();
|
||||
}
|
||||
},
|
||||
update: function(boardID, threadID, newData) {
|
||||
var data, key, line, n, newLine, val, _ref;
|
||||
if (!(data = (_ref = ThreadWatcher.db) != null ? _ref.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
}) : void 0)) {
|
||||
return;
|
||||
}
|
||||
if (newData.isDead && Conf['Auto Prune']) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
});
|
||||
helper = watched ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
|
||||
$[helper[0]](toggler, 'watched');
|
||||
toggler.title = "" + helper[1] + " Thread";
|
||||
ThreadWatcher.refresh();
|
||||
return;
|
||||
}
|
||||
_ref3 = ThreadWatcher.menu.refreshers;
|
||||
for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
|
||||
refresher = _ref3[_k];
|
||||
refresher();
|
||||
n = 0;
|
||||
for (key in newData) {
|
||||
val = newData[key];
|
||||
if (data[key] !== val) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
ThreadWatcher.db.forceSync();
|
||||
if (!(data = ThreadWatcher.db.get({
|
||||
boardID: boardID,
|
||||
threadID: threadID
|
||||
}))) {
|
||||
return;
|
||||
}
|
||||
$.extend(data, newData);
|
||||
ThreadWatcher.db.set({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
if (line = $("#watched-threads > [data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog)) {
|
||||
newLine = ThreadWatcher.makeLine(boardID, threadID, data);
|
||||
return $.replace(line, newLine);
|
||||
} else {
|
||||
return ThreadWatcher.refresh();
|
||||
}
|
||||
},
|
||||
toggle: function(thread) {
|
||||
@ -13817,7 +14045,14 @@
|
||||
threadID: threadID,
|
||||
val: data
|
||||
});
|
||||
return ThreadWatcher.refresh();
|
||||
ThreadWatcher.refresh();
|
||||
if (Conf['Show Unread Count']) {
|
||||
return ThreadWatcher.fetchStatus({
|
||||
boardID: boardID,
|
||||
threadID: threadID,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
},
|
||||
rm: function(boardID, threadID) {
|
||||
ThreadWatcher.db["delete"]({
|
||||
@ -13847,12 +14082,12 @@
|
||||
if (!Conf['Thread Watcher']) {
|
||||
return;
|
||||
}
|
||||
menu = new UI.Menu();
|
||||
menu = this.menu = new UI.Menu('thread watcher');
|
||||
$.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) {
|
||||
return menu.toggle(e, this, ThreadWatcher);
|
||||
});
|
||||
this.addHeaderMenuEntry();
|
||||
return this.addMenuEntries(menu);
|
||||
return this.addMenuEntries;
|
||||
},
|
||||
addHeaderMenuEntry: function() {
|
||||
var entryEl;
|
||||
@ -13877,7 +14112,7 @@
|
||||
return entryEl.textContent = text;
|
||||
});
|
||||
},
|
||||
addMenuEntries: function(menu) {
|
||||
addMenuEntries: function() {
|
||||
var cb, conf, entries, entry, name, refresh, subEntries, _i, _len, _ref, _ref1;
|
||||
entries = [];
|
||||
entries.push({
|
||||
@ -13891,22 +14126,11 @@
|
||||
return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled');
|
||||
}
|
||||
});
|
||||
entries.push({
|
||||
cb: ThreadWatcher.cb.checkThreads,
|
||||
entry: {
|
||||
el: $.el('a', {
|
||||
textContent: 'Check 404\'d threads'
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
return ($('div:not(.dead-thread)', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled');
|
||||
}
|
||||
});
|
||||
entries.push({
|
||||
cb: ThreadWatcher.cb.pruneDeads,
|
||||
entry: {
|
||||
el: $.el('a', {
|
||||
textContent: 'Prune 404\'d threads'
|
||||
textContent: 'Prune dead threads'
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
@ -13938,22 +14162,18 @@
|
||||
if (refresh) {
|
||||
this.refreshers.push(refresh.bind(entry));
|
||||
}
|
||||
menu.addEntry(entry);
|
||||
this.menu.addEntry(entry);
|
||||
}
|
||||
},
|
||||
createSubEntry: function(name, desc) {
|
||||
var entry, input;
|
||||
entry = {
|
||||
type: 'thread watcher',
|
||||
el: $.el('label', {
|
||||
innerHTML: "<input type=checkbox name='" + name + "'> " + name,
|
||||
title: desc
|
||||
})
|
||||
el: UI.checkbox(name, " " + name)
|
||||
};
|
||||
input = entry.el.firstElementChild;
|
||||
input.checked = Conf[name];
|
||||
$.on(input, 'change', $.cb.checked);
|
||||
if (name === 'Current Board') {
|
||||
if (name === 'Current Board' || name === 'Show Unread Count') {
|
||||
$.on(input, 'change', ThreadWatcher.refresh);
|
||||
}
|
||||
return entry;
|
||||
@ -15778,10 +15998,14 @@
|
||||
}
|
||||
},
|
||||
getThread: function() {
|
||||
var threadRoot, _i, _len, _ref;
|
||||
var thread, threadRoot, _i, _len, _ref;
|
||||
_ref = $$('.thread');
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
threadRoot = _ref[_i];
|
||||
thread = Get.threadFromRoot(threadRoot);
|
||||
if (thread.isHidden && !thread.stub) {
|
||||
continue;
|
||||
}
|
||||
if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) {
|
||||
return threadRoot;
|
||||
}
|
||||
@ -15789,7 +16013,10 @@
|
||||
return $('.board');
|
||||
},
|
||||
scroll: function(delta) {
|
||||
var axis, next, thread, top;
|
||||
var axis, extra, next, thread, top, _ref;
|
||||
if ((_ref = d.activeElement) != null) {
|
||||
_ref.blur();
|
||||
}
|
||||
thread = Nav.getThread();
|
||||
axis = delta === +1 ? 'following' : 'preceding';
|
||||
if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) {
|
||||
@ -15798,48 +16025,64 @@
|
||||
thread = next;
|
||||
}
|
||||
}
|
||||
return Header.scrollTo(thread);
|
||||
extra = Header.getTopOf(thread) + doc.clientHeight - d.body.getBoundingClientRect().bottom;
|
||||
if (extra > 0) {
|
||||
d.body.style.marginBottom = "" + extra + "px";
|
||||
}
|
||||
Header.scrollTo(thread);
|
||||
if (extra > 0 && !Nav.haveExtra) {
|
||||
Nav.haveExtra = true;
|
||||
return $.on(d, 'scroll', Nav.removeExtra);
|
||||
}
|
||||
},
|
||||
removeExtra: function() {
|
||||
var extra;
|
||||
extra = doc.clientHeight - d.body.getBoundingClientRect().bottom;
|
||||
if (extra > 0) {
|
||||
return d.body.style.marginBottom = "" + extra + "px";
|
||||
} else {
|
||||
d.body.style.marginBottom = null;
|
||||
delete Nav.haveExtra;
|
||||
return $.off(d, 'scroll', Nav.removeExtra);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RelativeDates = {
|
||||
INTERVAL: $.MINUTE / 2,
|
||||
init: function() {
|
||||
switch (g.VIEW) {
|
||||
case 'index':
|
||||
this.flush();
|
||||
$.on(d, 'visibilitychange', this.flush);
|
||||
if (!Conf['Relative Post Dates']) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'thread':
|
||||
if (!Conf['Relative Post Dates']) {
|
||||
return;
|
||||
}
|
||||
this.flush();
|
||||
$.on(d, 'visibilitychange ThreadUpdate', this.flush);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
var _ref;
|
||||
if (((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || g.VIEW === 'index' && Conf['JSON Navigation'] && g.BOARD.ID !== 'f') {
|
||||
this.flush();
|
||||
$.on(d, 'visibilitychange ThreadUpdate', this.flush);
|
||||
}
|
||||
if (Conf['Relative Post Dates']) {
|
||||
return Post.callbacks.push({
|
||||
name: 'Relative Post Dates',
|
||||
cb: this.node
|
||||
});
|
||||
}
|
||||
return Post.callbacks.push({
|
||||
name: 'Relative Post Dates',
|
||||
cb: this.node
|
||||
});
|
||||
},
|
||||
node: function() {
|
||||
var dateEl;
|
||||
dateEl = this.nodes.date;
|
||||
if (Conf['Relative Date Title']) {
|
||||
$.on(dateEl, 'mouseover', (function(_this) {
|
||||
return function() {
|
||||
return RelativeDates.hover(_this);
|
||||
};
|
||||
})(this));
|
||||
return;
|
||||
}
|
||||
if (this.isClone) {
|
||||
return;
|
||||
}
|
||||
dateEl = this.nodes.date;
|
||||
dateEl.title = dateEl.textContent;
|
||||
return RelativeDates.update(this);
|
||||
},
|
||||
relative: function(diff, now, date) {
|
||||
var days, months, number, rounded, unit, years;
|
||||
unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second');
|
||||
unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second');
|
||||
rounded = Math.round(number);
|
||||
if (rounded !== 1) {
|
||||
unit += 's';
|
||||
@ -15862,6 +16105,13 @@
|
||||
clearTimeout(RelativeDates.timeout);
|
||||
return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL);
|
||||
},
|
||||
hover: function(post) {
|
||||
var date, diff, now;
|
||||
date = post.info.date;
|
||||
now = new Date();
|
||||
diff = now - date;
|
||||
return post.nodes.date.title = RelativeDates.relative(diff, now, date);
|
||||
},
|
||||
update: function(data, now) {
|
||||
var date, diff, isPost, relative, singlePost, _i, _len, _ref;
|
||||
isPost = data instanceof Post;
|
||||
@ -15901,44 +16151,45 @@
|
||||
if (Conf['Reveal Spoilers']) {
|
||||
$.addClass(doc, 'reveal-spoilers');
|
||||
}
|
||||
if (Conf['Remove Spoilers']) {
|
||||
return $.addClass(doc, 'remove-spoilers');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Report = {
|
||||
init: function() {
|
||||
if (!/report/.test(location.search)) {
|
||||
if (!Conf['Remove Spoilers']) {
|
||||
return;
|
||||
}
|
||||
return $.asap((function() {
|
||||
return $.id('recaptcha_response_field');
|
||||
}), Report.ready);
|
||||
$.addClass(doc, 'remove-spoilers');
|
||||
Post.callbacks.push({
|
||||
name: 'Reveal Spoilers',
|
||||
cb: this.node
|
||||
});
|
||||
CatalogThread.callbacks.push({
|
||||
name: 'Reveal Spoilers',
|
||||
cb: this.node
|
||||
});
|
||||
if (g.VIEW === 'archive') {
|
||||
return $.ready(function() {
|
||||
return RemoveSpoilers.unspoiler($.id('arc-list'));
|
||||
});
|
||||
}
|
||||
},
|
||||
ready: function() {
|
||||
var field;
|
||||
field = $.id('recaptcha_response_field');
|
||||
$.on(field, 'keydown', function(e) {
|
||||
if (e.keyCode === 8 && !field.value) {
|
||||
return $.globalEval('Recaptcha.reload("t")');
|
||||
}
|
||||
});
|
||||
return $.on($('form'), 'submit', function(e) {
|
||||
var response;
|
||||
e.preventDefault();
|
||||
response = field.value.trim();
|
||||
if (!/\s|^\d+$/.test(response)) {
|
||||
field.value = "" + response + " " + response;
|
||||
}
|
||||
return this.submit();
|
||||
});
|
||||
node: function(post) {
|
||||
return RemoveSpoilers.unspoiler(this.nodes.comment);
|
||||
},
|
||||
unspoiler: function(el) {
|
||||
var span, spoiler, spoilers, _i, _len;
|
||||
spoilers = $$('s', el);
|
||||
for (_i = 0, _len = spoilers.length; _i < _len; _i++) {
|
||||
spoiler = spoilers[_i];
|
||||
span = $.el('span', {
|
||||
className: 'removed-spoiler'
|
||||
});
|
||||
$.replace(spoiler, span);
|
||||
$.add(span, __slice.call(spoiler.childNodes));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Time = {
|
||||
init: function() {
|
||||
if (!Conf['Time Formatting']) {
|
||||
var _ref;
|
||||
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Time Formatting'])) {
|
||||
return;
|
||||
}
|
||||
return Post.callbacks.push({
|
||||
@ -15953,7 +16204,7 @@
|
||||
return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date);
|
||||
},
|
||||
format: function(formatString, date) {
|
||||
return formatString.replace(/%([A-Za-z])/g, function(s, c) {
|
||||
return formatString.replace(/%(.)/g, function(s, c) {
|
||||
if (c in Time.formatters) {
|
||||
return Time.formatters[c].call(date);
|
||||
} else {
|
||||
@ -16029,6 +16280,9 @@
|
||||
},
|
||||
Y: function() {
|
||||
return this.getFullYear();
|
||||
},
|
||||
'%': function() {
|
||||
return '%';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<div>
|
||||
<span class="move">Thread Watcher <span id="watcher-status"></span></span>
|
||||
<span class="move">
|
||||
Thread Watcher
|
||||
<a class="refresh fa" title="Check threads" href="javascript:;">\uf021</a>
|
||||
<span id="watcher-status"></span>
|
||||
</span>
|
||||
<a class="menu-button" href="javascript:;"><i class="fa">\uf107</i></a>
|
||||
</div>
|
||||
<div id="watched-threads"></div>
|
||||
<div id="watched-threads"></div>
|
||||
@ -3,16 +3,17 @@ class Thread
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (@ID, @board) ->
|
||||
@fullID = "#{@board}.#{@ID}"
|
||||
@posts = new SimpleDict
|
||||
@isDead = false
|
||||
@isHidden = false
|
||||
@isOnTop = false
|
||||
@isPinned = false
|
||||
@isSticky = false
|
||||
@isClosed = false
|
||||
@postLimit = false
|
||||
@fileLimit = false
|
||||
@fullID = "#{@board}.#{@ID}"
|
||||
@posts = new SimpleDict
|
||||
@isDead = false
|
||||
@isHidden = false
|
||||
@isOnTop = false
|
||||
@isPinned = false
|
||||
@isSticky = false
|
||||
@isClosed = false
|
||||
@isArchived = false
|
||||
@postLimit = false
|
||||
@fileLimit = false
|
||||
@ipCount = undefined
|
||||
|
||||
@OP = null
|
||||
|
||||
@ -33,11 +33,14 @@ Nav =
|
||||
|
||||
getThread: ->
|
||||
for threadRoot in $$ '.thread'
|
||||
thread = Get.threadFromRoot threadRoot
|
||||
continue if thread.isHidden and !thread.stub
|
||||
if Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height # not scrolled past
|
||||
return threadRoot
|
||||
return $ '.board'
|
||||
|
||||
scroll: (delta) ->
|
||||
d.activeElement?.blur()
|
||||
thread = Nav.getThread()
|
||||
axis = if delta is +1
|
||||
'following'
|
||||
@ -49,4 +52,21 @@ Nav =
|
||||
# or we're above the first thread and don't want to skip it.
|
||||
top = Header.getTopOf thread
|
||||
thread = next if delta is +1 and top < 5 or delta is -1 and top > -5
|
||||
# Add extra space to the end of the page if necessary so that all threads can be selected by keybinds.
|
||||
extra = Header.getTopOf(thread) + doc.clientHeight - d.body.getBoundingClientRect().bottom
|
||||
d.body.style.marginBottom = "#{extra}px" if extra > 0
|
||||
|
||||
Header.scrollTo thread
|
||||
|
||||
if extra > 0 and !Nav.haveExtra
|
||||
Nav.haveExtra = true
|
||||
$.on d, 'scroll', Nav.removeExtra
|
||||
|
||||
removeExtra: ->
|
||||
extra = doc.clientHeight - d.body.getBoundingClientRect().bottom
|
||||
if extra > 0
|
||||
d.body.style.marginBottom = "#{extra}px"
|
||||
else
|
||||
d.body.style.marginBottom = null
|
||||
delete Nav.haveExtra
|
||||
$.off d, 'scroll', Nav.removeExtra
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
RelativeDates =
|
||||
INTERVAL: $.MINUTE / 2
|
||||
init: ->
|
||||
switch g.VIEW
|
||||
when 'index'
|
||||
@flush()
|
||||
$.on d, 'visibilitychange', @flush
|
||||
return unless Conf['Relative Post Dates']
|
||||
when 'thread'
|
||||
return unless Conf['Relative Post Dates']
|
||||
@flush()
|
||||
$.on d, 'visibilitychange ThreadUpdate', @flush
|
||||
else
|
||||
return
|
||||
if (
|
||||
g.VIEW in ['index', 'thread'] and Conf['Relative Post Dates'] and !Conf['Relative Date Title'] or
|
||||
g.VIEW is 'index' and Conf['JSON Navigation'] and g.BOARD.ID isnt 'f'
|
||||
)
|
||||
@flush()
|
||||
$.on d, 'visibilitychange ThreadUpdate', @flush
|
||||
|
||||
if Conf['Relative Post Dates']
|
||||
Post.callbacks.push
|
||||
name: 'Relative Post Dates'
|
||||
cb: @node
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Relative Post Dates'
|
||||
cb: @node
|
||||
node: ->
|
||||
dateEl = @nodes.date
|
||||
if Conf['Relative Date Title']
|
||||
$.on dateEl, 'mouseover', => RelativeDates.hover @
|
||||
return
|
||||
return if @isClone
|
||||
|
||||
# Show original absolute time as tooltip so users can still know exact times
|
||||
# Since "Time Formatting" runs its `node` before us, the title tooltip will
|
||||
# pick up the user-formatted time instead of 4chan time when enabled.
|
||||
dateEl = @nodes.date
|
||||
dateEl.title = dateEl.textContent
|
||||
|
||||
RelativeDates.update @
|
||||
@ -39,7 +39,7 @@ RelativeDates =
|
||||
else if years is 1 and (months > 0 or months is 0 and days >= 0)
|
||||
number = years
|
||||
'year'
|
||||
else if (months = (months+12)%12 ) > 1
|
||||
else if (months = months + 12*years) > 1
|
||||
number = months - (days < 0)
|
||||
'month'
|
||||
else if months is 1 and days >= 0
|
||||
@ -82,6 +82,12 @@ RelativeDates =
|
||||
clearTimeout RelativeDates.timeout
|
||||
RelativeDates.timeout = setTimeout RelativeDates.flush, RelativeDates.INTERVAL
|
||||
|
||||
hover: (post) ->
|
||||
date = post.info.date
|
||||
now = new Date()
|
||||
diff = now - date
|
||||
post.nodes.date.title = RelativeDates.relative diff, now, date
|
||||
|
||||
# `update()`, when called from `flush()`, updates the elements,
|
||||
# and re-calls `setOwnTimeout()` to re-add `data` to the stale list later.
|
||||
update: (data, now) ->
|
||||
|
||||
@ -3,5 +3,28 @@ RemoveSpoilers =
|
||||
if Conf['Reveal Spoilers']
|
||||
$.addClass doc, 'reveal-spoilers'
|
||||
|
||||
if Conf['Remove Spoilers']
|
||||
$.addClass doc, 'remove-spoilers'
|
||||
return unless Conf['Remove Spoilers']
|
||||
|
||||
$.addClass doc, 'remove-spoilers'
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Reveal Spoilers'
|
||||
cb: @node
|
||||
|
||||
CatalogThread.callbacks.push
|
||||
name: 'Reveal Spoilers'
|
||||
cb: @node
|
||||
|
||||
if g.VIEW is 'archive'
|
||||
$.ready -> RemoveSpoilers.unspoiler $.id 'arc-list'
|
||||
|
||||
node: (post) ->
|
||||
RemoveSpoilers.unspoiler @nodes.comment
|
||||
|
||||
unspoiler: (el) ->
|
||||
spoilers = $$ 's', el
|
||||
for spoiler in spoilers
|
||||
span = $.el 'span', className: 'removed-spoiler'
|
||||
$.replace spoiler, span
|
||||
$.add span, [spoiler.childNodes...]
|
||||
return
|
||||
@ -1,13 +0,0 @@
|
||||
Report =
|
||||
init: ->
|
||||
return unless /report/.test(location.search)
|
||||
$.asap (-> $.id 'recaptcha_response_field'), Report.ready
|
||||
ready: ->
|
||||
field = $.id 'recaptcha_response_field'
|
||||
$.on field, 'keydown', (e) ->
|
||||
$.globalEval 'Recaptcha.reload("t")' if e.keyCode is 8 and not field.value
|
||||
$.on $('form'), 'submit', (e) ->
|
||||
e.preventDefault()
|
||||
response = field.value.trim()
|
||||
field.value = "#{response} #{response}" unless /\s|^\d+$/.test response
|
||||
@submit()
|
||||
@ -1,19 +1,21 @@
|
||||
Time =
|
||||
init: ->
|
||||
return if !Conf['Time Formatting']
|
||||
return unless g.VIEW in ['index', 'thread'] and Conf['Time Formatting']
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Time Formatting'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return if @isClone
|
||||
@nodes.date.textContent = Time.format Conf['time'], @info.date
|
||||
format: (formatString, date) ->
|
||||
formatString.replace /%([A-Za-z])/g, (s, c) ->
|
||||
formatString.replace /%(.)/g, (s, c) ->
|
||||
if c of Time.formatters
|
||||
Time.formatters[c].call(date)
|
||||
else
|
||||
s
|
||||
|
||||
day: [
|
||||
'Sunday'
|
||||
'Monday'
|
||||
@ -23,6 +25,7 @@ Time =
|
||||
'Friday'
|
||||
'Saturday'
|
||||
]
|
||||
|
||||
month: [
|
||||
'January'
|
||||
'February'
|
||||
@ -37,7 +40,9 @@ Time =
|
||||
'November'
|
||||
'December'
|
||||
]
|
||||
|
||||
zeroPad: (n) -> if n < 10 then "0#{n}" else n
|
||||
|
||||
formatters:
|
||||
a: -> Time.day[@getDay()][...3]
|
||||
A: -> Time.day[@getDay()]
|
||||
@ -56,3 +61,4 @@ Time =
|
||||
S: -> Time.zeroPad @getSeconds()
|
||||
y: -> @getFullYear().toString()[2..]
|
||||
Y: -> @getFullYear()
|
||||
'%': -> '%'
|
||||
|
||||
@ -1,5 +1,15 @@
|
||||
Favicon =
|
||||
init: ->
|
||||
$.asap (-> d.head and Favicon.el = $ 'link[rel="shortcut icon"]', d.head), Favicon.initAsap
|
||||
|
||||
initAsap: ->
|
||||
Favicon.el.type = 'image/x-icon'
|
||||
{href} = Favicon.el
|
||||
Favicon.SFW = /ws\.ico$/.test href
|
||||
Favicon.default = href
|
||||
Favicon.switch()
|
||||
|
||||
switch: ->
|
||||
items = {
|
||||
ferongr: [
|
||||
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDead.png", {encoding: "base64"}) %>'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
ThreadExcerpt =
|
||||
init: ->
|
||||
return if g.VIEW isnt 'thread' or !Conf['Thread Excerpt']
|
||||
return if (g.BOARD.ID isnt 'f' and g.BOARD.ID isnt 'pol') or g.VIEW isnt 'thread' or !Conf['Thread Excerpt'] or Conf['Remove Thread Excerpt']
|
||||
|
||||
Thread.callbacks.push
|
||||
name: 'Thread Excerpt'
|
||||
|
||||
@ -21,16 +21,18 @@ ThreadStats =
|
||||
id: 'thread-stats'
|
||||
title: title
|
||||
Header.addShortcut sc
|
||||
|
||||
else
|
||||
@dialog = sc = UI.dialog 'thread-stats', 'bottom: 0px; right: 0px;',
|
||||
"<div class=move title='#{title}'>#{html}</div>"
|
||||
<%= html('<div class="move" title="${statsTitle}">&{statsHTML}</div>') %>
|
||||
$.addClass doc, 'float'
|
||||
$.ready ->
|
||||
$.add d.body, sc
|
||||
|
||||
@postCountEl = $ '#post-count', sc
|
||||
@ipCountEl = $ '#ip-count', sc
|
||||
@fileCountEl = $ '#file-count', sc
|
||||
@pageCountEl = $ '#page-count', sc
|
||||
@postCountEl = $ '#post-count', sc
|
||||
@ipCountEl = $ '#ip-count', sc
|
||||
@fileCountEl = $ '#file-count', sc
|
||||
@pageCountEl = $ '#page-count', sc
|
||||
|
||||
Thread.callbacks.push
|
||||
name: 'Thread Stats'
|
||||
@ -42,6 +44,7 @@ ThreadStats =
|
||||
@posts.forEach (post) ->
|
||||
postCount++
|
||||
fileCount++ if post.file
|
||||
ThreadStats.lastPost = post.info.date if Conf["Page Count in Stats"]
|
||||
ThreadStats.thread = @
|
||||
ThreadStats.fetchPage()
|
||||
ThreadStats.update postCount, fileCount, @ipCount
|
||||
@ -88,6 +91,7 @@ ThreadStats =
|
||||
|
||||
fetchPage: ->
|
||||
return if !Conf["Page Count in Stats"]
|
||||
clearTimeout ThreadStats.timeout
|
||||
if ThreadStats.thread.isDead
|
||||
ThreadStats.pageCountEl.textContent = 'Dead'
|
||||
$.addClass ThreadStats.pageCountEl, 'warning'
|
||||
@ -102,4 +106,6 @@ ThreadStats =
|
||||
for thread in page.threads when thread.no is ThreadStats.thread.ID
|
||||
ThreadStats.pageCountEl.textContent = page.page
|
||||
(if page.page is @response.length then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
|
||||
# Thread data may be stale (modification date given < time of last post). If so, try again on next thread update.
|
||||
ThreadStats.lastPageUpdate = new Date thread.last_modified * $.SECOND
|
||||
return
|
||||
|
||||
@ -4,14 +4,13 @@ ThreadUpdater =
|
||||
|
||||
if Conf['Updater and Stats in Header']
|
||||
@dialog = sc = $.el 'span',
|
||||
innerHTML: "[<span id=update-status></span><span id=update-timer title='Update now'></span>]\u00A0"
|
||||
id: 'updater'
|
||||
$.extend sc, <%= html('[<span id="update-status"></span><span id="update-timer" title="Update now"></span>]') %>
|
||||
$.ready ->
|
||||
Header.addShortcut sc
|
||||
else
|
||||
@dialog = sc = UI.dialog 'updater', 'bottom: 0px; left: 0px;',
|
||||
"<div class=move><span id=update-status></span><span id=update-timer title='Update now'></span></div>"
|
||||
$.addClass doc, 'float'
|
||||
<%= html('<div class="move"></div><span id="update-status"></span><span id="update-timer" title="Update now"></span>') %>
|
||||
$.ready ->
|
||||
$.addClass doc, 'float'
|
||||
$.add d.body, sc
|
||||
@ -28,20 +27,18 @@ ThreadUpdater =
|
||||
subEntries = []
|
||||
for name, conf of Config.updater.checkbox
|
||||
checked = if Conf[name] then 'checked' else ''
|
||||
el = $.el 'label',
|
||||
title: "#{conf[1]}"
|
||||
innerHTML: "<input name='#{name}' type=checkbox #{checked}> #{name}"
|
||||
el = UI.checkbox name, " #{name}"
|
||||
input = el.firstElementChild
|
||||
$.on input, 'change', $.cb.checked
|
||||
if input.name is 'Scroll BG'
|
||||
$.on input, 'change', @cb.scrollBG
|
||||
@cb.scrollBG()
|
||||
else if input.name is 'Auto Update'
|
||||
$.on input, 'change', @cb.update
|
||||
$.on input, 'change', @cb.autoUpdate
|
||||
subEntries.push el: el
|
||||
|
||||
@settings = $.el 'span',
|
||||
innerHTML: '<a href=javascript:;>Interval</a>'
|
||||
<%= html('<a href="javascript:;">Interval</a>') %>
|
||||
|
||||
$.on @settings, 'click', @intervalShortcut
|
||||
|
||||
@ -92,9 +89,10 @@ ThreadUpdater =
|
||||
Thread.callbacks.disconnect 'Thread Updater'
|
||||
|
||||
node: ->
|
||||
ThreadUpdater.thread = @
|
||||
ThreadUpdater.root = @OP.nodes.root.parentNode
|
||||
ThreadUpdater.lastPost = +@posts.keys[@posts.keys.length - 1]
|
||||
ThreadUpdater.thread = @
|
||||
ThreadUpdater.root = @OP.nodes.root.parentNode
|
||||
ThreadUpdater.lastPost = +@posts.keys[@posts.keys.length - 1]
|
||||
ThreadUpdater.outdateCount = 0
|
||||
|
||||
ThreadUpdater.cb.interval.call $.el 'input',
|
||||
value: Conf['Interval']
|
||||
@ -110,8 +108,6 @@ ThreadUpdater =
|
||||
ThreadUpdater.cb.online()
|
||||
|
||||
Rice.nodes ThreadUpdater.dialog
|
||||
|
||||
return
|
||||
|
||||
###
|
||||
http://freesound.org/people/pierrecartoons1979/sounds/90112/
|
||||
@ -121,24 +117,26 @@ ThreadUpdater =
|
||||
|
||||
cb:
|
||||
online: ->
|
||||
return if ThreadUpdater.thread.isDead
|
||||
if ThreadUpdater.online = navigator.onLine
|
||||
ThreadUpdater.outdateCount = 0
|
||||
ThreadUpdater.setInterval()
|
||||
ThreadUpdater.set 'status', null, null
|
||||
ThreadUpdater.set 'status', '', ''
|
||||
return
|
||||
ThreadUpdater.set 'timer', null
|
||||
ThreadUpdater.set 'timer', ''
|
||||
ThreadUpdater.set 'status', 'Offline', 'warning'
|
||||
clearTimeout ThreadUpdater.timeoutID
|
||||
post: (e) ->
|
||||
return unless ThreadUpdater.isUpdating and e.detail.threadID is ThreadUpdater.thread.ID
|
||||
ThreadUpdater.outdateCount = 0
|
||||
setTimeout ThreadUpdater.update, 1000 if ThreadUpdater.seconds > 2
|
||||
checkpost: (e) ->
|
||||
unless ThreadUpdater.checkPostCount
|
||||
return unless e.detail.threadID is ThreadUpdater.thread.ID
|
||||
return if e and e.detail.threadID isnt ThreadUpdater.thread.ID
|
||||
ThreadUpdater.seconds = 0
|
||||
ThreadUpdater.outdateCount = 0
|
||||
ThreadUpdater.set 'timer', '...'
|
||||
unless g.DEAD or ThreadUpdater.foundPost or ThreadUpdater.checkPostCount >= 5
|
||||
unless ThreadUpdater.thread.isDead or ThreadUpdater.foundPost or ThreadUpdater.checkPostCount >= 5
|
||||
return setTimeout ThreadUpdater.update, ++ThreadUpdater.checkPostCount * $.SECOND
|
||||
ThreadUpdater.setInterval()
|
||||
ThreadUpdater.checkPostCount = 0
|
||||
@ -155,6 +153,8 @@ ThreadUpdater =
|
||||
-> true
|
||||
else
|
||||
-> not d.hidden
|
||||
autoUpdate: (e) ->
|
||||
ThreadUpdater.count ThreadUpdater.isUpdating = @checked
|
||||
interval: (e) ->
|
||||
val = parseInt @value, 10
|
||||
if val < 1 then val = 1
|
||||
@ -164,7 +164,6 @@ ThreadUpdater =
|
||||
{req} = ThreadUpdater
|
||||
switch req.status
|
||||
when 200
|
||||
g.DEAD = false
|
||||
ThreadUpdater.parse req.response.posts
|
||||
if ThreadUpdater.thread.isArchived
|
||||
ThreadUpdater.set 'status', 'Archived', 'warning'
|
||||
@ -257,9 +256,10 @@ ThreadUpdater =
|
||||
timeout: ->
|
||||
ThreadUpdater.timeoutID = setTimeout ThreadUpdater.timeout, 1000
|
||||
unless n = --ThreadUpdater.seconds
|
||||
ThreadUpdater.outdateCount++
|
||||
ThreadUpdater.update()
|
||||
else if n <= -60
|
||||
ThreadUpdater.set 'status', 'Retrying', null
|
||||
ThreadUpdater.set 'status', 'Retrying', ''
|
||||
ThreadUpdater.update()
|
||||
else if n > 0
|
||||
ThreadUpdater.set 'timer', n
|
||||
@ -307,7 +307,7 @@ ThreadUpdater =
|
||||
ThreadUpdater.updateThreadStatus 'Closed', !!OP.closed
|
||||
ThreadUpdater.thread.postLimit = !!OP.bumplimit
|
||||
ThreadUpdater.thread.fileLimit = !!OP.imagelimit
|
||||
ThreadUpdater.thread.ipCount = OP.unique_ips if OP.unique_ips?
|
||||
ThreadUpdater.thread.ipCount = OP.unique_ips if OP.unique_ips?
|
||||
|
||||
posts = [] # post objects
|
||||
index = [] # existing posts
|
||||
@ -331,18 +331,28 @@ ThreadUpdater =
|
||||
# continue if post.isDead
|
||||
ID = +post.ID
|
||||
|
||||
unless ID in index
|
||||
post.kill()
|
||||
else if post.isDead
|
||||
post.resurrect()
|
||||
else if post.file and !post.file.isDead and ID not in files
|
||||
post.kill true
|
||||
# Assume deleted posts less than 60 seconds old are false positives.
|
||||
unless post.info.date > Date.now() - 60 * $.SECOND
|
||||
unless ID in index
|
||||
post.kill()
|
||||
else if post.isDead
|
||||
post.resurrect()
|
||||
else if post.file and not (post.file.isDead or ID in files)
|
||||
post.kill true
|
||||
|
||||
# Fetching your own posts after posting
|
||||
if ThreadUpdater.postID and ThreadUpdater.postID is ID
|
||||
ThreadUpdater.foundPost = true
|
||||
|
||||
sendEvent = ->
|
||||
# Update IP count in original post form.
|
||||
if OP.unique_ips? and ipCountEl = $.id('unique-ips')
|
||||
ipCountEl.textContent = OP.unique_ips
|
||||
ipCountEl.previousSibling.textContent = ipCountEl.previousSibling.textContent.replace(/\b(?:is|are)\b/, if OP.unique_ips is 1 then 'is' else 'are')
|
||||
ipCountEl.nextSibling.textContent = ipCountEl.nextSibling.textContent.replace(/\bposters?\b/, if OP.unique_ips is 1 then 'poster' else 'posters')
|
||||
|
||||
ThreadUpdater.postIDs = index
|
||||
|
||||
$.event 'ThreadUpdate',
|
||||
404: false
|
||||
threadID: ThreadUpdater.thread.fullID
|
||||
@ -352,7 +362,7 @@ ThreadUpdater =
|
||||
ipCount: OP.unique_ips
|
||||
|
||||
unless count
|
||||
ThreadUpdater.set 'status', null, null
|
||||
ThreadUpdater.set 'status', '', ''
|
||||
ThreadUpdater.outdateCount++
|
||||
sendEvent()
|
||||
return
|
||||
|
||||
@ -3,17 +3,18 @@ ThreadWatcher =
|
||||
return if !Conf['Thread Watcher']
|
||||
|
||||
@db = new DataBoard 'watchedThreads', @refresh, true
|
||||
@dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', """<%=
|
||||
grunt.file.read('src/General/html/Monitoring/ThreadWatcher.html').replace(/>\s+</g, '><').trim()
|
||||
%>"""
|
||||
@dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', <%= importHTML('Monitoring/ThreadWatcher') %>
|
||||
|
||||
@status = $ '#watcher-status', @dialog
|
||||
@list = @dialog.lastElementChild
|
||||
@refreshButton = $ '.refresh', @dialog
|
||||
|
||||
$.on d, 'QRPostSuccessful', @cb.post
|
||||
$.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread'
|
||||
$.on @refreshButton, 'click', @fetchAllStatus
|
||||
|
||||
$.on d, '4chanXInitFinished', @ready
|
||||
|
||||
switch g.VIEW
|
||||
when 'index'
|
||||
$.on d, 'IndexRefresh', @cb.onIndexRefresh
|
||||
@ -26,15 +27,50 @@ ThreadWatcher =
|
||||
ThreadWatcher.fetchAllStatus()
|
||||
@db.save()
|
||||
|
||||
Thread.callbacks.push
|
||||
if Conf['JSON Navigation'] and Conf['Menu'] and g.BOARD.ID isnt 'f'
|
||||
Menu.menu.addEntry
|
||||
el: $.el 'a', href: 'javascript:;'
|
||||
order: 6
|
||||
open: ({thread}) ->
|
||||
return false unless Conf['Index Mode'] is 'catalog' and g.VIEW is 'index'
|
||||
@el.textContent = if ThreadWatcher.isWatched thread
|
||||
'Unwatch thread'
|
||||
else
|
||||
'Watch thread'
|
||||
$.off @el, 'click', @cb if @cb
|
||||
@cb = ->
|
||||
$.event 'CloseMenu'
|
||||
ThreadWatcher.toggle thread
|
||||
$.on @el, 'click', @cb
|
||||
true
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Thread Watcher'
|
||||
cb: @node
|
||||
|
||||
CatalogThread.callbacks.push
|
||||
name: 'Thread Watcher'
|
||||
cb: @catalogNode
|
||||
|
||||
isWatched: (thread) ->
|
||||
ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID}
|
||||
|
||||
node: ->
|
||||
toggler = $.el 'img',
|
||||
className: 'watch-thread-link'
|
||||
return if @isReply
|
||||
if @isClone
|
||||
toggler = $ '.watch-thread-link', @nodes.post
|
||||
else
|
||||
toggler = $.el 'img',
|
||||
className: 'watch-thread-link'
|
||||
$.before $('input', @nodes.post), toggler
|
||||
$.on toggler, 'click', ThreadWatcher.cb.toggle
|
||||
$.before $('input', @OP.nodes.post), toggler
|
||||
|
||||
catalogNode: ->
|
||||
$.addClass @nodes.root, 'watched' if ThreadWatcher.isWatched @thread
|
||||
$.on @nodes.thumb.parentNode, 'click', (e) =>
|
||||
return unless e.button is 0 and e.altKey
|
||||
ThreadWatcher.toggle @thread
|
||||
e.preventDefault()
|
||||
|
||||
ready: ->
|
||||
$.off d, '4chanXInitFinished', ThreadWatcher.ready
|
||||
@ -63,9 +99,6 @@ ThreadWatcher =
|
||||
for a in $$ 'a[title]', ThreadWatcher.list
|
||||
$.open a.href
|
||||
$.event 'CloseMenu'
|
||||
checkThreads: ->
|
||||
return if $.hasClass @, 'disabled'
|
||||
ThreadWatcher.fetchAllStatus()
|
||||
pruneDeads: ->
|
||||
return if $.hasClass @, 'disabled'
|
||||
for {boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead
|
||||
@ -76,6 +109,9 @@ ThreadWatcher =
|
||||
$.event 'CloseMenu'
|
||||
toggle: ->
|
||||
ThreadWatcher.toggle Get.threadFromNode @
|
||||
Index.followedThreadID = thread.ID
|
||||
ThreadWatcher.toggle thread
|
||||
delete Index.followedThreadID
|
||||
rm: ->
|
||||
[boardID, threadID] = @parentNode.dataset.fullID.split '.'
|
||||
ThreadWatcher.rm boardID, +threadID
|
||||
@ -87,8 +123,10 @@ ThreadWatcher =
|
||||
else if Conf['Auto Watch Reply']
|
||||
ThreadWatcher.add g.threads[boardID + '.' + threadID]
|
||||
onIndexRefresh: ->
|
||||
{db} = ThreadWatcher
|
||||
boardID = g.BOARD.ID
|
||||
for threadID, data of ThreadWatcher.db.data.boards[boardID] when not data.isDead and threadID not of g.BOARD.threads
|
||||
db.forceSync()
|
||||
for threadID, data of db.data.boards[boardID] when not data.isDead and threadID not of g.BOARD.threads
|
||||
if Conf['Auto Prune']
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
else
|
||||
@ -98,21 +136,26 @@ ThreadWatcher =
|
||||
onThreadRefresh: (e) ->
|
||||
thread = g.threads[e.detail.threadID]
|
||||
return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID}
|
||||
# Update 404 status.
|
||||
# Update dead status.
|
||||
ThreadWatcher.add thread
|
||||
|
||||
fetchCount:
|
||||
fetched: 0
|
||||
fetching: 0
|
||||
fetchAllStatus: ->
|
||||
ThreadWatcher.db.forceSync()
|
||||
ThreadWatcher.unreaddb.forceSync()
|
||||
QR.db.forceSync()
|
||||
return unless (threads = ThreadWatcher.getAll()).length
|
||||
ThreadWatcher.status.textContent = '...'
|
||||
for thread in threads
|
||||
ThreadWatcher.fetchStatus thread
|
||||
return
|
||||
fetchStatus: ({boardID, threadID, data}) ->
|
||||
return if data.isDead
|
||||
return if data.isDead and !Conf['Show Unread Count']
|
||||
{fetchCount} = ThreadWatcher
|
||||
if fetchCount.fetching is 0
|
||||
ThreadWatcher.status.textContent = '...'
|
||||
$.addClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||
fetchCount.fetching++
|
||||
$.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
|
||||
onloadend: ->
|
||||
@ -121,18 +164,56 @@ ThreadWatcher =
|
||||
fetchCount.fetched = 0
|
||||
fetchCount.fetching = 0
|
||||
status = ''
|
||||
$.rmClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||
else
|
||||
status = "#{Math.round fetchCount.fetched / fetchCount.fetching * 100}%"
|
||||
ThreadWatcher.status.textContent = status
|
||||
return if @status isnt 404
|
||||
if Conf['Auto Prune']
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
else
|
||||
data.isDead = true
|
||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||
ThreadWatcher.refresh()
|
||||
,
|
||||
type: 'head'
|
||||
|
||||
if @status is 200 and @response
|
||||
isDead = !!@response.posts[0].archived
|
||||
if isDead and Conf['Auto Prune']
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
ThreadWatcher.refresh()
|
||||
return
|
||||
|
||||
lastReadPost = ThreadWatcher.unreaddb.get
|
||||
boardID: boardID
|
||||
threadID: threadID
|
||||
defaultValue: 0
|
||||
|
||||
unread = quotingYou = 0
|
||||
|
||||
for postObj in @response.posts
|
||||
continue unless postObj.no > lastReadPost
|
||||
continue if QR.db?.get {boardID, threadID, postID: postObj.no}
|
||||
unread++
|
||||
continue unless QR.db and postObj.com
|
||||
regexp = /<a [^>]*\bhref="(?:\/([^\/]+)\/thread\/(\d+))?(?:#p(\d+))?"/g
|
||||
while match = regexp.exec postObj.com
|
||||
if QR.db.get {
|
||||
boardID: match[1] or boardID
|
||||
threadID: match[2] or threadID
|
||||
postID: match[3] or match[2] or threadID
|
||||
}
|
||||
quotingYou++
|
||||
continue
|
||||
|
||||
if isDead isnt data.isDead or unread isnt data.unread or quotingYou isnt data.quotingYou
|
||||
data.isDead = isDead
|
||||
data.unread = unread
|
||||
data.quotingYou = quotingYou
|
||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||
ThreadWatcher.refresh()
|
||||
|
||||
else if @status is 404
|
||||
if Conf['Auto Prune']
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
else
|
||||
data.isDead = true
|
||||
delete data.unread
|
||||
delete data.quotingYou
|
||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||
ThreadWatcher.refresh()
|
||||
|
||||
getAll: ->
|
||||
all = []
|
||||
@ -150,18 +231,31 @@ ThreadWatcher =
|
||||
textContent: '\uf00d'
|
||||
$.on x, 'click', ThreadWatcher.cb.rm
|
||||
|
||||
if data.isDead
|
||||
href = Redirect.to 'thread', {boardID, threadID}
|
||||
link = $.el 'a',
|
||||
href: href or "/#{boardID}/thread/#{threadID}"
|
||||
href: "/#{boardID}/thread/#{threadID}"
|
||||
textContent: data.excerpt
|
||||
title: data.excerpt
|
||||
className: 'watcher-link'
|
||||
|
||||
if Conf['Show Unread Count'] and data.unread?
|
||||
count = $.el 'span',
|
||||
textContent: "(#{data.unread})"
|
||||
className: 'watcher-unread'
|
||||
$.add link, count
|
||||
|
||||
title = $.el 'span',
|
||||
textContent: data.excerpt
|
||||
className: 'watcher-title'
|
||||
$.add link, title
|
||||
|
||||
div = $.el 'div'
|
||||
fullID = "#{boardID}.#{threadID}"
|
||||
div.dataset.fullID = fullID
|
||||
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
||||
$.addClass div, 'dead-thread' if data.isDead
|
||||
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
||||
$.addClass div, 'dead-thread' if data.isDead
|
||||
if Conf['Show Unread Count']
|
||||
$.addClass div, 'replies-unread' if data.unread
|
||||
$.addClass div, 'replies-quoting-you' if data.quotingYou
|
||||
$.add div, [x, $.tn(' '), link]
|
||||
div
|
||||
refresh: ->
|
||||
@ -173,18 +267,40 @@ ThreadWatcher =
|
||||
$.rmAll list
|
||||
$.add list, nodes
|
||||
|
||||
{threads} = g.BOARD
|
||||
for threadID in threads.keys
|
||||
thread = threads[threadID]
|
||||
toggler = $ '.watch-thread-link', thread.OP.nodes.post
|
||||
watched = ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
|
||||
helper = if watched then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
|
||||
$[helper[0]] toggler, 'watched'
|
||||
toggler.title = "#{helper[1]} Thread"
|
||||
g.threads.forEach (thread) ->
|
||||
helper = if ThreadWatcher.isWatched thread then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
|
||||
if thread.OP
|
||||
for post in [thread.OP, thread.OP.clones...]
|
||||
toggler = $ '.watch-thread-link', post.nodes.post
|
||||
$[helper[0]] toggler, 'watched'
|
||||
toggler.title = "#{helper[1]} Thread"
|
||||
$[helper[0]] thread.catalogView.nodes.root, 'watched' if thread.catalogView
|
||||
|
||||
for refresher in ThreadWatcher.menu.refreshers
|
||||
refresher()
|
||||
return
|
||||
|
||||
if Index.nodes and Conf['Pin Watched Threads']
|
||||
Index.sort()
|
||||
Index.buildIndex()
|
||||
|
||||
update: (boardID, threadID, newData) ->
|
||||
return unless data = ThreadWatcher.db?.get {boardID, threadID}
|
||||
if newData.isDead and Conf['Auto Prune']
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
ThreadWatcher.refresh()
|
||||
return
|
||||
n = 0
|
||||
n++ for key, val of newData when data[key] isnt val
|
||||
return unless n
|
||||
ThreadWatcher.db.forceSync()
|
||||
return unless data = ThreadWatcher.db.get {boardID, threadID}
|
||||
$.extend data, newData
|
||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||
if line = $ "#watched-threads > [data-full-i-d='#{boardID}.#{threadID}']", ThreadWatcher.dialog
|
||||
newLine = ThreadWatcher.makeLine boardID, threadID, data
|
||||
$.replace line, newLine
|
||||
else
|
||||
ThreadWatcher.refresh()
|
||||
|
||||
toggle: (thread) ->
|
||||
boardID = thread.board.ID
|
||||
@ -205,6 +321,8 @@ ThreadWatcher =
|
||||
data.excerpt = Get.threadExcerpt thread
|
||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||
ThreadWatcher.refresh()
|
||||
if Conf['Show Unread Count']
|
||||
ThreadWatcher.fetchStatus {boardID, threadID, data}
|
||||
rm: (boardID, threadID) ->
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
ThreadWatcher.refresh()
|
||||
@ -220,11 +338,11 @@ ThreadWatcher =
|
||||
refreshers: []
|
||||
init: ->
|
||||
return if !Conf['Thread Watcher']
|
||||
menu = new UI.Menu()
|
||||
menu = @menu = new UI.Menu 'thread watcher'
|
||||
$.on $('.menu-button', ThreadWatcher.dialog), 'click', (e) ->
|
||||
menu.toggle e, @, ThreadWatcher
|
||||
@addHeaderMenuEntry()
|
||||
@addMenuEntries menu
|
||||
@addMenuEntries
|
||||
|
||||
addHeaderMenuEntry: ->
|
||||
return if g.VIEW isnt 'thread'
|
||||
@ -243,7 +361,7 @@ ThreadWatcher =
|
||||
$.rmClass entryEl, rmClass
|
||||
entryEl.textContent = text
|
||||
|
||||
addMenuEntries: (menu) ->
|
||||
addMenuEntries: ->
|
||||
entries = []
|
||||
|
||||
# `Open all` entry
|
||||
@ -254,20 +372,12 @@ ThreadWatcher =
|
||||
textContent: 'Open all threads'
|
||||
refresh: -> (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled'
|
||||
|
||||
# `Check 404'd threads` entry
|
||||
entries.push
|
||||
cb: ThreadWatcher.cb.checkThreads
|
||||
entry:
|
||||
el: $.el 'a',
|
||||
textContent: 'Check 404\'d threads'
|
||||
refresh: -> (if $('div:not(.dead-thread)', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
|
||||
|
||||
# `Prune 404'd threads` entry
|
||||
# `Prune dead threads` entry
|
||||
entries.push
|
||||
cb: ThreadWatcher.cb.pruneDeads
|
||||
entry:
|
||||
el: $.el 'a',
|
||||
textContent: 'Prune 404\'d threads'
|
||||
textContent: 'Prune dead threads'
|
||||
refresh: -> (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
|
||||
|
||||
# `Settings` entries:
|
||||
@ -284,16 +394,14 @@ ThreadWatcher =
|
||||
entry.el.href = 'javascript:;' if entry.el.nodeName is 'A'
|
||||
$.on entry.el, 'click', cb if cb
|
||||
@refreshers.push refresh.bind entry if refresh
|
||||
menu.addEntry entry
|
||||
@menu.addEntry entry
|
||||
return
|
||||
|
||||
createSubEntry: (name, desc) ->
|
||||
entry =
|
||||
type: 'thread watcher'
|
||||
el: $.el 'label',
|
||||
innerHTML: "<input type=checkbox name='#{name}'> #{name}"
|
||||
title: desc
|
||||
el: UI.checkbox name, " #{name}"
|
||||
input = entry.el.firstElementChild
|
||||
input.checked = Conf[name]
|
||||
$.on input, 'change', $.cb.checked
|
||||
$.on input, 'change', ThreadWatcher.refresh if name is 'Current Board'
|
||||
$.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count']
|
||||
entry
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user