diff --git a/builds/appchan-x.js b/builds/appchan-x.js index 384a9e4a5..f1b155773 100644 --- a/builds/appchan-x.js +++ b/builds/appchan-x.js @@ -19,7 +19,7 @@ // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg== // ==/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/ * * Copyright (c) 2009-2011 James Campos @@ -42,7 +42,7 @@ */ (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, __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; }; @@ -133,7 +133,8 @@ 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], '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: { @@ -2498,7 +2499,6 @@ arg = args[_i]; this.push.apply(this, arg); } - return this; }, remove: function(object) { var index; @@ -2565,6 +2565,7 @@ } type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync; r = new XMLHttpRequest(); + r.overrideMimeType('text/html'); type || (type = form && 'post' || 'get'); r.open(type, url, !sync); for (key in headers) { @@ -7066,7 +7067,7 @@ hashScroll: function() { var post; - if (!(post = $.id(this.location.hash.slice(1)))) { + if (!(post = this.location.hash.slice(1))) { return; } if ((Get.postFromRoot(post)).isHidden) { @@ -7331,7 +7332,8 @@ if (quote) { 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) { var range, selEnd, selStart, value; @@ -8329,7 +8331,7 @@ return $.after(root, [$.tn(' '), icon]); }, 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]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; @@ -8337,7 +8339,6 @@ ThreadUpdater.updateThreadStatus('Closed', OP); ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit; - nodes = []; posts = []; index = []; files = []; @@ -8354,7 +8355,6 @@ } count++; node = Build.postFromObject(postObject, ThreadUpdater.thread.board); - nodes.push(node); posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); } deletedPosts = []; @@ -8395,7 +8395,19 @@ ThreadUpdater.lastPost = posts[count - 1].ID; Main.callbackNodes(Post, posts); 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 (Conf['Bottom Scroll']) { doc.scrollTop = d.body.clientHeight; @@ -8715,26 +8727,27 @@ return arr.splice(0, i); }, 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) { return; } height = doc.clientHeight; - _ref = Unread.posts; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - post = _ref[i]; - bottom = post.nodes.root.getBoundingClientRect().bottom; - if (bottom > height) { - break; + posts = Unread.posts; + read = []; + i = posts.length; + while (post = posts[--i]) { + _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; + if ((bottom < height) && (top > 0)) { + ID = post.ID; + posts.remove(post); } } - if (!i) { + if (!ID) { return; } - Unread.lastReadPost = Unread.posts[i - 1].ID; + Unread.lastReadPost = ID; Unread.saveLastReadPost(); - Unread.posts.splice(0, i); Unread.readArray(Unread.postsQuotingYou); if (e) { return Unread.update(); @@ -8886,6 +8899,7 @@ QR.cleanNotifications(); d.activeElement.blur(); $.rmClass(QR.nodes.el, 'dump'); + $.toggleClass($('.qr-shortcut'), 'disabled'); _ref = QR.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { i = _ref[_i]; @@ -9141,7 +9155,8 @@ com.setSelectionRange(range, range); com.focus(); QR.selected.save(com); - return QR.selected.save(thread); + QR.selected.save(thread); + return $.rmClass($('.qr-shortcut'), 'disabled'); }, characterCount: function() { 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: '' + }); + 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 = { init: function() { if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) { @@ -13434,6 +13594,7 @@ 'Thread Excerpt': ThreadExcerpt, 'Favicon': Favicon, 'Unread': Unread, + 'Quote Threading': QuoteThreading, 'Thread Updater': ThreadUpdater, 'Thread Stats': ThreadStats, 'Thread Watcher': ThreadWatcher, diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index 47d396709..f24fc4911 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -19,7 +19,7 @@ // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg== // ==/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/ * * Copyright (c) 2009-2011 James Campos @@ -42,7 +42,7 @@ */ (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, __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; }; @@ -134,7 +134,8 @@ 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], '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: { @@ -2495,7 +2496,6 @@ arg = args[_i]; this.push.apply(this, arg); } - return this; }, remove: function(object) { var index; @@ -2562,6 +2562,7 @@ } type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync; r = new XMLHttpRequest(); + r.overrideMimeType('text/html'); type || (type = form && 'post' || 'get'); r.open(type, url, !sync); for (key in headers) { @@ -7058,7 +7059,7 @@ hashScroll: function() { var post; - if (!(post = $.id(this.location.hash.slice(1)))) { + if (!(post = this.location.hash.slice(1))) { return; } if ((Get.postFromRoot(post)).isHidden) { @@ -7323,7 +7324,8 @@ if (quote) { 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) { var range, selEnd, selStart, value; @@ -8321,7 +8323,7 @@ return $.after(root, [$.tn(' '), icon]); }, 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]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; @@ -8329,7 +8331,6 @@ ThreadUpdater.updateThreadStatus('Closed', OP); ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit; - nodes = []; posts = []; index = []; files = []; @@ -8346,7 +8347,6 @@ } count++; node = Build.postFromObject(postObject, ThreadUpdater.thread.board); - nodes.push(node); posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); } deletedPosts = []; @@ -8387,7 +8387,19 @@ ThreadUpdater.lastPost = posts[count - 1].ID; Main.callbackNodes(Post, posts); 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 (Conf['Bottom Scroll']) { doc.scrollTop = d.body.clientHeight; @@ -8707,26 +8719,27 @@ return arr.splice(0, i); }, 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) { return; } height = doc.clientHeight; - _ref = Unread.posts; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - post = _ref[i]; - bottom = post.nodes.root.getBoundingClientRect().bottom; - if (bottom > height) { - break; + posts = Unread.posts; + read = []; + i = posts.length; + while (post = posts[--i]) { + _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; + if ((bottom < height) && (top > 0)) { + ID = post.ID; + posts.remove(post); } } - if (!i) { + if (!ID) { return; } - Unread.lastReadPost = Unread.posts[i - 1].ID; + Unread.lastReadPost = ID; Unread.saveLastReadPost(); - Unread.posts.splice(0, i); Unread.readArray(Unread.postsQuotingYou); if (e) { return Unread.update(); @@ -8878,6 +8891,7 @@ QR.cleanNotifications(); d.activeElement.blur(); $.rmClass(QR.nodes.el, 'dump'); + $.toggleClass($('.qr-shortcut'), 'disabled'); _ref = QR.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { i = _ref[_i]; @@ -9133,7 +9147,8 @@ com.setSelectionRange(range, range); com.focus(); QR.selected.save(com); - return QR.selected.save(thread); + QR.selected.save(thread); + return $.rmClass($('.qr-shortcut'), 'disabled'); }, characterCount: function() { 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: '' + }); + 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 = { init: function() { if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) { @@ -13453,6 +13613,7 @@ 'Thread Excerpt': ThreadExcerpt, 'Favicon': Favicon, 'Unread': Unread, + 'Quote Threading': QuoteThreading, 'Thread Updater': ThreadUpdater, 'Thread Stats': ThreadStats, 'Thread Watcher': ThreadWatcher, diff --git a/builds/crx.crx b/builds/crx.crx index f74a224f2..3132bf8d3 100644 Binary files a/builds/crx.crx and b/builds/crx.crx differ diff --git a/builds/crx/script.js b/builds/crx/script.js index a898fc6b5..32fc5cdd7 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,5 +1,5 @@ (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, __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; }; @@ -90,7 +90,8 @@ 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], '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: { @@ -2451,7 +2452,6 @@ arg = args[_i]; this.push.apply(this, arg); } - return this; }, remove: function(object) { var index; @@ -2518,6 +2518,7 @@ } type = opts.type, cred = opts.cred, headers = opts.headers, upCallbacks = opts.upCallbacks, form = opts.form, sync = opts.sync; r = new XMLHttpRequest(); + r.overrideMimeType('text/html'); type || (type = form && 'post' || 'get'); r.open(type, url, !sync); for (key in headers) { @@ -6980,7 +6981,7 @@ hashScroll: function() { var post; - if (!(post = $.id(this.location.hash.slice(1)))) { + if (!(post = this.location.hash.slice(1))) { return; } if ((Get.postFromRoot(post)).isHidden) { @@ -7245,7 +7246,8 @@ if (quote) { 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) { var range, selEnd, selStart, value; @@ -8243,7 +8245,7 @@ return $.after(root, [$.tn(' '), icon]); }, 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]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; @@ -8251,7 +8253,6 @@ ThreadUpdater.updateThreadStatus('Closed', OP); ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit; - nodes = []; posts = []; index = []; files = []; @@ -8268,7 +8269,6 @@ } count++; node = Build.postFromObject(postObject, ThreadUpdater.thread.board); - nodes.push(node); posts.push(new Post(node, ThreadUpdater.thread, ThreadUpdater.thread.board)); } deletedPosts = []; @@ -8309,7 +8309,19 @@ ThreadUpdater.lastPost = posts[count - 1].ID; Main.callbackNodes(Post, posts); 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 (Conf['Bottom Scroll']) { d.body.scrollTop = d.body.clientHeight; @@ -8629,26 +8641,27 @@ return arr.splice(0, i); }, 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) { return; } height = doc.clientHeight; - _ref = Unread.posts; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - post = _ref[i]; - bottom = post.nodes.root.getBoundingClientRect().bottom; - if (bottom > height) { - break; + posts = Unread.posts; + read = []; + i = posts.length; + while (post = posts[--i]) { + _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; + if ((bottom < height) && (top > 0)) { + ID = post.ID; + posts.remove(post); } } - if (!i) { + if (!ID) { return; } - Unread.lastReadPost = Unread.posts[i - 1].ID; + Unread.lastReadPost = ID; Unread.saveLastReadPost(); - Unread.posts.splice(0, i); Unread.readArray(Unread.postsQuotingYou); if (e) { return Unread.update(); @@ -8806,6 +8819,7 @@ QR.cleanNotifications(); d.activeElement.blur(); $.rmClass(QR.nodes.el, 'dump'); + $.toggleClass($('.qr-shortcut'), 'disabled'); _ref = QR.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { i = _ref[_i]; @@ -9061,7 +9075,8 @@ com.setSelectionRange(range, range); com.focus(); QR.selected.save(com); - return QR.selected.save(thread); + QR.selected.save(thread); + return $.rmClass($('.qr-shortcut'), 'disabled'); }, characterCount: function() { 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: '' + }); + 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 = { init: function() { if (g.VIEW === 'catalog' || !Conf['Mark Quotes of You'] || !Conf['Quick Reply']) { @@ -13356,6 +13516,7 @@ 'Thread Excerpt': ThreadExcerpt, 'Favicon': Favicon, 'Unread': Unread, + 'Quote Threading': QuoteThreading, 'Thread Updater': ThreadUpdater, 'Thread Stats': ThreadStats, 'Thread Watcher': ThreadWatcher, diff --git a/src/code/config.coffee b/src/code/config.coffee index d8fe6d7a4..33cc65475 100644 --- a/src/code/config.coffee +++ b/src/code/config.coffee @@ -301,6 +301,10 @@ Config = true 'Add \'(Cross-thread)\' to cross-threads quotes.' ] + 'Quote Threading': [ + false + 'Thread conversations' + ] imageExpansion: 'Fit width': [ diff --git a/src/code/main.coffee b/src/code/main.coffee index 4489e7a0a..1faf21988 100644 --- a/src/code/main.coffee +++ b/src/code/main.coffee @@ -137,6 +137,7 @@ Main = 'Thread Excerpt': ThreadExcerpt 'Favicon': Favicon 'Unread': Unread + 'Quote Threading': QuoteThreading 'Thread Updater': ThreadUpdater 'Thread Stats': ThreadStats 'Thread Watcher': ThreadWatcher diff --git a/src/code/misc/header.coffee b/src/code/misc/header.coffee index d26b618f3..421201384 100644 --- a/src/code/misc/header.coffee +++ b/src/code/misc/header.coffee @@ -177,7 +177,7 @@ Header = (if hide then $.addClass else $.rmClass) Header.nav, 'autohide' hashScroll: -> - return unless post = $.id @location.hash[1..] + return unless post = @location.hash[1..] return if (Get.postFromRoot post).isHidden Header.scrollToPost post diff --git a/src/code/misc/keybinds.coffee b/src/code/misc/keybinds.coffee index 761adb231..d03f89761 100644 --- a/src/code/misc/keybinds.coffee +++ b/src/code/misc/keybinds.coffee @@ -134,6 +134,7 @@ Keybinds = if quote QR.quote.call $ 'input', $('.post.highlight', thread) or thread do QR.nodes.com.focus + $.rmClass $('.qr-shortcut'), 'disabled' tags: (tag, ta) -> value = ta.value diff --git a/src/code/monitoring/threadupdater.coffee b/src/code/monitoring/threadupdater.coffee index 34189dc15..09e36ec0c 100644 --- a/src/code/monitoring/threadupdater.coffee +++ b/src/code/monitoring/threadupdater.coffee @@ -219,7 +219,6 @@ ThreadUpdater = ThreadUpdater.thread.postLimit = !!OP.bumplimit ThreadUpdater.thread.fileLimit = !!OP.imagelimit - nodes = [] # post container elements posts = [] # post objects index = [] # existing posts files = [] # existing files @@ -233,7 +232,6 @@ ThreadUpdater = # Insert new posts, not older ones. count++ node = Build.postFromObject postObject, ThreadUpdater.thread.board - nodes.push node posts.push new Post node, ThreadUpdater.thread, ThreadUpdater.thread.board deletedPosts = [] @@ -259,6 +257,7 @@ ThreadUpdater = unless count ThreadUpdater.set 'status', null, null ThreadUpdater.outdateCount++ + else ThreadUpdater.set 'status', "+#{count}", 'new' ThreadUpdater.outdateCount = 0 @@ -272,7 +271,15 @@ ThreadUpdater = scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and 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 Conf['Bottom Scroll'] <% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop = d.body.clientHeight diff --git a/src/code/monitoring/unread.coffee b/src/code/monitoring/unread.coffee index eb3f2ba1d..53368f1af 100644 --- a/src/code/monitoring/unread.coffee +++ b/src/code/monitoring/unread.coffee @@ -100,15 +100,20 @@ Unread = read: (e) -> return if d.hidden or !Unread.posts.length - height = doc.clientHeight - for post, i in Unread.posts - {bottom} = post.nodes.root.getBoundingClientRect() - break if bottom > height # post is not completely read - return unless i + height = doc.clientHeight + {posts} = Unread + read = [] + i = posts.length - 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.posts.splice 0, i Unread.readArray Unread.postsQuotingYou Unread.update() if e diff --git a/src/code/posting/qr.coffee b/src/code/posting/qr.coffee index 9afe7ea34..e2f878ffc 100644 --- a/src/code/posting/qr.coffee +++ b/src/code/posting/qr.coffee @@ -81,6 +81,7 @@ QR = QR.cleanNotifications() d.activeElement.blur() $.rmClass QR.nodes.el, 'dump' + $.toggleClass $('.qr-shortcut'), 'disabled' for i in QR.posts QR.posts[0].rm() QR.cooldown.auto = false @@ -302,6 +303,8 @@ QR = QR.selected.save com QR.selected.save thread + $.rmClass $('.qr-shortcut'), 'disabled' + characterCount: -> counter = QR.nodes.charCount count = QR.nodes.com.textLength diff --git a/src/code/quoting/quotethreading.coffee b/src/code/quoting/quotethreading.coffee new file mode 100644 index 000000000..185e70ed8 --- /dev/null +++ b/src/code/quoting/quotethreading.coffee @@ -0,0 +1,97 @@ +### + <3 aeosynth +### + +QuoteThreading = + init: -> + return unless Conf['Quote Threading'] and g.VIEW is 'thread' + + @enabled = true + @controls = $.el 'span', + innerHTML: '' + + 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() \ No newline at end of file diff --git a/src/lib/$.coffee b/src/lib/$.coffee index 350a73714..bfefd49b5 100644 --- a/src/lib/$.coffee +++ b/src/lib/$.coffee @@ -36,7 +36,7 @@ $.extend Array::, args = arguments for arg in args @push.apply @, arg - return @ + return remove: (object) -> if (index = @indexOf object) > -1 @@ -78,6 +78,7 @@ $.extend $, ajax: (url, callbacks, opts={}) -> {type, cred, headers, upCallbacks, form, sync} = opts r = new XMLHttpRequest() + r.overrideMimeType 'text/html' type or= form and 'post' or 'get' r.open type, url, !sync for key, val of headers