Merge ccd0's IP Count stuff
This commit is contained in:
parent
0389bcf930
commit
020a68f73a
2
LICENSE
2
LICENSE
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
45
src/Monitoring/MarkNewIPs.coffee
Normal file
45
src/Monitoring/MarkNewIPs.coffee
Normal 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'
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user