Merge ccd0's IP Count stuff

This commit is contained in:
Zixaphir 2014-12-09 00:28:38 -07:00
parent 0389bcf930
commit 020a68f73a
10 changed files with 570 additions and 156 deletions

View File

@ -1,5 +1,5 @@
/*
* appchan x - Version 2.9.38 - 2014-12-08
* appchan x - Version 2.9.38 - 2014-12-09
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -28,7 +28,7 @@
// ==/UserScript==
/*
* appchan x - Version 2.9.38 - 2014-12-08
* appchan x - Version 2.9.38 - 2014-12-09
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -116,7 +116,7 @@
'use strict';
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, 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, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, 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, TrashQueue, UI, Unread, Video, 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,
@ -212,9 +212,11 @@
'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.'],
'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'],
'Thread Stats': [true, 'Display reply and image count.'],
'Page Count in Stats': [false, 'Display the page count in the thread stats as well.'],
'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.'],
'Page Count in Stats': [true, 'Display the page count in the thread stats.'],
'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'],
'Thread Watcher': [true, 'Bookmark threads.']
'Thread Watcher': [true, 'Bookmark threads.'],
'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.']
},
'Posting': {
'Header Shortcut': [true, 'Add a shortcut to the header to toggle the QR.'],
@ -228,7 +230,7 @@
'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'],
'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'],
'Dump List Before Comment': [false, 'Position of the QR\'s Dump List.'],
'Auto-load captcha': [false, 'Automatically load the captcha when you open a thread, and reload it after you post.']
'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.']
},
'Quote Links': {
'Quote Backlinks': [true, 'Add quote backlinks.'],
@ -3163,6 +3165,7 @@
this.isClosed = false;
this.postLimit = false;
this.fileLimit = false;
this.ipCount = void 0;
this.OP = null;
this.catalogView = null;
g.threads.push(this.fullID, board.threads.push(this, this));
@ -3192,7 +3195,7 @@
};
Thread.prototype.setStatus = function(type, status) {
var icon, name, root, typeLC;
var name, typeLC;
name = "is" + type;
if (this[name] === status) {
return;
@ -3202,8 +3205,21 @@
return;
}
typeLC = type.toLowerCase();
this.setIcon('Sticky', this.isSticky);
this.setIcon('Closed', this.isClosed && !this.isArchived);
return this.setIcon('Archived', this.isArchived);
};
Thread.prototype.setIcon = function(type, status) {
var icon, root, typeLC;
typeLC = type.toLowerCase();
icon = $("." + typeLC + "Icon", this.OP.nodes.info);
if (!!icon === status) {
return;
}
if (!status) {
$.rm($("." + typeLC + "Icon", this.OP.nodes.info));
$.rm(icon.previousSibling);
$.rm(icon);
if (this.catalogView) {
$.rm($("." + typeLC + "Icon", this.catalogView.nodes.icons));
}
@ -3211,10 +3227,11 @@
}
icon = $.el('img', {
src: "" + Build.staticPath + typeLC + Build.gifIcon,
alt: type,
title: type,
className: "" + typeLC + "Icon"
className: "" + typeLC + "Icon retina"
});
root = type === 'Closed' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : g.VIEW === 'index' ? $('.page-num', this.OP.nodes.info) : $('[title="Reply to this post"]', this.OP.nodes.info);
root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || $('[title="Reply to this post"]', this.OP.nodes.info);
$.after(root, [$.tn(' '), icon]);
if (!this.catalogView) {
return;
@ -9707,29 +9724,23 @@
}
},
save: function(e) {
var err, _base;
try {
if (this.needed()) {
this.shouldFocus = true;
this.reload();
} else {
this.nodes.counter.focus();
if ((_base = this.timeouts).destroy == null) {
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
}
var _base;
if (this.needed()) {
this.shouldFocus = true;
this.reload();
} else {
this.nodes.counter.focus();
if ((_base = this.timeouts).destroy == null) {
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
}
console.log(e.detail);
$.forceSync('captchas');
this.captchas.push({
response: e.detail,
timeout: Date.now() + 2 * $.MINUTE
});
this.count();
return $.set('captchas', this.captchas);
} catch (_error) {
err = _error;
return console.log(err);
}
$.forceSync('captchas');
this.captchas.push({
response: e.detail,
timeout: Date.now() + 2 * $.MINUTE
});
this.count();
return $.set('captchas', this.captchas);
},
clear: function() {
var captcha, i, now, _i, _len, _ref;
@ -11876,6 +11887,84 @@
logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAJtJREFUeF7t17ENgDAMBdGswAqswAqskP1XgUhG37JQQv25a1I4eR1CbopoS3295wIASD1SBZnecwEApO9RRurjPdLcAQDQY9UnBSDEBQAIRA3gfGkA9cfiAwDUHqCnHqDl/AGA0v8AAL4FgO0uAxUcc2cAQEtFjwQoLSMuAMBqsVwtpp4AwNDngPIGAPI5mVsCAELSuZybAQBEF/bX4X+FReD/AAAAAElFTkSuQmCC'
};
MarkNewIPs = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) {
return;
}
return Thread.callbacks.push({
name: 'Mark New IPs',
cb: this.node
});
},
node: function() {
MarkNewIPs.ipCount = this.ipCount;
MarkNewIPs.postIDs = this.posts.keys.map(function(x) {
return +x;
});
return $.on(d, 'ThreadUpdate', MarkNewIPs.onUpdate);
},
onUpdate: function(e) {
var added, fullID, i, ipCount, newPosts, obj, postIDs, removed, x, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1;
_ref = e.detail, ipCount = _ref.ipCount, newPosts = _ref.newPosts;
postIDs = ThreadUpdater.postIDs;
if (ipCount == null) {
return;
}
if (newPosts.length) {
obj = {};
_ref1 = MarkNewIPs.postIDs;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
x = _ref1[_i];
obj[x] = true;
}
added = 0;
for (_j = 0, _len1 = postIDs.length; _j < _len1; _j++) {
x = postIDs[_j];
if (!(x in obj)) {
added++;
}
}
removed = MarkNewIPs.postIDs.length + added - postIDs.length;
switch (ipCount - MarkNewIPs.ipCount) {
case added:
i = MarkNewIPs.ipCount;
for (_k = 0, _len2 = newPosts.length; _k < _len2; _k++) {
fullID = newPosts[_k];
MarkNewIPs.markNew(g.posts[fullID], ++i);
}
break;
case -removed:
for (_l = 0, _len3 = newPosts.length; _l < _len3; _l++) {
fullID = newPosts[_l];
MarkNewIPs.markOld(g.posts[fullID]);
}
}
}
MarkNewIPs.ipCount = ipCount;
return MarkNewIPs.postIDs = postIDs;
},
markNew: function(post, ipCount) {
var counter, suffix;
suffix = {
1: 'st',
2: 'nd',
3: 'rd'
}[ipCount % 10] || Math.floor('th' / fuck(switches));
counter = $.el('span', {
className: 'ip-counter',
textContent: "(" + ipCount + ")"
});
post.nodes.nameBlock.title = "This is the " + ipCount + suffix + " IP in the thread.";
$.add(post.nodes.nameBlock, [$.tn(' '), counter]);
return $.addClass(post.nodes.root, 'new-ip');
},
markOld: function(post) {
post.nodes.nameBlock.title = 'Not the first post from this IP.';
return $.addClass(post.nodes.root, 'old-ip');
}
};
ThreadExcerpt = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
@ -11905,9 +11994,9 @@
}
if (Conf['Updater and Stats in Header']) {
this.dialog = sc = $.el('span', {
innerHTML: "[<span id=post-count>0</span> / <span id=file-count>0</span>" + (Conf["Page Count in Stats"] ? " / <span id=page-count>0</span>" : "") + "]",
innerHTML: "[<span id=post-count>0</span> / \n<span id=file-count>0</span>\n" + (Conf['IP Count in Stats'] ? ' / <span id=ip-count>?</span>' : "") + "\n" + (Conf['Page Count in Stats'] ? ' / <span id=page-count>0</span>' : "") + "]",
id: 'thread-stats',
title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "")
title: 'Post Count / File Count' + (Conf['IP Count in Stats'] ? " / IPs" : "") + (Conf['Page Count in Stats'] ? " / Page Count" : "")
});
$.ready(function() {
return Header.addShortcut(sc);
@ -11921,6 +12010,7 @@
})(this));
}
this.postCountEl = $('#post-count', sc);
this.ipCountEl = $('#ip-count', sc);
this.fileCountEl = $('#file-count', sc);
this.pageCountEl = $('#page-count', sc);
return Thread.callbacks.push({
@ -11940,7 +12030,7 @@
});
ThreadStats.thread = this;
ThreadStats.fetchPage();
ThreadStats.update(postCount, fileCount);
ThreadStats.update(postCount, fileCount, this.ipCount);
return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate);
},
disconnect: function() {
@ -11963,18 +12053,30 @@
return $.off(d, 'ThreadUpdate', ThreadStats.onUpdate);
},
onUpdate: function(e) {
var fileCount, postCount, _ref;
var fileCount, ipCount, newPosts, postCount, _ref, _ref1;
if (e.detail[404]) {
return;
}
_ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount;
return ThreadStats.update(postCount, fileCount);
_ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount, ipCount = _ref.ipCount, newPosts = _ref.newPosts;
ThreadStats.update(postCount, fileCount, ipCount);
if (!Conf["Page Count in Stats"]) {
return;
}
if (newPosts.length) {
ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date;
}
if (ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((_ref1 = ThreadStats.pageCountEl) != null ? _ref1.textContent : void 0) !== '1') {
return ThreadStats.fetchPage();
}
},
update: function(postCount, fileCount) {
var fileCountEl, postCountEl, thread;
thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl;
update: function(postCount, fileCount, ipCount) {
var fileCountEl, ipCountEl, postCountEl, thread;
thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl;
postCountEl.textContent = postCount;
fileCountEl.textContent = fileCount;
if ((ipCount != null) && Conf["IP Count in Stats"]) {
ipCountEl.textContent = ipCount;
}
(thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning');
return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning');
},
@ -12134,8 +12236,12 @@
$.on(window, 'online offline', ThreadUpdater.cb.online);
$.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost);
$.on(d, 'visibilitychange', ThreadUpdater.cb.visibility);
ThreadUpdater.cb.online();
return Rice.nodes(ThreadUpdater.dialog);
if (ThreadUpdater.thread.isArchived) {
ThreadUpdater.set('status', 'Archived', 'warning');
} else {
ThreadUpdater.cb.online();
}
Rice.nodes(ThreadUpdater.dialog);
},
/*
@ -12208,36 +12314,72 @@
}
},
load: function(e) {
var klass, req, text, _ref;
var req;
req = ThreadUpdater.req;
switch (req.status) {
case 200:
g.DEAD = false;
ThreadUpdater.parse(req.response.posts);
ThreadUpdater.setInterval();
if (ThreadUpdater.thread.isArchived) {
ThreadUpdater.set('status', 'Archived', 'warning');
ThreadUpdater.kill();
} else {
ThreadUpdater.setInterval();
}
break;
case 404:
g.DEAD = true;
ThreadUpdater.set('timer', null);
ThreadUpdater.set('status', '404', 'warning');
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.thread.kill();
$.event('ThreadUpdate', {
404: true,
threadID: ThreadUpdater.thread.fullID
$.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", {
onloadend: function() {
var confirmed, page, thread, _i, _j, _len, _len1, _ref, _ref1;
if (this.status === 200) {
confirmed = true;
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no === ThreadUpdater.thread.ID) {
confirmed = false;
break;
}
}
}
} else {
confirmed = false;
}
if (confirmed) {
ThreadUpdater.set('status', '404', 'warning');
return ThreadUpdater.kill();
} else {
return ThreadUpdater.error(req);
}
}
});
break;
default:
ThreadUpdater.outdateCount++;
ThreadUpdater.setInterval();
_ref = req.status === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1];
ThreadUpdater.set('status', text, klass);
ThreadUpdater.error(req);
}
if (ThreadUpdater.postID) {
return ThreadUpdater.cb.checkpost();
}
}
},
kill: function() {
ThreadUpdater.set('timer', '');
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.thread.kill();
return $.event('ThreadUpdate', {
404: true,
threadID: ThreadUpdater.thread.fullID
});
},
error: function(req) {
var klass, text, _ref;
ThreadUpdater.setInterval();
_ref = req.status === 304 ? ['', ''] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1];
return ThreadUpdater.set('status', text, klass);
},
setInterval: function() {
var cur, i, j, limit;
i = ThreadUpdater.interval + 1;
@ -12319,6 +12461,9 @@
return;
}
ThreadUpdater.thread.setStatus(type, status);
if (type === 'Closed' && ThreadUpdater.thread.isArchived) {
return;
}
change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore';
return new Notice('info', "The thread is " + change + ".", 30);
},
@ -12326,10 +12471,14 @@
var OP, count, files, index, node, num, post, postObject, posts, root, scroll, sendEvent, _i, _j, _len, _len1;
OP = postObjects[0];
Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler;
ThreadUpdater.thread.setStatus('Archived', !!+OP.archived);
ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky);
ThreadUpdater.updateThreadStatus('Closed', !!OP.closed);
ThreadUpdater.thread.postLimit = !!OP.bumplimit;
ThreadUpdater.thread.fileLimit = !!OP.imagelimit;
if (OP.unique_ips != null) {
ThreadUpdater.thread.ipCount = OP.unique_ips;
}
posts = [];
index = [];
files = [];
@ -12370,7 +12519,8 @@
return post.fullID;
}),
postCount: OP.replies + 1,
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead)
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead),
ipCount: OP.unique_ips
});
};
if (!count) {
@ -17968,6 +18118,7 @@
init('Thread Updater', ThreadUpdater);
init('Thread Watcher', ThreadWatcher);
init('Thread Watcher (Menu)', ThreadWatcher.menu);
init('Mark New IPs', MarkNewIPs);
init('Index Navigation', Nav);
init('Keybinds', Keybinds);
init('Show Dice Roll', Dice);

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
* appchan x - Version 2.9.38 - 2014-12-08
* appchan x - Version 2.9.38 - 2014-12-09
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -88,7 +88,7 @@
'use strict';
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, 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, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, 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, TrashQueue, UI, Unread, Video, 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,
@ -184,9 +184,11 @@
'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.'],
'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'],
'Thread Stats': [true, 'Display reply and image count.'],
'Page Count in Stats': [false, 'Display the page count in the thread stats as well.'],
'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.'],
'Page Count in Stats': [true, 'Display the page count in the thread stats.'],
'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'],
'Thread Watcher': [true, 'Bookmark threads.']
'Thread Watcher': [true, 'Bookmark threads.'],
'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.']
},
'Posting': {
'Header Shortcut': [true, 'Add a shortcut to the header to toggle the QR.'],
@ -200,7 +202,7 @@
'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'],
'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'],
'Dump List Before Comment': [false, 'Position of the QR\'s Dump List.'],
'Auto-load captcha': [false, 'Automatically load the captcha when you open a thread, and reload it after you post.']
'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.']
},
'Quote Links': {
'Quote Backlinks': [true, 'Add quote backlinks.'],
@ -3188,6 +3190,7 @@
this.isClosed = false;
this.postLimit = false;
this.fileLimit = false;
this.ipCount = void 0;
this.OP = null;
this.catalogView = null;
g.threads.push(this.fullID, board.threads.push(this, this));
@ -3217,7 +3220,7 @@
};
Thread.prototype.setStatus = function(type, status) {
var icon, name, root, typeLC;
var name, typeLC;
name = "is" + type;
if (this[name] === status) {
return;
@ -3227,8 +3230,21 @@
return;
}
typeLC = type.toLowerCase();
this.setIcon('Sticky', this.isSticky);
this.setIcon('Closed', this.isClosed && !this.isArchived);
return this.setIcon('Archived', this.isArchived);
};
Thread.prototype.setIcon = function(type, status) {
var icon, root, typeLC;
typeLC = type.toLowerCase();
icon = $("." + typeLC + "Icon", this.OP.nodes.info);
if (!!icon === status) {
return;
}
if (!status) {
$.rm($("." + typeLC + "Icon", this.OP.nodes.info));
$.rm(icon.previousSibling);
$.rm(icon);
if (this.catalogView) {
$.rm($("." + typeLC + "Icon", this.catalogView.nodes.icons));
}
@ -3236,10 +3252,11 @@
}
icon = $.el('img', {
src: "" + Build.staticPath + typeLC + Build.gifIcon,
alt: type,
title: type,
className: "" + typeLC + "Icon"
className: "" + typeLC + "Icon retina"
});
root = type === 'Closed' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : g.VIEW === 'index' ? $('.page-num', this.OP.nodes.info) : $('[title="Reply to this post"]', this.OP.nodes.info);
root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || $('[title="Reply to this post"]', this.OP.nodes.info);
$.after(root, [$.tn(' '), icon]);
if (!this.catalogView) {
return;
@ -9721,29 +9738,23 @@
}
},
save: function(e) {
var err, _base;
try {
if (this.needed()) {
this.shouldFocus = true;
this.reload();
} else {
this.nodes.counter.focus();
if ((_base = this.timeouts).destroy == null) {
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
}
var _base;
if (this.needed()) {
this.shouldFocus = true;
this.reload();
} else {
this.nodes.counter.focus();
if ((_base = this.timeouts).destroy == null) {
_base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND);
}
console.log(e.detail);
$.forceSync('captchas');
this.captchas.push({
response: e.detail,
timeout: Date.now() + 2 * $.MINUTE
});
this.count();
return $.set('captchas', this.captchas);
} catch (_error) {
err = _error;
return console.log(err);
}
$.forceSync('captchas');
this.captchas.push({
response: e.detail,
timeout: Date.now() + 2 * $.MINUTE
});
this.count();
return $.set('captchas', this.captchas);
},
clear: function() {
var captcha, i, now, _i, _len, _ref;
@ -11859,6 +11870,84 @@
logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAJtJREFUeF7t17ENgDAMBdGswAqswAqskP1XgUhG37JQQv25a1I4eR1CbopoS3295wIASD1SBZnecwEApO9RRurjPdLcAQDQY9UnBSDEBQAIRA3gfGkA9cfiAwDUHqCnHqDl/AGA0v8AAL4FgO0uAxUcc2cAQEtFjwQoLSMuAMBqsVwtpp4AwNDngPIGAPI5mVsCAELSuZybAQBEF/bX4X+FReD/AAAAAElFTkSuQmCC'
};
MarkNewIPs = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) {
return;
}
return Thread.callbacks.push({
name: 'Mark New IPs',
cb: this.node
});
},
node: function() {
MarkNewIPs.ipCount = this.ipCount;
MarkNewIPs.postIDs = this.posts.keys.map(function(x) {
return +x;
});
return $.on(d, 'ThreadUpdate', MarkNewIPs.onUpdate);
},
onUpdate: function(e) {
var added, fullID, i, ipCount, newPosts, obj, postIDs, removed, x, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1;
_ref = e.detail, ipCount = _ref.ipCount, newPosts = _ref.newPosts;
postIDs = ThreadUpdater.postIDs;
if (ipCount == null) {
return;
}
if (newPosts.length) {
obj = {};
_ref1 = MarkNewIPs.postIDs;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
x = _ref1[_i];
obj[x] = true;
}
added = 0;
for (_j = 0, _len1 = postIDs.length; _j < _len1; _j++) {
x = postIDs[_j];
if (!(x in obj)) {
added++;
}
}
removed = MarkNewIPs.postIDs.length + added - postIDs.length;
switch (ipCount - MarkNewIPs.ipCount) {
case added:
i = MarkNewIPs.ipCount;
for (_k = 0, _len2 = newPosts.length; _k < _len2; _k++) {
fullID = newPosts[_k];
MarkNewIPs.markNew(g.posts[fullID], ++i);
}
break;
case -removed:
for (_l = 0, _len3 = newPosts.length; _l < _len3; _l++) {
fullID = newPosts[_l];
MarkNewIPs.markOld(g.posts[fullID]);
}
}
}
MarkNewIPs.ipCount = ipCount;
return MarkNewIPs.postIDs = postIDs;
},
markNew: function(post, ipCount) {
var counter, suffix;
suffix = {
1: 'st',
2: 'nd',
3: 'rd'
}[ipCount % 10] || Math.floor('th' / fuck(switches));
counter = $.el('span', {
className: 'ip-counter',
textContent: "(" + ipCount + ")"
});
post.nodes.nameBlock.title = "This is the " + ipCount + suffix + " IP in the thread.";
$.add(post.nodes.nameBlock, [$.tn(' '), counter]);
return $.addClass(post.nodes.root, 'new-ip');
},
markOld: function(post) {
post.nodes.nameBlock.title = 'Not the first post from this IP.';
return $.addClass(post.nodes.root, 'old-ip');
}
};
ThreadExcerpt = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
@ -11888,9 +11977,9 @@
}
if (Conf['Updater and Stats in Header']) {
this.dialog = sc = $.el('span', {
innerHTML: "[<span id=post-count>0</span> / <span id=file-count>0</span>" + (Conf["Page Count in Stats"] ? " / <span id=page-count>0</span>" : "") + "]",
innerHTML: "[<span id=post-count>0</span> / \n<span id=file-count>0</span>\n" + (Conf['IP Count in Stats'] ? ' / <span id=ip-count>?</span>' : "") + "\n" + (Conf['Page Count in Stats'] ? ' / <span id=page-count>0</span>' : "") + "]",
id: 'thread-stats',
title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "")
title: 'Post Count / File Count' + (Conf['IP Count in Stats'] ? " / IPs" : "") + (Conf['Page Count in Stats'] ? " / Page Count" : "")
});
$.ready(function() {
return Header.addShortcut(sc);
@ -11904,6 +11993,7 @@
})(this));
}
this.postCountEl = $('#post-count', sc);
this.ipCountEl = $('#ip-count', sc);
this.fileCountEl = $('#file-count', sc);
this.pageCountEl = $('#page-count', sc);
return Thread.callbacks.push({
@ -11923,7 +12013,7 @@
});
ThreadStats.thread = this;
ThreadStats.fetchPage();
ThreadStats.update(postCount, fileCount);
ThreadStats.update(postCount, fileCount, this.ipCount);
return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate);
},
disconnect: function() {
@ -11946,18 +12036,30 @@
return $.off(d, 'ThreadUpdate', ThreadStats.onUpdate);
},
onUpdate: function(e) {
var fileCount, postCount, _ref;
var fileCount, ipCount, newPosts, postCount, _ref, _ref1;
if (e.detail[404]) {
return;
}
_ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount;
return ThreadStats.update(postCount, fileCount);
_ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount, ipCount = _ref.ipCount, newPosts = _ref.newPosts;
ThreadStats.update(postCount, fileCount, ipCount);
if (!Conf["Page Count in Stats"]) {
return;
}
if (newPosts.length) {
ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date;
}
if (ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((_ref1 = ThreadStats.pageCountEl) != null ? _ref1.textContent : void 0) !== '1') {
return ThreadStats.fetchPage();
}
},
update: function(postCount, fileCount) {
var fileCountEl, postCountEl, thread;
thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl;
update: function(postCount, fileCount, ipCount) {
var fileCountEl, ipCountEl, postCountEl, thread;
thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl;
postCountEl.textContent = postCount;
fileCountEl.textContent = fileCount;
if ((ipCount != null) && Conf["IP Count in Stats"]) {
ipCountEl.textContent = ipCount;
}
(thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning');
return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning');
},
@ -12117,8 +12219,12 @@
$.on(window, 'online offline', ThreadUpdater.cb.online);
$.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost);
$.on(d, 'visibilitychange', ThreadUpdater.cb.visibility);
ThreadUpdater.cb.online();
return Rice.nodes(ThreadUpdater.dialog);
if (ThreadUpdater.thread.isArchived) {
ThreadUpdater.set('status', 'Archived', 'warning');
} else {
ThreadUpdater.cb.online();
}
Rice.nodes(ThreadUpdater.dialog);
},
/*
@ -12191,36 +12297,72 @@
}
},
load: function(e) {
var klass, req, text, _ref;
var req;
req = ThreadUpdater.req;
switch (req.status) {
case 200:
g.DEAD = false;
ThreadUpdater.parse(req.response.posts);
ThreadUpdater.setInterval();
if (ThreadUpdater.thread.isArchived) {
ThreadUpdater.set('status', 'Archived', 'warning');
ThreadUpdater.kill();
} else {
ThreadUpdater.setInterval();
}
break;
case 404:
g.DEAD = true;
ThreadUpdater.set('timer', null);
ThreadUpdater.set('status', '404', 'warning');
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.thread.kill();
$.event('ThreadUpdate', {
404: true,
threadID: ThreadUpdater.thread.fullID
$.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", {
onloadend: function() {
var confirmed, page, thread, _i, _j, _len, _len1, _ref, _ref1;
if (this.status === 200) {
confirmed = true;
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no === ThreadUpdater.thread.ID) {
confirmed = false;
break;
}
}
}
} else {
confirmed = false;
}
if (confirmed) {
ThreadUpdater.set('status', '404', 'warning');
return ThreadUpdater.kill();
} else {
return ThreadUpdater.error(req);
}
}
});
break;
default:
ThreadUpdater.outdateCount++;
ThreadUpdater.setInterval();
_ref = req.status === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1];
ThreadUpdater.set('status', text, klass);
ThreadUpdater.error(req);
}
if (ThreadUpdater.postID) {
return ThreadUpdater.cb.checkpost();
}
}
},
kill: function() {
ThreadUpdater.set('timer', '');
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.thread.kill();
return $.event('ThreadUpdate', {
404: true,
threadID: ThreadUpdater.thread.fullID
});
},
error: function(req) {
var klass, text, _ref;
ThreadUpdater.setInterval();
_ref = req.status === 304 ? ['', ''] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1];
return ThreadUpdater.set('status', text, klass);
},
setInterval: function() {
var cur, i, j, limit;
i = ThreadUpdater.interval + 1;
@ -12302,6 +12444,9 @@
return;
}
ThreadUpdater.thread.setStatus(type, status);
if (type === 'Closed' && ThreadUpdater.thread.isArchived) {
return;
}
change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore';
return new Notice('info', "The thread is " + change + ".", 30);
},
@ -12309,10 +12454,14 @@
var OP, count, files, index, node, num, post, postObject, posts, root, scroll, sendEvent, _i, _j, _len, _len1;
OP = postObjects[0];
Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler;
ThreadUpdater.thread.setStatus('Archived', !!+OP.archived);
ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky);
ThreadUpdater.updateThreadStatus('Closed', !!OP.closed);
ThreadUpdater.thread.postLimit = !!OP.bumplimit;
ThreadUpdater.thread.fileLimit = !!OP.imagelimit;
if (OP.unique_ips != null) {
ThreadUpdater.thread.ipCount = OP.unique_ips;
}
posts = [];
index = [];
files = [];
@ -12353,7 +12502,8 @@
return post.fullID;
}),
postCount: OP.replies + 1,
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead)
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead),
ipCount: OP.unique_ips
});
};
if (!count) {
@ -17953,6 +18103,7 @@
init('Thread Updater', ThreadUpdater);
init('Thread Watcher', ThreadWatcher);
init('Thread Watcher (Menu)', ThreadWatcher.menu);
init('Mark New IPs', MarkNewIPs);
init('Index Navigation', Nav);
init('Keybinds', Keybinds);
init('Show Dice Roll', Dice);

View File

@ -255,9 +255,13 @@ Config =
true
'Display reply and image count.'
]
'IP Count in Stats': [
true
'Display the unique IP count in the thread stats.'
]
'Page Count in Stats': [
false
'Display the page count in the thread stats as well.'
true
'Display the page count in the thread stats.'
]
'Updater and Stats in Header': [
true,
@ -267,6 +271,10 @@ Config =
true
'Bookmark threads.'
]
'Mark New IPs': [
false
'Label each post from a new IP with the thread\'s current IP count.'
]
'Posting':
'Header Shortcut': [
@ -315,7 +323,7 @@ Config =
]
'Auto-load captcha': [
false
'Automatically load the captcha when you open a thread, and reload it after you post.'
'Automatically load the captcha in the QR even if your post is empty.'
]
'Quote Links':

View File

@ -170,6 +170,7 @@ Main =
init 'Thread Updater', ThreadUpdater
init 'Thread Watcher', ThreadWatcher
init 'Thread Watcher (Menu)', ThreadWatcher.menu
init 'Mark New IPs', MarkNewIPs
init 'Index Navigation', Nav
init 'Keybinds', Keybinds
init 'Show Dice Roll', Dice

View File

@ -13,6 +13,7 @@ class Thread
@isClosed = false
@postLimit = false
@fileLimit = false
@ipCount = undefined
@OP = null
@catalogView = null
@ -35,21 +36,32 @@ class Thread
@[name] = status
return unless @OP
typeLC = type.toLowerCase()
@setIcon 'Sticky', @isSticky
@setIcon 'Closed', @isClosed and !@isArchived
@setIcon 'Archived', @isArchived
setIcon: (type, status) ->
typeLC = type.toLowerCase()
icon = $ ".#{typeLC}Icon", @OP.nodes.info
return if !!icon is status
unless status
$.rm $ ".#{typeLC}Icon", @OP.nodes.info
$.rm icon.previousSibling
$.rm icon
$.rm $ ".#{typeLC}Icon", @catalogView.nodes.icons if @catalogView
return
icon = $.el 'img',
src: "#{Build.staticPath}#{typeLC}#{Build.gifIcon}"
alt: type
title: type
className: "#{typeLC}Icon"
root = if type is 'Closed' and @isSticky
$ '.stickyIcon', @OP.nodes.info
else if g.VIEW is 'index'
$ '.page-num', @OP.nodes.info
else
$ '[title="Reply to this post"]', @OP.nodes.info
className: "#{typeLC}Icon retina"
root =
if type isnt 'Sticky' and @isSticky
$ '.stickyIcon', @OP.nodes.info
else
$('.page-num', @OP.nodes.info) or $('[title="Reply to this post"]', @OP.nodes.info)
$.after root, [$.tn(' '), icon]
return unless @catalogView

View File

@ -0,0 +1,45 @@
MarkNewIPs =
init: ->
return if g.VIEW isnt 'thread' or !Conf['Mark New IPs']
Thread.callbacks.push
name: 'Mark New IPs'
cb: @node
node: ->
MarkNewIPs.ipCount = @ipCount
MarkNewIPs.postIDs = @posts.keys.map (x) -> +x
$.on d, 'ThreadUpdate', MarkNewIPs.onUpdate
onUpdate: (e) ->
{ipCount, newPosts} = e.detail
{postIDs} = ThreadUpdater
return unless ipCount?
if newPosts.length
obj = {}
obj[x] = true for x in MarkNewIPs.postIDs
added = 0
added++ for x in postIDs when not (x of obj)
removed = MarkNewIPs.postIDs.length + added - postIDs.length
switch ipCount - MarkNewIPs.ipCount
when added
i = MarkNewIPs.ipCount
for fullID in newPosts
MarkNewIPs.markNew g.posts[fullID], ++i
when -removed
for fullID in newPosts
MarkNewIPs.markOld g.posts[fullID]
MarkNewIPs.ipCount = ipCount
MarkNewIPs.postIDs = postIDs
markNew: (post, ipCount) ->
suffix = {1: 'st', 2: 'nd', 3: 'rd'}[ipCount % 10] or 'th' // fuck switches
counter = $.el 'span',
className: 'ip-counter'
textContent: "(#{ipCount})"
post.nodes.nameBlock.title = "This is the #{ipCount}#{suffix} IP in the thread."
$.add post.nodes.nameBlock, [$.tn(' '), counter]
$.addClass post.nodes.root, 'new-ip'
markOld: (post) ->
post.nodes.nameBlock.title = 'Not the first post from this IP.'
$.addClass post.nodes.root, 'old-ip'

View File

@ -4,9 +4,17 @@ ThreadStats =
if Conf['Updater and Stats in Header']
@dialog = sc = $.el 'span',
innerHTML: "[<span id=post-count>0</span> / <span id=file-count>0</span>#{if Conf["Page Count in Stats"] then " / <span id=page-count>0</span>" else ""}]"
innerHTML: """
[<span id=post-count>0</span> /
<span id=file-count>0</span>
#{if Conf['IP Count in Stats'] then ' / <span id=ip-count>?</span>' else ""}
#{if Conf['Page Count in Stats'] then ' / <span id=page-count>0</span>' else ""}]
"""
id: 'thread-stats'
title: 'Post Count / File Count' + (if Conf["Page Count in Stats"] then " / Page Count" else "")
title:
'Post Count / File Count' +
(if Conf['IP Count in Stats'] then " / IPs" else "") +
(if Conf['Page Count in Stats'] then " / Page Count" else "")
$.ready ->
Header.addShortcut sc
else
@ -15,8 +23,9 @@ ThreadStats =
$.ready =>
$.add d.body, sc
@postCountEl = $ '#post-count', sc
@fileCountEl = $ '#file-count', sc
@postCountEl = $ '#post-count', sc
@ipCountEl = $ '#ip-count', sc
@fileCountEl = $ '#file-count', sc
@pageCountEl = $ '#page-count', sc
Thread.callbacks.push
@ -31,7 +40,7 @@ ThreadStats =
fileCount++ if post.file
ThreadStats.thread = @
ThreadStats.fetchPage()
ThreadStats.update postCount, fileCount
ThreadStats.update postCount, fileCount, @ipCount
$.on d, 'ThreadUpdate', ThreadStats.onUpdate
disconnect: ->
@ -56,13 +65,20 @@ ThreadStats =
onUpdate: (e) ->
return if e.detail[404]
{postCount, fileCount} = e.detail
ThreadStats.update postCount, fileCount
{postCount, fileCount, ipCount, newPosts} = e.detail
ThreadStats.update postCount, fileCount, ipCount
return unless Conf["Page Count in Stats"]
if newPosts.length
ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date
if ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1'
ThreadStats.fetchPage()
update: (postCount, fileCount) ->
{thread, postCountEl, fileCountEl} = ThreadStats
update: (postCount, fileCount, ipCount) ->
{thread, postCountEl, fileCountEl, ipCountEl} = ThreadStats
postCountEl.textContent = postCount
fileCountEl.textContent = fileCount
if ipCount? and Conf["IP Count in Stats"]
ipCountEl.textContent = ipCount
(if thread.postLimit and !thread.isSticky then $.addClass else $.rmClass) postCountEl, 'warning'
(if thread.fileLimit and !thread.isSticky then $.addClass else $.rmClass) fileCountEl, 'warning'

View File

@ -104,8 +104,14 @@ ThreadUpdater =
$.on d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost
$.on d, 'visibilitychange', ThreadUpdater.cb.visibility
ThreadUpdater.cb.online()
if ThreadUpdater.thread.isArchived
ThreadUpdater.set 'status', 'Archived', 'warning'
else
ThreadUpdater.cb.online()
Rice.nodes ThreadUpdater.dialog
return
###
http://freesound.org/people/pierrecartoons1979/sounds/90112/
@ -160,28 +166,50 @@ ThreadUpdater =
when 200
g.DEAD = false
ThreadUpdater.parse req.response.posts
ThreadUpdater.setInterval()
when 404
g.DEAD = true
ThreadUpdater.set 'timer', null
ThreadUpdater.set 'status', '404', 'warning'
clearTimeout ThreadUpdater.timeoutID
ThreadUpdater.thread.kill()
$.event 'ThreadUpdate',
404: true
threadID: ThreadUpdater.thread.fullID
else
ThreadUpdater.outdateCount++
ThreadUpdater.setInterval()
[text, klass] = if req.status is 304
[null, null]
if ThreadUpdater.thread.isArchived
ThreadUpdater.set 'status', 'Archived', 'warning'
ThreadUpdater.kill()
else
["#{req.statusText} (#{req.status})", 'warning']
ThreadUpdater.set 'status', text, klass
ThreadUpdater.setInterval()
when 404
# XXX workaround for 4chan sending false 404s
$.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/catalog.json", onloadend: ->
if @status is 200
confirmed = true
for page in @response
for thread in page.threads
if thread.no is ThreadUpdater.thread.ID
confirmed = false
break
else
confirmed = false
if confirmed
ThreadUpdater.set 'status', '404', 'warning'
ThreadUpdater.kill()
else
ThreadUpdater.error req
else
ThreadUpdater.error req
if ThreadUpdater.postID
ThreadUpdater.cb.checkpost()
kill: ->
ThreadUpdater.set 'timer', ''
clearTimeout ThreadUpdater.timeoutID
ThreadUpdater.thread.kill()
$.event 'ThreadUpdate',
404: true
threadID: ThreadUpdater.thread.fullID
error: (req) ->
ThreadUpdater.setInterval()
[text, klass] = if req.status is 304
['', '']
else
["#{req.statusText} (#{req.status})", 'warning']
ThreadUpdater.set 'status', text, klass
setInterval: ->
i = ThreadUpdater.interval + 1
@ -256,6 +284,7 @@ ThreadUpdater =
updateThreadStatus: (type, status) ->
return unless hasChanged = ThreadUpdater.thread["is#{type}"] isnt status
ThreadUpdater.thread.setStatus type, status
return if type is 'Closed' and ThreadUpdater.thread.isArchived
change = if type is 'Sticky'
if status
'now a sticky'
@ -272,10 +301,13 @@ ThreadUpdater =
OP = postObjects[0]
Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler
# XXX Some threads such as /g/'s sticky https://a.4cdn.org/g/thread/39894014.json still use a string as the archived property.
ThreadUpdater.thread.setStatus 'Archived', !!+OP.archived
ThreadUpdater.updateThreadStatus 'Sticky', !!OP.sticky
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?
posts = [] # post objects
index = [] # existing posts
@ -317,6 +349,7 @@ ThreadUpdater =
newPosts: posts.map (post) -> post.fullID
postCount: OP.replies + 1
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file and !ThreadUpdater.thread.OP.file.isDead)
ipCount: OP.unique_ips
unless count
ThreadUpdater.set 'status', null, null

View File

@ -98,22 +98,19 @@ QR.captcha =
else
null
save: (e) -> try
save: (e) ->
if @needed()
@shouldFocus = true
@reload()
else
@nodes.counter.focus()
@timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND
console.log e.detail
$.forceSync 'captchas'
@captchas.push
response: e.detail
timeout: Date.now() + 2 * $.MINUTE
@count()
$.set 'captchas', @captchas
catch err
console.log err
clear: ->
return unless @captchas.length