Merge branch 'v3' into Av2

Conflicts:
	builds/4chan-X.js
	builds/4chan-X.user.js
	builds/crx.crx
	builds/crx/script.js
	src/code/main.coffee
	src/code/misc/keybinds.coffee
	src/code/posting/qr.coffee
	src/css/style.css
This commit is contained in:
Zixaphir 2013-04-19 01:15:35 -07:00
commit ffec9f6881
13 changed files with 676 additions and 74 deletions

View File

@ -19,7 +19,7 @@
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg== // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg==
// ==/UserScript== // ==/UserScript==
/* appchan x - Version 2.0.0 - 2013-04-16 /* appchan x - Version 2.0.0 - 2013-04-19
* http://zixaphir.github.com/appchan-x/ * http://zixaphir.github.com/appchan-x/
* *
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
@ -42,7 +42,7 @@
*/ */
(function() { (function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation, var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice, __slice = [].slice,
__hasProp = {}.hasOwnProperty, __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -133,7 +133,8 @@
'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'],
'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'],
'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'],
'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'] 'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'],
'Quote Threading': [false, 'Thread conversations']
} }
}, },
imageExpansion: { imageExpansion: {
@ -2498,7 +2499,6 @@
arg = args[_i]; arg = args[_i];
this.push.apply(this, arg); this.push.apply(this, arg);
} }
return this;
}, },
remove: function(object) { remove: function(object) {
var index; var index;
@ -2565,6 +2565,7 @@
} }
type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync; type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync;
r = new XMLHttpRequest(); r = new XMLHttpRequest();
r.overrideMimeType('text/html');
type || (type = form && 'post' || 'get'); type || (type = form && 'post' || 'get');
r.open(type, url, !sync); r.open(type, url, !sync);
for (key in headers) { for (key in headers) {
@ -7066,7 +7067,7 @@
hashScroll: function() { hashScroll: function() {
var post; var post;
if (!(post = $.id(this.location.hash.slice(1)))) { if (!(post = this.location.hash.slice(1))) {
return; return;
} }
if ((Get.postFromRoot(post)).isHidden) { if ((Get.postFromRoot(post)).isHidden) {
@ -7331,7 +7332,8 @@
if (quote) { if (quote) {
QR.quote.call($('input', $('.post.highlight', thread) || thread)); QR.quote.call($('input', $('.post.highlight', thread) || thread));
} }
return QR.nodes.com.focus(); QR.nodes.com.focus();
return $.rmClass($('.qr-shortcut'), 'disabled');
}, },
tags: function(tag, ta) { tags: function(tag, ta) {
var range, selEnd, selStart, value; var range, selEnd, selStart, value;
@ -8329,7 +8331,7 @@
return $.after(root, [$.tn(' '), icon]); return $.after(root, [$.tn(' '), icon]);
}, },
parse: function(postObjects) { parse: function(postObjects) {
var ID, OP, count, deletedFiles, deletedPosts, files, index, node, nodes, num, post, postObject, posts, scroll, _i, _len, _ref; var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, scroll, _i, _len, _ref;
OP = postObjects[0]; OP = postObjects[0];
Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler;
@ -8337,7 +8339,6 @@
ThreadUpdater.updateThreadStatus('Closed', OP); ThreadUpdater.updateThreadStatus('Closed', OP);
ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.postLimit = !!OP.bumplimit;
ThreadUpdater.thread.fileLimit = !!OP.imagelimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit;
nodes = [];
posts = []; posts = [];
index = []; index = [];
files = []; files = [];
@ -8354,7 +8355,6 @@
} }
count++; count++;
node = Build.postFromObject(postObject, ThreadUpdater.thread.board); node = Build.postFromObject(postObject, ThreadUpdater.thread.board);
nodes.push(node);
posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board));
} }
deletedPosts = []; deletedPosts = [];
@ -8395,7 +8395,19 @@
ThreadUpdater.lastPost = posts[count - 1].ID; ThreadUpdater.lastPost = posts[count - 1].ID;
Main.callbackNodes(Post, posts); Main.callbackNodes(Post, posts);
scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25; scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25;
$.add(ThreadUpdater.root, nodes); for (key in posts) {
post = posts[key];
if (!posts.hasOwnProperty(key)) {
continue;
}
if (post.cb) {
if (!post.cb.call(post)) {
$.add(ThreadUpdater.root, post.nodes.root);
}
} else {
$.add(ThreadUpdater.root, post.nodes.root);
}
}
if (scroll) { if (scroll) {
if (Conf['Bottom Scroll']) { if (Conf['Bottom Scroll']) {
doc.scrollTop = d.body.clientHeight; doc.scrollTop = d.body.clientHeight;
@ -8715,26 +8727,27 @@
return arr.splice(0, i); return arr.splice(0, i);
}, },
read: function(e) { read: function(e) {
var bottom, height, i, post, _i, _len, _ref; var ID, bottom, height, i, post, posts, read, top, _ref;
if (d.hidden || !Unread.posts.length) { if (d.hidden || !Unread.posts.length) {
return; return;
} }
height = doc.clientHeight; height = doc.clientHeight;
_ref = Unread.posts; posts = Unread.posts;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { read = [];
post = _ref[i]; i = posts.length;
bottom = post.nodes.root.getBoundingClientRect().bottom; while (post = posts[--i]) {
if (bottom > height) { _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
break; if ((bottom < height) && (top > 0)) {
ID = post.ID;
posts.remove(post);
} }
} }
if (!i) { if (!ID) {
return; return;
} }
Unread.lastReadPost = Unread.posts[i - 1].ID; Unread.lastReadPost = ID;
Unread.saveLastReadPost(); Unread.saveLastReadPost();
Unread.posts.splice(0, i);
Unread.readArray(Unread.postsQuotingYou); Unread.readArray(Unread.postsQuotingYou);
if (e) { if (e) {
return Unread.update(); return Unread.update();
@ -8886,6 +8899,7 @@
QR.cleanNotifications(); QR.cleanNotifications();
d.activeElement.blur(); d.activeElement.blur();
$.rmClass(QR.nodes.el, 'dump'); $.rmClass(QR.nodes.el, 'dump');
$.toggleClass($('.qr-shortcut'), 'disabled');
_ref = QR.posts; _ref = QR.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
i = _ref[_i]; i = _ref[_i];
@ -9141,7 +9155,8 @@
com.setSelectionRange(range, range); com.setSelectionRange(range, range);
com.focus(); com.focus();
QR.selected.save(com); QR.selected.save(com);
return QR.selected.save(thread); QR.selected.save(thread);
return $.rmClass($('.qr-shortcut'), 'disabled');
}, },
characterCount: function() { characterCount: function() {
var count, counter; var count, counter;
@ -10438,6 +10453,151 @@
} }
}; };
/*
<3 aeosynth
*/
QuoteThreading = {
init: function() {
var input;
if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) {
return;
}
this.enabled = true;
this.controls = $.el('span', {
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
});
input = $('input', this.controls);
$.on(input, 'change', QuoteThreading.toggle);
$.event('AddMenuEntry', {
type: 'header',
el: this.controls,
order: 115
});
$.on(d, '4chanXInitFinished', this.setup);
return Post.prototype.callbacks.push({
name: 'Quote Threading',
cb: this.node
});
},
setup: function() {
var ID, post, posts;
$.off(d, '4chanXInitFinished', QuoteThreading.setup);
posts = g.posts;
for (ID in posts) {
post = posts[ID];
if (post.cb) {
post.cb.call(post);
}
}
return QuoteThreading.hasRun = true;
},
node: function() {
var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len;
if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
return;
}
quotes = this.quotes, ID = this.ID, fullID = this.fullID;
posts = g.posts;
if (!(post = posts[fullID]) || post.isHidden) {
return;
}
uniq = {};
len = ("" + g.BOARD).length + 1;
for (_i = 0, _len = quotes.length; _i < _len; _i++) {
quote = quotes[_i];
qid = quote;
if (!(qid.slice(len) < ID)) {
continue;
}
if (qid in posts) {
uniq[qid.slice(len)] = true;
}
}
keys = Object.keys(uniq);
if (keys.length !== 1) {
return;
}
this.threaded = "" + g.BOARD + "." + keys[0];
return this.cb = QuoteThreading.nodeinsert;
},
nodeinsert: function() {
var posts, qpost, qroot, threadContainer;
posts = g.posts;
qpost = posts[this.threaded];
delete this.threaded;
delete this.cb;
if (this.thread.OP === qpost || (QuoteThreading.hasRun && !Unread.posts.contains(qpost))) {
return false;
}
qroot = qpost.nodes.root;
threadContainer = qroot.nextSibling;
if ((threadContainer != null ? threadContainer.className : void 0) !== 'threadContainer') {
threadContainer = $.el('div', {
className: 'threadContainer'
});
$.after(qroot, threadContainer);
}
$.add(threadContainer, this.nodes.root);
return true;
},
toggle: function() {
var container, containers, node, nodes, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _results;
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
QuoteThreading.enabled = this.checked;
if (this.checked) {
nodes = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
_results.push(Get.postFromNode(reply));
}
return _results;
})();
for (_i = 0, _len = nodes.length; _i < _len; _i++) {
node = nodes[_i];
Unread.node.call(node);
}
_results = [];
for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {
node = nodes[_j];
_results.push(QuoteThreading.node(node));
}
return _results;
} else {
replies.sort(function(a, b) {
var aID, bID;
aID = Number(a.id.slice(2));
bID = Number(b.id.slice(2));
return aID - bID;
});
$.add(thread, replies);
containers = $$('.threadContainer', thread);
for (_k = 0, _len2 = containers.length; _k < _len2; _k++) {
container = containers[_k];
$.rm(container);
}
return Unread.update(true);
}
},
kb: function() {
var control;
control = $.id('threadingControl');
return control.click();
}
};
QuoteYou = { QuoteYou = {
init: function() { init: function() {
if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) { if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) {
@ -13434,6 +13594,7 @@
'Thread Excerpt': ThreadExcerpt, 'Thread Excerpt': ThreadExcerpt,
'Favicon': Favicon, 'Favicon': Favicon,
'Unread': Unread, 'Unread': Unread,
'Quote Threading': QuoteThreading,
'Thread Updater': ThreadUpdater, 'Thread Updater': ThreadUpdater,
'Thread Stats': ThreadStats, 'Thread Stats': ThreadStats,
'Thread Watcher': ThreadWatcher, 'Thread Watcher': ThreadWatcher,

View File

@ -19,7 +19,7 @@
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg== // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg==
// ==/UserScript== // ==/UserScript==
/* appchan x - Version 2.0.0 - 2013-04-16 /* appchan x - Version 2.0.0 - 2013-04-19
* http://zixaphir.github.com/appchan-x/ * http://zixaphir.github.com/appchan-x/
* *
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
@ -42,7 +42,7 @@
*/ */
(function() { (function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation, var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice, __slice = [].slice,
__hasProp = {}.hasOwnProperty, __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -134,7 +134,8 @@
'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'],
'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'],
'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'],
'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'] 'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'],
'Quote Threading': [false, 'Thread conversations']
} }
}, },
imageExpansion: { imageExpansion: {
@ -2495,7 +2496,6 @@
arg = args[_i]; arg = args[_i];
this.push.apply(this, arg); this.push.apply(this, arg);
} }
return this;
}, },
remove: function(object) { remove: function(object) {
var index; var index;
@ -2562,6 +2562,7 @@
} }
type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync; type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync;
r = new XMLHttpRequest(); r = new XMLHttpRequest();
r.overrideMimeType('text/html');
type || (type = form && 'post' || 'get'); type || (type = form && 'post' || 'get');
r.open(type, url, !sync); r.open(type, url, !sync);
for (key in headers) { for (key in headers) {
@ -7058,7 +7059,7 @@
hashScroll: function() { hashScroll: function() {
var post; var post;
if (!(post = $.id(this.location.hash.slice(1)))) { if (!(post = this.location.hash.slice(1))) {
return; return;
} }
if ((Get.postFromRoot(post)).isHidden) { if ((Get.postFromRoot(post)).isHidden) {
@ -7323,7 +7324,8 @@
if (quote) { if (quote) {
QR.quote.call($('input', $('.post.highlight', thread) || thread)); QR.quote.call($('input', $('.post.highlight', thread) || thread));
} }
return QR.nodes.com.focus(); QR.nodes.com.focus();
return $.rmClass($('.qr-shortcut'), 'disabled');
}, },
tags: function(tag, ta) { tags: function(tag, ta) {
var range, selEnd, selStart, value; var range, selEnd, selStart, value;
@ -8321,7 +8323,7 @@
return $.after(root, [$.tn(' '), icon]); return $.after(root, [$.tn(' '), icon]);
}, },
parse: function(postObjects) { parse: function(postObjects) {
var ID, OP, count, deletedFiles, deletedPosts, files, index, node, nodes, num, post, postObject, posts, scroll, _i, _len, _ref; var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, scroll, _i, _len, _ref;
OP = postObjects[0]; OP = postObjects[0];
Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler;
@ -8329,7 +8331,6 @@
ThreadUpdater.updateThreadStatus('Closed', OP); ThreadUpdater.updateThreadStatus('Closed', OP);
ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.postLimit = !!OP.bumplimit;
ThreadUpdater.thread.fileLimit = !!OP.imagelimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit;
nodes = [];
posts = []; posts = [];
index = []; index = [];
files = []; files = [];
@ -8346,7 +8347,6 @@
} }
count++; count++;
node = Build.postFromObject(postObject, ThreadUpdater.thread.board); node = Build.postFromObject(postObject, ThreadUpdater.thread.board);
nodes.push(node);
posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board));
} }
deletedPosts = []; deletedPosts = [];
@ -8387,7 +8387,19 @@
ThreadUpdater.lastPost = posts[count - 1].ID; ThreadUpdater.lastPost = posts[count - 1].ID;
Main.callbackNodes(Post, posts); Main.callbackNodes(Post, posts);
scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25; scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25;
$.add(ThreadUpdater.root, nodes); for (key in posts) {
post = posts[key];
if (!posts.hasOwnProperty(key)) {
continue;
}
if (post.cb) {
if (!post.cb.call(post)) {
$.add(ThreadUpdater.root, post.nodes.root);
}
} else {
$.add(ThreadUpdater.root, post.nodes.root);
}
}
if (scroll) { if (scroll) {
if (Conf['Bottom Scroll']) { if (Conf['Bottom Scroll']) {
doc.scrollTop = d.body.clientHeight; doc.scrollTop = d.body.clientHeight;
@ -8707,26 +8719,27 @@
return arr.splice(0, i); return arr.splice(0, i);
}, },
read: function(e) { read: function(e) {
var bottom, height, i, post, _i, _len, _ref; var ID, bottom, height, i, post, posts, read, top, _ref;
if (d.hidden || !Unread.posts.length) { if (d.hidden || !Unread.posts.length) {
return; return;
} }
height = doc.clientHeight; height = doc.clientHeight;
_ref = Unread.posts; posts = Unread.posts;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { read = [];
post = _ref[i]; i = posts.length;
bottom = post.nodes.root.getBoundingClientRect().bottom; while (post = posts[--i]) {
if (bottom > height) { _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
break; if ((bottom < height) && (top > 0)) {
ID = post.ID;
posts.remove(post);
} }
} }
if (!i) { if (!ID) {
return; return;
} }
Unread.lastReadPost = Unread.posts[i - 1].ID; Unread.lastReadPost = ID;
Unread.saveLastReadPost(); Unread.saveLastReadPost();
Unread.posts.splice(0, i);
Unread.readArray(Unread.postsQuotingYou); Unread.readArray(Unread.postsQuotingYou);
if (e) { if (e) {
return Unread.update(); return Unread.update();
@ -8878,6 +8891,7 @@
QR.cleanNotifications(); QR.cleanNotifications();
d.activeElement.blur(); d.activeElement.blur();
$.rmClass(QR.nodes.el, 'dump'); $.rmClass(QR.nodes.el, 'dump');
$.toggleClass($('.qr-shortcut'), 'disabled');
_ref = QR.posts; _ref = QR.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
i = _ref[_i]; i = _ref[_i];
@ -9133,7 +9147,8 @@
com.setSelectionRange(range, range); com.setSelectionRange(range, range);
com.focus(); com.focus();
QR.selected.save(com); QR.selected.save(com);
return QR.selected.save(thread); QR.selected.save(thread);
return $.rmClass($('.qr-shortcut'), 'disabled');
}, },
characterCount: function() { characterCount: function() {
var count, counter; var count, counter;
@ -10455,6 +10470,151 @@
} }
}; };
/*
<3 aeosynth
*/
QuoteThreading = {
init: function() {
var input;
if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) {
return;
}
this.enabled = true;
this.controls = $.el('span', {
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
});
input = $('input', this.controls);
$.on(input, 'change', QuoteThreading.toggle);
$.event('AddMenuEntry', {
type: 'header',
el: this.controls,
order: 115
});
$.on(d, '4chanXInitFinished', this.setup);
return Post.prototype.callbacks.push({
name: 'Quote Threading',
cb: this.node
});
},
setup: function() {
var ID, post, posts;
$.off(d, '4chanXInitFinished', QuoteThreading.setup);
posts = g.posts;
for (ID in posts) {
post = posts[ID];
if (post.cb) {
post.cb.call(post);
}
}
return QuoteThreading.hasRun = true;
},
node: function() {
var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len;
if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
return;
}
quotes = this.quotes, ID = this.ID, fullID = this.fullID;
posts = g.posts;
if (!(post = posts[fullID]) || post.isHidden) {
return;
}
uniq = {};
len = ("" + g.BOARD).length + 1;
for (_i = 0, _len = quotes.length; _i < _len; _i++) {
quote = quotes[_i];
qid = quote;
if (!(qid.slice(len) < ID)) {
continue;
}
if (qid in posts) {
uniq[qid.slice(len)] = true;
}
}
keys = Object.keys(uniq);
if (keys.length !== 1) {
return;
}
this.threaded = "" + g.BOARD + "." + keys[0];
return this.cb = QuoteThreading.nodeinsert;
},
nodeinsert: function() {
var posts, qpost, qroot, threadContainer;
posts = g.posts;
qpost = posts[this.threaded];
delete this.threaded;
delete this.cb;
if (this.thread.OP === qpost || (QuoteThreading.hasRun && !Unread.posts.contains(qpost))) {
return false;
}
qroot = qpost.nodes.root;
threadContainer = qroot.nextSibling;
if ((threadContainer != null ? threadContainer.className : void 0) !== 'threadContainer') {
threadContainer = $.el('div', {
className: 'threadContainer'
});
$.after(qroot, threadContainer);
}
$.add(threadContainer, this.nodes.root);
return true;
},
toggle: function() {
var container, containers, node, nodes, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _results;
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
QuoteThreading.enabled = this.checked;
if (this.checked) {
nodes = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
_results.push(Get.postFromNode(reply));
}
return _results;
})();
for (_i = 0, _len = nodes.length; _i < _len; _i++) {
node = nodes[_i];
Unread.node.call(node);
}
_results = [];
for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {
node = nodes[_j];
_results.push(QuoteThreading.node(node));
}
return _results;
} else {
replies.sort(function(a, b) {
var aID, bID;
aID = Number(a.id.slice(2));
bID = Number(b.id.slice(2));
return aID - bID;
});
$.add(thread, replies);
containers = $$('.threadContainer', thread);
for (_k = 0, _len2 = containers.length; _k < _len2; _k++) {
container = containers[_k];
$.rm(container);
}
return Unread.update(true);
}
},
kb: function() {
var control;
control = $.id('threadingControl');
return control.click();
}
};
QuoteYou = { QuoteYou = {
init: function() { init: function() {
if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) { if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) {
@ -13453,6 +13613,7 @@
'Thread Excerpt': ThreadExcerpt, 'Thread Excerpt': ThreadExcerpt,
'Favicon': Favicon, 'Favicon': Favicon,
'Unread': Unread, 'Unread': Unread,
'Quote Threading': QuoteThreading,
'Thread Updater': ThreadUpdater, 'Thread Updater': ThreadUpdater,
'Thread Stats': ThreadStats, 'Thread Stats': ThreadStats,
'Thread Watcher': ThreadWatcher, 'Thread Watcher': ThreadWatcher,

Binary file not shown.

View File

@ -1,5 +1,5 @@
(function() { (function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation, var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, Icons, ImageExpand, ImageHover, ImageReplace, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice, __slice = [].slice,
__hasProp = {}.hasOwnProperty, __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -90,7 +90,8 @@
'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'],
'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'],
'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'],
'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'] 'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'],
'Quote Threading': [false, 'Thread conversations']
} }
}, },
imageExpansion: { imageExpansion: {
@ -2451,7 +2452,6 @@
arg = args[_i]; arg = args[_i];
this.push.apply(this, arg); this.push.apply(this, arg);
} }
return this;
}, },
remove: function(object) { remove: function(object) {
var index; var index;
@ -2518,6 +2518,7 @@
} }
type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync; type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync;
r = new XMLHttpRequest(); r = new XMLHttpRequest();
r.overrideMimeType('text/html');
type || (type = form && 'post' || 'get'); type || (type = form && 'post' || 'get');
r.open(type, url, !sync); r.open(type, url, !sync);
for (key in headers) { for (key in headers) {
@ -6980,7 +6981,7 @@
hashScroll: function() { hashScroll: function() {
var post; var post;
if (!(post = $.id(this.location.hash.slice(1)))) { if (!(post = this.location.hash.slice(1))) {
return; return;
} }
if ((Get.postFromRoot(post)).isHidden) { if ((Get.postFromRoot(post)).isHidden) {
@ -7245,7 +7246,8 @@
if (quote) { if (quote) {
QR.quote.call($('input', $('.post.highlight', thread) || thread)); QR.quote.call($('input', $('.post.highlight', thread) || thread));
} }
return QR.nodes.com.focus(); QR.nodes.com.focus();
return $.rmClass($('.qr-shortcut'), 'disabled');
}, },
tags: function(tag, ta) { tags: function(tag, ta) {
var range, selEnd, selStart, value; var range, selEnd, selStart, value;
@ -8243,7 +8245,7 @@
return $.after(root, [$.tn(' '), icon]); return $.after(root, [$.tn(' '), icon]);
}, },
parse: function(postObjects) { parse: function(postObjects) {
var ID, OP, count, deletedFiles, deletedPosts, files, index, node, nodes, num, post, postObject, posts, scroll, _i, _len, _ref; var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, scroll, _i, _len, _ref;
OP = postObjects[0]; OP = postObjects[0];
Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler;
@ -8251,7 +8253,6 @@
ThreadUpdater.updateThreadStatus('Closed', OP); ThreadUpdater.updateThreadStatus('Closed', OP);
ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.postLimit = !!OP.bumplimit;
ThreadUpdater.thread.fileLimit = !!OP.imagelimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit;
nodes = [];
posts = []; posts = [];
index = []; index = [];
files = []; files = [];
@ -8268,7 +8269,6 @@
} }
count++; count++;
node = Build.postFromObject(postObject, ThreadUpdater.thread.board); node = Build.postFromObject(postObject, ThreadUpdater.thread.board);
nodes.push(node);
posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board));
} }
deletedPosts = []; deletedPosts = [];
@ -8309,7 +8309,19 @@
ThreadUpdater.lastPost = posts[count - 1].ID; ThreadUpdater.lastPost = posts[count - 1].ID;
Main.callbackNodes(Post, posts); Main.callbackNodes(Post, posts);
scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25; scroll = Conf['Auto Scroll'] && ThreadUpdater.scrollBG() && ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25;
$.add(ThreadUpdater.root, nodes); for (key in posts) {
post = posts[key];
if (!posts.hasOwnProperty(key)) {
continue;
}
if (post.cb) {
if (!post.cb.call(post)) {
$.add(ThreadUpdater.root, post.nodes.root);
}
} else {
$.add(ThreadUpdater.root, post.nodes.root);
}
}
if (scroll) { if (scroll) {
if (Conf['Bottom Scroll']) { if (Conf['Bottom Scroll']) {
d.body.scrollTop = d.body.clientHeight; d.body.scrollTop = d.body.clientHeight;
@ -8629,26 +8641,27 @@
return arr.splice(0, i); return arr.splice(0, i);
}, },
read: function(e) { read: function(e) {
var bottom, height, i, post, _i, _len, _ref; var ID, bottom, height, i, post, posts, read, top, _ref;
if (d.hidden || !Unread.posts.length) { if (d.hidden || !Unread.posts.length) {
return; return;
} }
height = doc.clientHeight; height = doc.clientHeight;
_ref = Unread.posts; posts = Unread.posts;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { read = [];
post = _ref[i]; i = posts.length;
bottom = post.nodes.root.getBoundingClientRect().bottom; while (post = posts[--i]) {
if (bottom > height) { _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
break; if ((bottom < height) && (top > 0)) {
ID = post.ID;
posts.remove(post);
} }
} }
if (!i) { if (!ID) {
return; return;
} }
Unread.lastReadPost = Unread.posts[i - 1].ID; Unread.lastReadPost = ID;
Unread.saveLastReadPost(); Unread.saveLastReadPost();
Unread.posts.splice(0, i);
Unread.readArray(Unread.postsQuotingYou); Unread.readArray(Unread.postsQuotingYou);
if (e) { if (e) {
return Unread.update(); return Unread.update();
@ -8806,6 +8819,7 @@
QR.cleanNotifications(); QR.cleanNotifications();
d.activeElement.blur(); d.activeElement.blur();
$.rmClass(QR.nodes.el, 'dump'); $.rmClass(QR.nodes.el, 'dump');
$.toggleClass($('.qr-shortcut'), 'disabled');
_ref = QR.posts; _ref = QR.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
i = _ref[_i]; i = _ref[_i];
@ -9061,7 +9075,8 @@
com.setSelectionRange(range, range); com.setSelectionRange(range, range);
com.focus(); com.focus();
QR.selected.save(com); QR.selected.save(com);
return QR.selected.save(thread); QR.selected.save(thread);
return $.rmClass($('.qr-shortcut'), 'disabled');
}, },
characterCount: function() { characterCount: function() {
var count, counter; var count, counter;
@ -10358,6 +10373,151 @@
} }
}; };
/*
<3 aeosynth
*/
QuoteThreading = {
init: function() {
var input;
if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) {
return;
}
this.enabled = true;
this.controls = $.el('span', {
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
});
input = $('input', this.controls);
$.on(input, 'change', QuoteThreading.toggle);
$.event('AddMenuEntry', {
type: 'header',
el: this.controls,
order: 115
});
$.on(d, '4chanXInitFinished', this.setup);
return Post.prototype.callbacks.push({
name: 'Quote Threading',
cb: this.node
});
},
setup: function() {
var ID, post, posts;
$.off(d, '4chanXInitFinished', QuoteThreading.setup);
posts = g.posts;
for (ID in posts) {
post = posts[ID];
if (post.cb) {
post.cb.call(post);
}
}
return QuoteThreading.hasRun = true;
},
node: function() {
var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len;
if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
return;
}
quotes = this.quotes, ID = this.ID, fullID = this.fullID;
posts = g.posts;
if (!(post = posts[fullID]) || post.isHidden) {
return;
}
uniq = {};
len = ("" + g.BOARD).length + 1;
for (_i = 0, _len = quotes.length; _i < _len; _i++) {
quote = quotes[_i];
qid = quote;
if (!(qid.slice(len) < ID)) {
continue;
}
if (qid in posts) {
uniq[qid.slice(len)] = true;
}
}
keys = Object.keys(uniq);
if (keys.length !== 1) {
return;
}
this.threaded = "" + g.BOARD + "." + keys[0];
return this.cb = QuoteThreading.nodeinsert;
},
nodeinsert: function() {
var posts, qpost, qroot, threadContainer;
posts = g.posts;
qpost = posts[this.threaded];
delete this.threaded;
delete this.cb;
if (this.thread.OP === qpost || (QuoteThreading.hasRun && !Unread.posts.contains(qpost))) {
return false;
}
qroot = qpost.nodes.root;
threadContainer = qroot.nextSibling;
if ((threadContainer != null ? threadContainer.className : void 0) !== 'threadContainer') {
threadContainer = $.el('div', {
className: 'threadContainer'
});
$.after(qroot, threadContainer);
}
$.add(threadContainer, this.nodes.root);
return true;
},
toggle: function() {
var container, containers, node, nodes, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _results;
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
QuoteThreading.enabled = this.checked;
if (this.checked) {
nodes = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
_results.push(Get.postFromNode(reply));
}
return _results;
})();
for (_i = 0, _len = nodes.length; _i < _len; _i++) {
node = nodes[_i];
Unread.node.call(node);
}
_results = [];
for (_j = 0, _len1 = nodes.length; _j < _len1; _j++) {
node = nodes[_j];
_results.push(QuoteThreading.node(node));
}
return _results;
} else {
replies.sort(function(a, b) {
var aID, bID;
aID = Number(a.id.slice(2));
bID = Number(b.id.slice(2));
return aID - bID;
});
$.add(thread, replies);
containers = $$('.threadContainer', thread);
for (_k = 0, _len2 = containers.length; _k < _len2; _k++) {
container = containers[_k];
$.rm(container);
}
return Unread.update(true);
}
},
kb: function() {
var control;
control = $.id('threadingControl');
return control.click();
}
};
QuoteYou = { QuoteYou = {
init: function() { init: function() {
if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) { if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) {
@ -13356,6 +13516,7 @@
'Thread Excerpt': ThreadExcerpt, 'Thread Excerpt': ThreadExcerpt,
'Favicon': Favicon, 'Favicon': Favicon,
'Unread': Unread, 'Unread': Unread,
'Quote Threading': QuoteThreading,
'Thread Updater': ThreadUpdater, 'Thread Updater': ThreadUpdater,
'Thread Stats': ThreadStats, 'Thread Stats': ThreadStats,
'Thread Watcher': ThreadWatcher, 'Thread Watcher': ThreadWatcher,

View File

@ -301,6 +301,10 @@ Config =
true true
'Add \'(Cross-thread)\' to cross-threads quotes.' 'Add \'(Cross-thread)\' to cross-threads quotes.'
] ]
'Quote Threading': [
false
'Thread conversations'
]
imageExpansion: imageExpansion:
'Fit width': [ 'Fit width': [

View File

@ -137,6 +137,7 @@ Main =
'Thread Excerpt': ThreadExcerpt 'Thread Excerpt': ThreadExcerpt
'Favicon': Favicon 'Favicon': Favicon
'Unread': Unread 'Unread': Unread
'Quote Threading': QuoteThreading
'Thread Updater': ThreadUpdater 'Thread Updater': ThreadUpdater
'Thread Stats': ThreadStats 'Thread Stats': ThreadStats
'Thread Watcher': ThreadWatcher 'Thread Watcher': ThreadWatcher

View File

@ -177,7 +177,7 @@ Header =
(if hide then $.addClass else $.rmClass) Header.nav, 'autohide' (if hide then $.addClass else $.rmClass) Header.nav, 'autohide'
hashScroll: -> hashScroll: ->
return unless post = $.id @location.hash[1..] return unless post = @location.hash[1..]
return if (Get.postFromRoot post).isHidden return if (Get.postFromRoot post).isHidden
Header.scrollToPost post Header.scrollToPost post

View File

@ -134,6 +134,7 @@ Keybinds =
if quote if quote
QR.quote.call $ 'input', $('.post.highlight', thread) or thread QR.quote.call $ 'input', $('.post.highlight', thread) or thread
do QR.nodes.com.focus do QR.nodes.com.focus
$.rmClass $('.qr-shortcut'), 'disabled'
tags: (tag, ta) -> tags: (tag, ta) ->
value = ta.value value = ta.value

View File

@ -219,7 +219,6 @@ ThreadUpdater =
ThreadUpdater.thread.postLimit = !!OP.bumplimit ThreadUpdater.thread.postLimit = !!OP.bumplimit
ThreadUpdater.thread.fileLimit = !!OP.imagelimit ThreadUpdater.thread.fileLimit = !!OP.imagelimit
nodes = [] # post container elements
posts = [] # post objects posts = [] # post objects
index = [] # existing posts index = [] # existing posts
files = [] # existing files files = [] # existing files
@ -233,7 +232,6 @@ ThreadUpdater =
# Insert new posts, not older ones. # Insert new posts, not older ones.
count++ count++
node = Build.postFromObject postObject, ThreadUpdater.thread.board node = Build.postFromObject postObject, ThreadUpdater.thread.board
nodes.push node
posts.push new Post node, ThreadUpdater.thread, ThreadUpdater.thread.board posts.push new Post node, ThreadUpdater.thread, ThreadUpdater.thread.board
deletedPosts = [] deletedPosts = []
@ -259,6 +257,7 @@ ThreadUpdater =
unless count unless count
ThreadUpdater.set 'status', null, null ThreadUpdater.set 'status', null, null
ThreadUpdater.outdateCount++ ThreadUpdater.outdateCount++
else else
ThreadUpdater.set 'status', "+#{count}", 'new' ThreadUpdater.set 'status', "+#{count}", 'new'
ThreadUpdater.outdateCount = 0 ThreadUpdater.outdateCount = 0
@ -272,7 +271,15 @@ ThreadUpdater =
scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and
ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25 ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25
$.add ThreadUpdater.root, nodes
for key, post of posts
continue unless posts.hasOwnProperty key
if post.cb
unless post.cb.call post
$.add ThreadUpdater.root, post.nodes.root
else
$.add ThreadUpdater.root, post.nodes.root
if scroll if scroll
if Conf['Bottom Scroll'] if Conf['Bottom Scroll']
<% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop = d.body.clientHeight <% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop = d.body.clientHeight

View File

@ -101,14 +101,19 @@ Unread =
read: (e) -> read: (e) ->
return if d.hidden or !Unread.posts.length return if d.hidden or !Unread.posts.length
height = doc.clientHeight height = doc.clientHeight
for post, i in Unread.posts {posts} = Unread
{bottom} = post.nodes.root.getBoundingClientRect() read = []
break if bottom > height # post is not completely read i = posts.length
return unless i
Unread.lastReadPost = Unread.posts[i - 1].ID while post = posts[--i]
{bottom, top} = post.nodes.root.getBoundingClientRect()
if (bottom < height) and (top > 0) # post is completely read
ID = post.ID
posts.remove post
return unless ID
Unread.lastReadPost = ID
Unread.saveLastReadPost() Unread.saveLastReadPost()
Unread.posts.splice 0, i
Unread.readArray Unread.postsQuotingYou Unread.readArray Unread.postsQuotingYou
Unread.update() if e Unread.update() if e

View File

@ -81,6 +81,7 @@ QR =
QR.cleanNotifications() QR.cleanNotifications()
d.activeElement.blur() d.activeElement.blur()
$.rmClass QR.nodes.el, 'dump' $.rmClass QR.nodes.el, 'dump'
$.toggleClass $('.qr-shortcut'), 'disabled'
for i in QR.posts for i in QR.posts
QR.posts[0].rm() QR.posts[0].rm()
QR.cooldown.auto = false QR.cooldown.auto = false
@ -302,6 +303,8 @@ QR =
QR.selected.save com QR.selected.save com
QR.selected.save thread QR.selected.save thread
$.rmClass $('.qr-shortcut'), 'disabled'
characterCount: -> characterCount: ->
counter = QR.nodes.charCount counter = QR.nodes.charCount
count = QR.nodes.com.textLength count = QR.nodes.com.textLength

View File

@ -0,0 +1,97 @@
###
<3 aeosynth
###
QuoteThreading =
init: ->
return unless Conf['Quote Threading'] and g.VIEW is 'thread'
@enabled = true
@controls = $.el 'span',
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
input = $ 'input', @controls
$.on input, 'change', QuoteThreading.toggle
$.event 'AddMenuEntry',
type: 'header'
el: @controls
order: 115
$.on d, '4chanXInitFinished', @setup
Post::callbacks.push
name: 'Quote Threading'
cb: @node
setup: ->
$.off d, '4chanXInitFinished', QuoteThreading.setup
{posts} = g
for ID, post of posts
if post.cb
post.cb.call post
QuoteThreading.hasRun = true
node: ->
return if @isClone or not QuoteThreading.enabled or @thread.OP is @
{quotes, ID, fullID} = @
{posts} = g
return if !(post = posts[fullID]) or post.isHidden # Filtered
uniq = {}
len = "#{g.BOARD}".length + 1
for quote in quotes
qid = quote
continue unless qid[len..] < ID
if qid of posts
uniq[qid[len..]] = true
keys = Object.keys uniq
return unless keys.length is 1
@threaded = "#{g.BOARD}.#{keys[0]}"
@cb = QuoteThreading.nodeinsert
nodeinsert: ->
{posts} = g
qpost = posts[@threaded]
delete @threaded
delete @cb
return false if @thread.OP is qpost or (QuoteThreading.hasRun and !Unread.posts.contains qpost)
qroot = qpost.nodes.root
threadContainer = qroot.nextSibling
if threadContainer?.className isnt 'threadContainer'
threadContainer = $.el 'div',
className: 'threadContainer'
$.after qroot, threadContainer
$.add threadContainer, @nodes.root
return true
toggle: ->
thread = $ '.thread'
replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread
QuoteThreading.enabled = @checked
if @checked
nodes = (Get.postFromNode reply for reply in replies)
Unread.node.call node for node in nodes
QuoteThreading.node node for node in nodes
else
replies.sort (a, b) ->
aID = Number a.id[2..]
bID = Number b.id[2..]
aID - bID
$.add thread, replies
containers = $$ '.threadContainer', thread
$.rm container for container in containers
Unread.update true
kb: ->
control = $.id 'threadingControl'
control.click()

View File

@ -36,7 +36,7 @@ $.extend Array::,
args = arguments args = arguments
for arg in args for arg in args
@push.apply @, arg @push.apply @, arg
return @ return
remove: (object) -> remove: (object) ->
if (index = @indexOf object) > -1 if (index = @indexOf object) > -1
@ -78,6 +78,7 @@ $.extend $,
ajax: (url, callbacks, opts={}) -> ajax: (url, callbacks, opts={}) ->
{type, cred, headers, upCallbacks, form, sync} = opts {type, cred, headers, upCallbacks, form, sync} = opts
r = new XMLHttpRequest() r = new XMLHttpRequest()
r.overrideMimeType 'text/html'
type or= form and 'post' or 'get' type or= form and 'post' or 'get'
r.open type, url, !sync r.open type, url, !sync
for key, val of headers for key, val of headers