diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 59ba54363..bc001cc5c 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -104,7 +104,7 @@ 'use strict'; (function() { - var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, Keybinds, Linkify, Main, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, g, + var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, Keybinds, Linkify, Main, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, g, __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; }, __slice = [].slice, __hasProp = {}.hasOwnProperty, @@ -363,9 +363,7 @@ g = { VERSION: '1.3.2', NAMESPACE: '4chan X.', - boards: {}, - threads: {}, - posts: {} + boards: {} }; $ = function(selector, root) { @@ -807,6 +805,16 @@ }; })(); + $.remove = function(arr, value) { + var i; + i = arr.indexOf(value); + if (i === -1) { + return false; + } + arr.splice(i, 1); + return true; + }; + $$ = function(selector, root) { if (root == null) { root = d.body; @@ -880,8 +888,8 @@ function Board(ID) { this.ID = ID; - this.threads = {}; - this.posts = {}; + this.threads = new SimpleDict; + this.posts = new SimpleDict; g.boards[this] = this; } @@ -900,12 +908,12 @@ this.ID = ID; this.board = board; this.fullID = "" + this.board + "." + this.ID; - this.posts = {}; + this.posts = new SimpleDict; this.isSticky = false; this.isClosed = false; this.postLimit = false; this.fileLimit = false; - g.threads[this.fullID] = board.threads[this] = this; + g.threads.push(this.fullID, board.threads.push(this, this)); } Thread.prototype.setPage = function(pageNum) { @@ -949,14 +957,11 @@ }; Thread.prototype.collect = function() { - var post, postID, _ref; - _ref = this.posts; - for (postID in _ref) { - post = _ref[postID]; - post.collect(); - } - delete g.threads[this.fullID]; - return delete this.board.threads[this]; + this.posts.forEach(function(post) { + return post.collect(); + }); + g.threads.rm(this.fullID); + return this.board.threads.rm(this); }; return Thread; @@ -1035,7 +1040,7 @@ this.parseQuotes(); this.parseFile(that); this.clones = []; - g.posts[this.fullID] = thread.posts[this] = board.posts[this] = this; + g.posts.push(this.fullID, thread.posts.push(this, board.posts.push(this, this))); if (that.isArchived) { this.kill(); } @@ -1205,9 +1210,9 @@ Post.prototype.collect = function() { this.kill(); - delete g.posts[this.fullID]; - delete this.thread.posts[this]; - return delete this.board.posts[this]; + g.posts.rm(this.fullID); + this.thread.posts.rm(this); + return this.board.posts.rm(this); }; Post.prototype.addClone = function(context) { @@ -1605,6 +1610,41 @@ })(); + SimpleDict = (function() { + function SimpleDict() { + this.keys = []; + } + + SimpleDict.prototype.push = function(key, data) { + key = "" + key; + if (!this[key]) { + this.keys.push(key); + } + return this[key] = data; + }; + + SimpleDict.prototype.rm = function(key) { + key = "" + key; + if ($.remove(this.keys, key)) { + return delete this[key]; + } + }; + + SimpleDict.prototype.forEach = function(fn) { + var key, _i, _len, _ref, _results; + _ref = __slice.call(this.keys); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + _results.push(fn(this[key])); + } + return _results; + }; + + return SimpleDict; + + })(); + Polyfill = { init: function() {}, notificationPermission: function() { @@ -2559,7 +2599,7 @@ } } catch (_error) { err = _error; - c.error('Index failure:', err); + c.error("Index failure: " + err.message, err.stack); if (notice) { notice.setType('error'); notice.el.lastElementChild.textContent = 'Index refresh failed.'; @@ -2587,7 +2627,6 @@ return Index.setPage(); }, parseThreadList: function(pages) { - var thread, threadID, _ref, _ref1; Index.pagesNum = pages.length; Index.threadsNumPerPage = pages[0].threads.length; Index.liveThreadData = pages.reduce((function(arr, next) { @@ -2596,13 +2635,12 @@ Index.liveThreadIDs = Index.liveThreadData.map(function(data) { return data.no; }); - _ref = g.BOARD.threads; - for (threadID in _ref) { - thread = _ref[threadID]; - if (_ref1 = thread.ID, __indexOf.call(Index.liveThreadIDs, _ref1) < 0) { - thread.collect(); + g.BOARD.threads.forEach(function(thread) { + var _ref; + if (_ref = thread.ID, __indexOf.call(Index.liveThreadIDs, _ref) < 0) { + return thread.collect(); } - } + }); }, buildThreads: function() { var err, errors, i, posts, thread, threadData, threadRoot, threads, _i, _len, _ref; @@ -2612,20 +2650,20 @@ _ref = Index.liveThreadData; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { threadData = _ref[i]; - threadRoot = Build.thread(g.BOARD, threadData); - Index.nodes.push(threadRoot, $.el('hr')); - if (thread = g.BOARD.threads[threadData.no]) { - thread.setPage(Math.floor(i / Index.threadsNumPerPage)); - thread.setStatus('Sticky', !!threadData.sticky); - thread.setStatus('Closed', !!threadData.closed); - } else { - thread = new Thread(threadData.no, g.BOARD); - threads.push(thread); - } - if (thread.ID in thread.posts) { - continue; - } try { + threadRoot = Build.thread(g.BOARD, threadData); + if (thread = g.BOARD.threads[threadData.no]) { + thread.setPage(Math.floor(i / Index.threadsNumPerPage)); + thread.setStatus('Sticky', !!threadData.sticky); + thread.setStatus('Closed', !!threadData.closed); + } else { + thread = new Thread(threadData.no, g.BOARD); + threads.push(thread); + } + Index.nodes.push(threadRoot, $.el('hr')); + if (thread.ID in thread.posts) { + continue; + } posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD)); } catch (_error) { err = _error; @@ -2633,7 +2671,7 @@ errors = []; } errors.push({ - message: "Parsing of Post No." + thread + " failed. Post will be skipped.", + message: "Parsing of Thread No." + thread + " failed. Thread will be skipped.", error: err }); } @@ -3105,36 +3143,38 @@ }; }, allQuotelinksLinkingTo: function(post) { - var ID, handleQuotes, quote, quotedPost, quotelinks, quoterPost, _i, _len, _ref, _ref1, _ref2; + var fullID, handleQuotes, posts, qPost, quote, quotelinks, _i, _len, _ref; quotelinks = []; - handleQuotes = function(post, type) { + posts = g.posts; + fullID = { + post: post + }; + handleQuotes = function(qPost, type) { var clone, _i, _len, _ref; - quotelinks.push.apply(quotelinks, post.nodes[type]); - _ref = post.clones; + quotelinks.push.apply(quotelinks, qPost.nodes[type]); + _ref = qPost.clones; for (_i = 0, _len = _ref.length; _i < _len; _i++) { clone = _ref[_i]; quotelinks.push.apply(quotelinks, clone.nodes[type]); } }; - _ref = g.posts; - for (ID in _ref) { - quoterPost = _ref[ID]; - if (_ref1 = post.fullID, __indexOf.call(quoterPost.quotes, _ref1) >= 0) { - handleQuotes(quoterPost, 'quotelinks'); + posts.forEach(function(qPost) { + if (__indexOf.call(qPost.quotes, fullID) >= 0) { + return handleQuotes(qPost, 'quotelinks'); } - } + }); if (Conf['Quote Backlinks']) { - _ref2 = post.quotes; - for (_i = 0, _len = _ref2.length; _i < _len; _i++) { - quote = _ref2[_i]; - if (quotedPost = g.posts[quote]) { - handleQuotes(quotedPost, 'backlinks'); + _ref = post.quotes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + quote = _ref[_i]; + if (qPost = posts[quote]) { + handleQuotes(qPost, 'backlinks'); } } } return quotelinks.filter(function(quotelink) { - var boardID, postID, _ref3; - _ref3 = Get.postDataFromLink(quotelink), boardID = _ref3.boardID, postID = _ref3.postID; + var boardID, postID, _ref1; + _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID; return boardID === post.board.ID && postID === post.ID; }); }, @@ -4400,16 +4440,14 @@ } }, apply: function() { - var ID, args, fullID, post, recursive, _ref; + var args, fullID, post, recursive; recursive = arguments[0], post = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : []; fullID = post.fullID; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; + return g.posts.forEach(function(post) { if (__indexOf.call(post.quotes, fullID) >= 0) { - recursive.apply(null, [post].concat(__slice.call(args))); + return recursive.apply(null, [post].concat(__slice.call(args))); } - } + }); } }; @@ -5125,14 +5163,11 @@ return QuoteThreading.force(); }, force: function() { - var ID, post, _ref; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; + return g.posts.forEach(function(post) { if (post.cb) { - post.cb(true); + return post.cb(true); } - } + }); }, node: function() { var keys, len, post, posts, quote, _i, _len, _ref; @@ -5199,20 +5234,18 @@ return true; }, toggle: function() { - var ID, container, containers, nodes, post, posts, thread, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; + var container, containers, nodes, post, posts, thread, _i, _j, _k, _len, _len1, _len2, _ref; if (QuoteThreading.enabled = this.checked) { QuoteThreading.force(); } else { thread = $('.thread'); posts = []; nodes = []; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; + g.posts.forEach(function(post) { if (!(post === post.thread.OP || post.isClone)) { - posts.push(post); + return posts.push(post); } - } + }); posts.sort(function(a, b) { return a.ID - b.ID; }); @@ -5226,9 +5259,9 @@ container = containers[_j]; $.rm(container); } - _ref1 = $$('.threadOP'); - for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { - post = _ref1[_k]; + _ref = $$('.threadOP'); + for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) { + post = _ref[_k]; $.rmClass(post, 'threadOP'); } } @@ -5836,13 +5869,15 @@ return QR.nodes.fileInput.click(); }, generatePostableThreadsList: function() { - var list, options, thread, val; + var list, options, thread, val, _i, _len, _ref; if (!QR.nodes) { return; } list = QR.nodes.thread; options = [list.firstChild]; - for (thread in g.BOARD.threads) { + _ref = g.BOARD.threads.keys; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + thread = _ref[_i]; options.push($.el('option', { value: thread, textContent: "Thread No." + thread @@ -7371,8 +7406,19 @@ return ImageExpand.toggle(Get.postFromNode(this)); }, toggleAll: function() { - var ID, file, func, post, _i, _len, _ref, _ref1; + var func; $.event('CloseMenu'); + func = function(post) { + var file; + file = post.file; + if (!(file && file.isImage && doc.contains(post.nodes.root))) { + return; + } + if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { + return; + } + return $.queueTask(func, post); + }; if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) { ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress'; ImageExpand.EAI.title = 'Contract All Images'; @@ -7382,22 +7428,15 @@ ImageExpand.EAI.title = 'Expand All Images'; func = ImageExpand.contract; } - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; - _ref1 = [post].concat(post.clones); - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - post = _ref1[_i]; - file = post.file; - if (!(file && file.isImage && doc.contains(post.nodes.root))) { - continue; - } - if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { - continue; - } - $.queueTask(func, post); + return g.posts.forEach(function(post) { + var _i, _len, _ref; + func(post); + _ref = post.clones; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + post = _ref[_i]; + func(post); } - } + }); }, setFitness: function() { return (this.checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-')); @@ -7681,6 +7720,10 @@ name: 'Image Replace', cb: this.node }); + Thread.callbacks.push({ + name: 'Image Replace', + cb: this.thread + }); if (!(Conf['Image Prefetching'] && g.VIEW === 'thread')) { return; } @@ -7695,6 +7738,9 @@ order: 104 }); }, + thread: function() { + return ImageLoader.thread = this; + }, node: function() { var URL, img, string, style, thumb, type, _ref, _ref1; if (this.isClone || this.isHidden || this.thread.isHidden || !((_ref = this.file) != null ? _ref.isImage : void 0)) { @@ -7717,14 +7763,10 @@ return img.src = URL; }, toggle: function() { - var enabled, id, post, _ref; + var enabled; enabled = Conf['prefetch'] = this.checked; if (enabled) { - _ref = g.threads["" + g.BOARD.ID + "." + g.THREADID].posts; - for (id in _ref) { - post = _ref[id]; - ImageLoader.node.call(post); - } + ImageLoader.thread.posts.forEach(ImageLoader.node.call); } } }; @@ -8721,17 +8763,15 @@ }); }, node: function() { - var ID, fileCount, post, postCount, _ref; + var fileCount, postCount; postCount = 0; fileCount = 0; - _ref = this.posts; - for (ID in _ref) { - post = _ref[ID]; + this.posts.forEach(function(post) { postCount++; if (post.file) { - fileCount++; + return fileCount++; } - } + }); ThreadStats.thread = this; ThreadStats.fetchPage(); ThreadStats.update(postCount, fileCount); @@ -9112,7 +9152,7 @@ return new Notice('info', "The thread is " + change + ".", 30); }, parse: function(postObjects) { - var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, root, scroll, _i, _len, _ref; + var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, root, scroll, _i, _j, _len, _len1, _ref; OP = postObjects[0]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); @@ -9139,9 +9179,11 @@ } deletedPosts = []; deletedFiles = []; - _ref = ThreadUpdater.thread.posts; - for (ID in _ref) { - post = _ref[ID]; + posts = ThreadUpdater.thread.posts; + _ref = posts.keys; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + ID = _ref[_j]; + post = posts[ID]; ID = +ID; if (__indexOf.call(index, ID) < 0) { post.kill(); @@ -9360,7 +9402,7 @@ _ref = db.data.boards[boardID]; for (threadID in _ref) { data = _ref[threadID]; - if (!data.isDead && !(threadID in g.BOARD.threads)) { + if (!data.isDead && __indexOf.call(g.BOARD.threads.keys, threadID) < 0) { if (Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ boardID: boardID, @@ -9498,7 +9540,7 @@ return div; }, refresh: function() { - var boardID, data, helper, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3; + var boardID, data, helper, list, nodes, refresher, thread, threadID, threads, toggler, watched, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3; nodes = []; _ref = ThreadWatcher.getAll(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -9508,9 +9550,11 @@ list = ThreadWatcher.list; $.rmAll(list); $.add(list, nodes); - _ref2 = g.BOARD.threads; - for (threadID in _ref2) { - thread = _ref2[threadID]; + threads = g.BOARD.threads; + _ref2 = threads.keys; + for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { + threadID = _ref2[_j]; + thread = threads[threadID]; toggler = $('.watch-thread-link', thread.OP.nodes.post); watched = ThreadWatcher.db.get({ boardID: thread.board.ID, @@ -9521,8 +9565,8 @@ toggler.title = "" + helper[1] + " Thread"; } _ref3 = ThreadWatcher.menu.refreshers; - for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { - refresher = _ref3[_j]; + for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { + refresher = _ref3[_k]; refresher(); } }, @@ -9763,16 +9807,14 @@ } }, ready: function() { - var ID, post, posts, _ref; + var posts; $.off(d, '4chanXInitFinished', Unread.ready); posts = []; - _ref = Unread.thread.posts; - for (ID in _ref) { - post = _ref[ID]; + Unread.thread.posts.forEach(function(post) { if (post.isReply) { - posts.push(post); + return posts.push(post); } - } + }); if (!Conf['Quote Threading']) { Unread.addPosts(posts); } @@ -9784,7 +9826,7 @@ } }, scroll: function() { - var down, hash, post, posts, root; + var down, hash, keys, post, posts, root; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } @@ -9799,8 +9841,9 @@ } down = true; } else { - posts = Object.keys(Unread.thread.posts); - root = Unread.thread.posts[posts[posts.length - 1]].nodes.root; + posts = Unread.thread.posts; + keys = posts.keys; + root = posts[keys[keys.length - 1]].nodes.root; } if (Header.getBottomOf(root) < 0) { return Header.scrollTo(root, down); @@ -10700,13 +10743,10 @@ } }, onIndexRefresh: function() { - var thread, threadID, _ref; ExpandThread.disconnect(true); - _ref = g.BOARD.threads; - for (threadID in _ref) { - thread = _ref[threadID]; - ExpandThread.setButton(thread); - } + return g.BOARD.threads.forEach(function(thread) { + return ExpandThread.setButton(thread); + }); }, text: function(status, posts, files) { return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); @@ -11924,13 +11964,11 @@ } }, clean: function() { - var id, posts, thread, threads, _ref; + var posts, threads; posts = g.posts, threads = g.threads; - _ref = g.threads; - for (id in _ref) { - thread = _ref[id]; - thread.collect(); - } + g.threads.forEach(function(thread) { + return thread.collect(); + }); QuoteBacklink.containers = {}; return $.rmAll($('.board')); }, @@ -12876,6 +12914,8 @@ Main = { init: function() { var db, flatten, pathname, _i, _len, _ref, _ref1; + g.threads = new SimpleDict; + g.posts = new SimpleDict; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { diff --git a/builds/crx/script.js b/builds/crx/script.js index ed2fe23e3..7f1b2cea6 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -82,7 +82,7 @@ 'use strict'; (function() { - var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, Keybinds, Linkify, Main, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, g, + var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, Keybinds, Linkify, Main, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, g, __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; }, __slice = [].slice, __hasProp = {}.hasOwnProperty, @@ -341,9 +341,7 @@ g = { VERSION: '1.3.2', NAMESPACE: '4chan X.', - boards: {}, - threads: {}, - posts: {} + boards: {} }; $ = function(selector, root) { @@ -812,6 +810,16 @@ }; })(); + $.remove = function(arr, value) { + var i; + i = arr.indexOf(value); + if (i === -1) { + return false; + } + arr.splice(i, 1); + return true; + }; + $$ = function(selector, root) { if (root == null) { root = d.body; @@ -885,8 +893,8 @@ function Board(ID) { this.ID = ID; - this.threads = {}; - this.posts = {}; + this.threads = new SimpleDict; + this.posts = new SimpleDict; g.boards[this] = this; } @@ -905,12 +913,12 @@ this.ID = ID; this.board = board; this.fullID = "" + this.board + "." + this.ID; - this.posts = {}; + this.posts = new SimpleDict; this.isSticky = false; this.isClosed = false; this.postLimit = false; this.fileLimit = false; - g.threads[this.fullID] = board.threads[this] = this; + g.threads.push(this.fullID, board.threads.push(this, this)); } Thread.prototype.setPage = function(pageNum) { @@ -954,14 +962,11 @@ }; Thread.prototype.collect = function() { - var post, postID, _ref; - _ref = this.posts; - for (postID in _ref) { - post = _ref[postID]; - post.collect(); - } - delete g.threads[this.fullID]; - return delete this.board.threads[this]; + this.posts.forEach(function(post) { + return post.collect(); + }); + g.threads.rm(this.fullID); + return this.board.threads.rm(this); }; return Thread; @@ -1040,7 +1045,7 @@ this.parseQuotes(); this.parseFile(that); this.clones = []; - g.posts[this.fullID] = thread.posts[this] = board.posts[this] = this; + g.posts.push(this.fullID, thread.posts.push(this, board.posts.push(this, this))); if (that.isArchived) { this.kill(); } @@ -1211,9 +1216,9 @@ Post.prototype.collect = function() { this.kill(); - delete g.posts[this.fullID]; - delete this.thread.posts[this]; - return delete this.board.posts[this]; + g.posts.rm(this.fullID); + this.thread.posts.rm(this); + return this.board.posts.rm(this); }; Post.prototype.addClone = function(context) { @@ -1611,6 +1616,41 @@ })(); + SimpleDict = (function() { + function SimpleDict() { + this.keys = []; + } + + SimpleDict.prototype.push = function(key, data) { + key = "" + key; + if (!this[key]) { + this.keys.push(key); + } + return this[key] = data; + }; + + SimpleDict.prototype.rm = function(key) { + key = "" + key; + if ($.remove(this.keys, key)) { + return delete this[key]; + } + }; + + SimpleDict.prototype.forEach = function(fn) { + var key, _i, _len, _ref, _results; + _ref = __slice.call(this.keys); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + _results.push(fn(this[key])); + } + return _results; + }; + + return SimpleDict; + + })(); + Polyfill = { init: function() { this.notificationPermission(); @@ -2569,7 +2609,7 @@ } } catch (_error) { err = _error; - c.error('Index failure:', err); + c.error("Index failure: " + err.message, err.stack); if (notice) { notice.setType('error'); notice.el.lastElementChild.textContent = 'Index refresh failed.'; @@ -2597,7 +2637,6 @@ return Index.setPage(); }, parseThreadList: function(pages) { - var thread, threadID, _ref, _ref1; Index.pagesNum = pages.length; Index.threadsNumPerPage = pages[0].threads.length; Index.liveThreadData = pages.reduce((function(arr, next) { @@ -2606,13 +2645,12 @@ Index.liveThreadIDs = Index.liveThreadData.map(function(data) { return data.no; }); - _ref = g.BOARD.threads; - for (threadID in _ref) { - thread = _ref[threadID]; - if (_ref1 = thread.ID, __indexOf.call(Index.liveThreadIDs, _ref1) < 0) { - thread.collect(); + g.BOARD.threads.forEach(function(thread) { + var _ref; + if (_ref = thread.ID, __indexOf.call(Index.liveThreadIDs, _ref) < 0) { + return thread.collect(); } - } + }); }, buildThreads: function() { var err, errors, i, posts, thread, threadData, threadRoot, threads, _i, _len, _ref; @@ -2622,20 +2660,20 @@ _ref = Index.liveThreadData; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { threadData = _ref[i]; - threadRoot = Build.thread(g.BOARD, threadData); - Index.nodes.push(threadRoot, $.el('hr')); - if (thread = g.BOARD.threads[threadData.no]) { - thread.setPage(Math.floor(i / Index.threadsNumPerPage)); - thread.setStatus('Sticky', !!threadData.sticky); - thread.setStatus('Closed', !!threadData.closed); - } else { - thread = new Thread(threadData.no, g.BOARD); - threads.push(thread); - } - if (thread.ID in thread.posts) { - continue; - } try { + threadRoot = Build.thread(g.BOARD, threadData); + if (thread = g.BOARD.threads[threadData.no]) { + thread.setPage(Math.floor(i / Index.threadsNumPerPage)); + thread.setStatus('Sticky', !!threadData.sticky); + thread.setStatus('Closed', !!threadData.closed); + } else { + thread = new Thread(threadData.no, g.BOARD); + threads.push(thread); + } + Index.nodes.push(threadRoot, $.el('hr')); + if (thread.ID in thread.posts) { + continue; + } posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD)); } catch (_error) { err = _error; @@ -2643,7 +2681,7 @@ errors = []; } errors.push({ - message: "Parsing of Post No." + thread + " failed. Post will be skipped.", + message: "Parsing of Thread No." + thread + " failed. Thread will be skipped.", error: err }); } @@ -3115,36 +3153,38 @@ }; }, allQuotelinksLinkingTo: function(post) { - var ID, handleQuotes, quote, quotedPost, quotelinks, quoterPost, _i, _len, _ref, _ref1, _ref2; + var fullID, handleQuotes, posts, qPost, quote, quotelinks, _i, _len, _ref; quotelinks = []; - handleQuotes = function(post, type) { + posts = g.posts; + fullID = { + post: post + }; + handleQuotes = function(qPost, type) { var clone, _i, _len, _ref; - quotelinks.push.apply(quotelinks, post.nodes[type]); - _ref = post.clones; + quotelinks.push.apply(quotelinks, qPost.nodes[type]); + _ref = qPost.clones; for (_i = 0, _len = _ref.length; _i < _len; _i++) { clone = _ref[_i]; quotelinks.push.apply(quotelinks, clone.nodes[type]); } }; - _ref = g.posts; - for (ID in _ref) { - quoterPost = _ref[ID]; - if (_ref1 = post.fullID, __indexOf.call(quoterPost.quotes, _ref1) >= 0) { - handleQuotes(quoterPost, 'quotelinks'); + posts.forEach(function(qPost) { + if (__indexOf.call(qPost.quotes, fullID) >= 0) { + return handleQuotes(qPost, 'quotelinks'); } - } + }); if (Conf['Quote Backlinks']) { - _ref2 = post.quotes; - for (_i = 0, _len = _ref2.length; _i < _len; _i++) { - quote = _ref2[_i]; - if (quotedPost = g.posts[quote]) { - handleQuotes(quotedPost, 'backlinks'); + _ref = post.quotes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + quote = _ref[_i]; + if (qPost = posts[quote]) { + handleQuotes(qPost, 'backlinks'); } } } return quotelinks.filter(function(quotelink) { - var boardID, postID, _ref3; - _ref3 = Get.postDataFromLink(quotelink), boardID = _ref3.boardID, postID = _ref3.postID; + var boardID, postID, _ref1; + _ref1 = Get.postDataFromLink(quotelink), boardID = _ref1.boardID, postID = _ref1.postID; return boardID === post.board.ID && postID === post.ID; }); }, @@ -4403,16 +4443,14 @@ } }, apply: function() { - var ID, args, fullID, post, recursive, _ref; + var args, fullID, post, recursive; recursive = arguments[0], post = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : []; fullID = post.fullID; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; + return g.posts.forEach(function(post) { if (__indexOf.call(post.quotes, fullID) >= 0) { - recursive.apply(null, [post].concat(__slice.call(args))); + return recursive.apply(null, [post].concat(__slice.call(args))); } - } + }); } }; @@ -5128,14 +5166,11 @@ return QuoteThreading.force(); }, force: function() { - var ID, post, _ref; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; + return g.posts.forEach(function(post) { if (post.cb) { - post.cb(true); + return post.cb(true); } - } + }); }, node: function() { var keys, len, post, posts, quote, _i, _len, _ref; @@ -5202,20 +5237,18 @@ return true; }, toggle: function() { - var ID, container, containers, nodes, post, posts, thread, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; + var container, containers, nodes, post, posts, thread, _i, _j, _k, _len, _len1, _len2, _ref; if (QuoteThreading.enabled = this.checked) { QuoteThreading.force(); } else { thread = $('.thread'); posts = []; nodes = []; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; + g.posts.forEach(function(post) { if (!(post === post.thread.OP || post.isClone)) { - posts.push(post); + return posts.push(post); } - } + }); posts.sort(function(a, b) { return a.ID - b.ID; }); @@ -5229,9 +5262,9 @@ container = containers[_j]; $.rm(container); } - _ref1 = $$('.threadOP'); - for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { - post = _ref1[_k]; + _ref = $$('.threadOP'); + for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) { + post = _ref[_k]; $.rmClass(post, 'threadOP'); } } @@ -5844,13 +5877,15 @@ return QR.nodes.fileInput.click(); }, generatePostableThreadsList: function() { - var list, options, thread, val; + var list, options, thread, val, _i, _len, _ref; if (!QR.nodes) { return; } list = QR.nodes.thread; options = [list.firstChild]; - for (thread in g.BOARD.threads) { + _ref = g.BOARD.threads.keys; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + thread = _ref[_i]; options.push($.el('option', { value: thread, textContent: "Thread No." + thread @@ -7354,8 +7389,19 @@ return ImageExpand.toggle(Get.postFromNode(this)); }, toggleAll: function() { - var ID, file, func, post, _i, _len, _ref, _ref1; + var func; $.event('CloseMenu'); + func = function(post) { + var file; + file = post.file; + if (!(file && file.isImage && doc.contains(post.nodes.root))) { + return; + } + if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { + return; + } + return $.queueTask(func, post); + }; if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) { ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress'; ImageExpand.EAI.title = 'Contract All Images'; @@ -7365,22 +7411,15 @@ ImageExpand.EAI.title = 'Expand All Images'; func = ImageExpand.contract; } - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; - _ref1 = [post].concat(post.clones); - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - post = _ref1[_i]; - file = post.file; - if (!(file && file.isImage && doc.contains(post.nodes.root))) { - continue; - } - if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { - continue; - } - $.queueTask(func, post); + return g.posts.forEach(function(post) { + var _i, _len, _ref; + func(post); + _ref = post.clones; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + post = _ref[_i]; + func(post); } - } + }); }, setFitness: function() { return (this.checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-')); @@ -7664,6 +7703,10 @@ name: 'Image Replace', cb: this.node }); + Thread.callbacks.push({ + name: 'Image Replace', + cb: this.thread + }); if (!(Conf['Image Prefetching'] && g.VIEW === 'thread')) { return; } @@ -7678,6 +7721,9 @@ order: 104 }); }, + thread: function() { + return ImageLoader.thread = this; + }, node: function() { var URL, img, string, style, thumb, type, _ref, _ref1; if (this.isClone || this.isHidden || this.thread.isHidden || !((_ref = this.file) != null ? _ref.isImage : void 0)) { @@ -7700,14 +7746,10 @@ return img.src = URL; }, toggle: function() { - var enabled, id, post, _ref; + var enabled; enabled = Conf['prefetch'] = this.checked; if (enabled) { - _ref = g.threads["" + g.BOARD.ID + "." + g.THREADID].posts; - for (id in _ref) { - post = _ref[id]; - ImageLoader.node.call(post); - } + ImageLoader.thread.posts.forEach(ImageLoader.node.call); } } }; @@ -8704,17 +8746,15 @@ }); }, node: function() { - var ID, fileCount, post, postCount, _ref; + var fileCount, postCount; postCount = 0; fileCount = 0; - _ref = this.posts; - for (ID in _ref) { - post = _ref[ID]; + this.posts.forEach(function(post) { postCount++; if (post.file) { - fileCount++; + return fileCount++; } - } + }); ThreadStats.thread = this; ThreadStats.fetchPage(); ThreadStats.update(postCount, fileCount); @@ -9095,7 +9135,7 @@ return new Notice('info', "The thread is " + change + ".", 30); }, parse: function(postObjects) { - var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, root, scroll, _i, _len, _ref; + var ID, OP, count, deletedFiles, deletedPosts, files, index, key, node, num, post, postObject, posts, root, scroll, _i, _j, _len, _len1, _ref; OP = postObjects[0]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); @@ -9122,9 +9162,11 @@ } deletedPosts = []; deletedFiles = []; - _ref = ThreadUpdater.thread.posts; - for (ID in _ref) { - post = _ref[ID]; + posts = ThreadUpdater.thread.posts; + _ref = posts.keys; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + ID = _ref[_j]; + post = posts[ID]; ID = +ID; if (__indexOf.call(index, ID) < 0) { post.kill(); @@ -9343,7 +9385,7 @@ _ref = db.data.boards[boardID]; for (threadID in _ref) { data = _ref[threadID]; - if (!data.isDead && !(threadID in g.BOARD.threads)) { + if (!data.isDead && __indexOf.call(g.BOARD.threads.keys, threadID) < 0) { if (Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ boardID: boardID, @@ -9481,7 +9523,7 @@ return div; }, refresh: function() { - var boardID, data, helper, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3; + var boardID, data, helper, list, nodes, refresher, thread, threadID, threads, toggler, watched, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3; nodes = []; _ref = ThreadWatcher.getAll(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -9491,9 +9533,11 @@ list = ThreadWatcher.list; $.rmAll(list); $.add(list, nodes); - _ref2 = g.BOARD.threads; - for (threadID in _ref2) { - thread = _ref2[threadID]; + threads = g.BOARD.threads; + _ref2 = threads.keys; + for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { + threadID = _ref2[_j]; + thread = threads[threadID]; toggler = $('.watch-thread-link', thread.OP.nodes.post); watched = ThreadWatcher.db.get({ boardID: thread.board.ID, @@ -9504,8 +9548,8 @@ toggler.title = "" + helper[1] + " Thread"; } _ref3 = ThreadWatcher.menu.refreshers; - for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { - refresher = _ref3[_j]; + for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { + refresher = _ref3[_k]; refresher(); } }, @@ -9746,16 +9790,14 @@ } }, ready: function() { - var ID, post, posts, _ref; + var posts; $.off(d, '4chanXInitFinished', Unread.ready); posts = []; - _ref = Unread.thread.posts; - for (ID in _ref) { - post = _ref[ID]; + Unread.thread.posts.forEach(function(post) { if (post.isReply) { - posts.push(post); + return posts.push(post); } - } + }); if (!Conf['Quote Threading']) { Unread.addPosts(posts); } @@ -9767,7 +9809,7 @@ } }, scroll: function() { - var down, hash, post, posts, root; + var down, hash, keys, post, posts, root; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } @@ -9782,8 +9824,9 @@ } down = true; } else { - posts = Object.keys(Unread.thread.posts); - root = Unread.thread.posts[posts[posts.length - 1]].nodes.root; + posts = Unread.thread.posts; + keys = posts.keys; + root = posts[keys[keys.length - 1]].nodes.root; } if (Header.getBottomOf(root) < 0) { return Header.scrollTo(root, down); @@ -10689,13 +10732,10 @@ } }, onIndexRefresh: function() { - var thread, threadID, _ref; ExpandThread.disconnect(true); - _ref = g.BOARD.threads; - for (threadID in _ref) { - thread = _ref[threadID]; - ExpandThread.setButton(thread); - } + return g.BOARD.threads.forEach(function(thread) { + return ExpandThread.setButton(thread); + }); }, text: function(status, posts, files) { return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); @@ -11913,13 +11953,11 @@ } }, clean: function() { - var id, posts, thread, threads, _ref; + var posts, threads; posts = g.posts, threads = g.threads; - _ref = g.threads; - for (id in _ref) { - thread = _ref[id]; - thread.collect(); - } + g.threads.forEach(function(thread) { + return thread.collect(); + }); QuoteBacklink.containers = {}; return $.rmAll($('.board')); }, @@ -12863,6 +12901,8 @@ Main = { init: function() { var db, flatten, pathname, _i, _len, _ref, _ref1; + g.threads = new SimpleDict; + g.posts = new SimpleDict; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { diff --git a/src/Filtering/Recursive.coffee b/src/Filtering/Recursive.coffee index da5496ceb..6d84dd78b 100755 --- a/src/Filtering/Recursive.coffee +++ b/src/Filtering/Recursive.coffee @@ -32,7 +32,6 @@ Recursive = apply: (recursive, post, args...) -> {fullID} = post - for ID, post of g.posts + g.posts.forEach (post) -> if fullID in post.quotes recursive post, args... - return diff --git a/src/General/Get.coffee b/src/General/Get.coffee index 605a0f651..0f41b7f9e 100755 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -40,22 +40,28 @@ Get = allQuotelinksLinkingTo: (post) -> # Get quotelinks & backlinks linking to the given post. quotelinks = [] - handleQuotes = (post, type) -> - quotelinks.push post.nodes[type]... - quotelinks.push clone.nodes[type]... for clone in post.clones + {posts} = g + fullID = {post} + handleQuotes = (qPost, type) -> + quotelinks.push qPost.nodes[type]... + quotelinks.push clone.nodes[type]... for clone in qPost.clones return # First: # In every posts, # if it did quote this post, # get all their backlinks. - handleQuotes quoterPost, 'quotelinks' for ID, quoterPost of g.posts when post.fullID in quoterPost.quotes + posts.forEach (qPost) -> + if fullID in qPost.quotes + handleQuotes qPost, 'quotelinks' + # Second: # If we have quote backlinks: # in all posts this post quoted # and their clones, # get all of their backlinks. if Conf['Quote Backlinks'] - handleQuotes quotedPost, 'backlinks' for quote in post.quotes when quotedPost = g.posts[quote] + handleQuotes qPost, 'backlinks' for quote in post.quotes when qPost = posts[quote] + # Third: # Filter out irrelevant quotelinks. quotelinks.filter (quotelink) -> diff --git a/src/General/Globals.coffee b/src/General/Globals.coffee index d5d09eaa6..e301f775a 100755 --- a/src/General/Globals.coffee +++ b/src/General/Globals.coffee @@ -5,6 +5,4 @@ doc = d.documentElement g = VERSION: '<%= version %>' NAMESPACE: '<%= meta.name %>.' - boards: {} - threads: {} - posts: {} + boards: {} \ No newline at end of file diff --git a/src/General/Index.coffee b/src/General/Index.coffee index b293af5de..5438d1bdc 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -277,7 +277,7 @@ Index = else if req.status is 304 and pageNum? Index.pageNav pageNum catch err - c.error 'Index failure:', err + c.error "Index failure: #{err.message}", err.stack # network error or non-JSON content for example. if notice notice.setType 'error' @@ -308,8 +308,8 @@ Index = Index.threadsNumPerPage = pages[0].threads.length Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), [] Index.liveThreadIDs = Index.liveThreadData.map (data) -> data.no - for threadID, thread of g.BOARD.threads when thread.ID not in Index.liveThreadIDs - thread.collect() + g.BOARD.threads.forEach (thread) -> + thread.collect() unless thread.ID in Index.liveThreadIDs return buildThreads: -> @@ -317,23 +317,23 @@ Index = threads = [] posts = [] for threadData, i in Index.liveThreadData - threadRoot = Build.thread g.BOARD, threadData - Index.nodes.push threadRoot, $.el 'hr' - if thread = g.BOARD.threads[threadData.no] - thread.setPage Math.floor i / Index.threadsNumPerPage - thread.setStatus 'Sticky', !!threadData.sticky - thread.setStatus 'Closed', !!threadData.closed - else - thread = new Thread threadData.no, g.BOARD - threads.push thread - continue if thread.ID of thread.posts try + threadRoot = Build.thread g.BOARD, threadData + if thread = g.BOARD.threads[threadData.no] + thread.setPage Math.floor i / Index.threadsNumPerPage + thread.setStatus 'Sticky', !!threadData.sticky + thread.setStatus 'Closed', !!threadData.closed + else + thread = new Thread threadData.no, g.BOARD + threads.push thread + Index.nodes.push threadRoot, $.el 'hr' + continue if thread.ID of thread.posts posts.push new Post $('.opContainer', threadRoot), thread, g.BOARD catch err # Skip posts that we failed to parse. errors = [] unless errors errors.push - message: "Parsing of Post No.#{thread} failed. Post will be skipped." + message: "Parsing of Thread No.#{thread} failed. Thread will be skipped." error: err Main.handleErrors errors if errors diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 7ccb225ac..5603518a0 100755 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -1,5 +1,8 @@ Main = init: -> + g.threads = new SimpleDict + g.posts = new SimpleDict + pathname = location.pathname.split '/' g.BOARD = new Board pathname[1] return if g.BOARD.ID in ['z', 'fk'] diff --git a/src/General/Navigate.coffee b/src/General/Navigate.coffee index 43659ebc9..95a0d9d2d 100644 --- a/src/General/Navigate.coffee +++ b/src/General/Navigate.coffee @@ -34,7 +34,7 @@ Navigate = {posts, threads} = g # Garbage collection - thread.collect() for id, thread of g.threads + g.threads.forEach (thread) -> thread.collect() QuoteBacklink.containers = {} diff --git a/src/General/lib/$.coffee b/src/General/lib/$.coffee index 87807ac9c..3e45ed12a 100755 --- a/src/General/lib/$.coffee +++ b/src/General/lib/$.coffee @@ -404,5 +404,11 @@ $.set = do -> return <% } %> +$.remove = (arr, value) -> + i = arr.indexOf value + return false if i is -1 + arr.splice i, 1 + true + $$ = (selector, root=d.body) -> [root.querySelectorAll(selector)...] diff --git a/src/General/lib/board.class b/src/General/lib/board.class index 23d9211b5..4b4fdd720 100755 --- a/src/General/lib/board.class +++ b/src/General/lib/board.class @@ -2,7 +2,7 @@ class Board toString: -> @ID constructor: (@ID) -> - @threads = {} - @posts = {} + @threads = new SimpleDict + @posts = new SimpleDict g.boards[@] = @ \ No newline at end of file diff --git a/src/General/lib/classes.coffee b/src/General/lib/classes.coffee index 5654ff914..eba1788af 100755 --- a/src/General/lib/classes.coffee +++ b/src/General/lib/classes.coffee @@ -5,4 +5,5 @@ <%= grunt.file.read('src/General/lib/clone.class') %> <%= grunt.file.read('src/General/lib/databoard.class') %> <%= grunt.file.read('src/General/lib/notice.class') %> -<%= grunt.file.read('src/General/lib/randomaccesslist.class') %> \ No newline at end of file +<%= grunt.file.read('src/General/lib/randomaccesslist.class') %> +<%= grunt.file.read('src/General/lib/simpledict.class') %> \ No newline at end of file diff --git a/src/General/lib/post.class b/src/General/lib/post.class index b1a3f4beb..a5e6ebc6f 100755 --- a/src/General/lib/post.class +++ b/src/General/lib/post.class @@ -54,7 +54,7 @@ class Post @parseFile that @clones = [] - g.posts[@fullID] = thread.posts[@] = board.posts[@] = @ + g.posts.push @fullID, thread.posts.push @, board.posts.push @, @ @kill() if that.isArchived parseComment: -> @@ -208,9 +208,9 @@ class Post collect: -> @kill() - delete g.posts[@fullID] - delete @thread.posts[@] - delete @board.posts[@] + g.posts.rm @fullID + @thread.posts.rm @ + @board.posts.rm @ addClone: (context) -> new Clone @, context diff --git a/src/General/lib/simpledict.class b/src/General/lib/simpledict.class new file mode 100644 index 000000000..7dddffc69 --- /dev/null +++ b/src/General/lib/simpledict.class @@ -0,0 +1,14 @@ +class SimpleDict + constructor: -> + @keys = [] + + push: (key, data) -> + key = "#{key}" + @keys.push key unless @[key] + @[key] = data + + rm: (key) -> + key = "#{key}" + delete @[key] if $.remove @keys, key + + forEach: (fn) -> fn @[key] for key in [@keys...] diff --git a/src/General/lib/thread.class b/src/General/lib/thread.class index da1bfbb03..48153a54d 100755 --- a/src/General/lib/thread.class +++ b/src/General/lib/thread.class @@ -4,13 +4,13 @@ class Thread constructor: (@ID, @board) -> @fullID = "#{@board}.#{@ID}" - @posts = {} + @posts = new SimpleDict @isSticky = false @isClosed = false @postLimit = false @fileLimit = false - g.threads[@fullID] = board.threads[@] = @ + g.threads.push @fullID, board.threads.push @, @ setPage: (pageNum) -> icon = $ '.page-num', @OP.nodes.post @@ -44,6 +44,6 @@ class Thread @timeOfDeath = Date.now() collect: -> - post.collect() for postID, post of @posts - delete g.threads[@fullID] - delete @board.threads[@] + @posts.forEach (post) -> post.collect() + g.threads.rm @fullID + @board.threads.rm @ diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index 135a50481..b4e1f1ed5 100755 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -32,6 +32,14 @@ ImageExpand = ImageExpand.toggle Get.postFromNode @ toggleAll: -> $.event 'CloseMenu' + func = (post) -> + {file} = post + return unless file and file.isImage and doc.contains post.nodes.root + if ImageExpand.on and + (!Conf['Expand spoilers'] and file.isSpoiler or + Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0) + return + $.queueTask func, post if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut' ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress' ImageExpand.EAI.title = 'Contract All Images' @@ -40,16 +48,10 @@ ImageExpand = ImageExpand.EAI.className = 'expand-all-shortcut fa fa-expand' ImageExpand.EAI.title = 'Expand All Images' func = ImageExpand.contract - for ID, post of g.posts - for post in [post].concat post.clones - {file} = post - continue unless file and file.isImage and doc.contains post.nodes.root - if ImageExpand.on and - (!Conf['Expand spoilers'] and file.isSpoiler or - Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0) - continue - $.queueTask func, post - return + g.posts.forEach (post) -> + func post + func post for post in post.clones + return setFitness: -> (if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-' diff --git a/src/Images/ImageLoader.coffee b/src/Images/ImageLoader.coffee index 66220873e..dd9d8af99 100755 --- a/src/Images/ImageLoader.coffee +++ b/src/Images/ImageLoader.coffee @@ -7,6 +7,10 @@ ImageLoader = name: 'Image Replace' cb: @node + Thread.callbacks.push + name: 'Image Replace' + cb: @thread + return unless Conf['Image Prefetching'] and g.VIEW is 'thread' prefetch = $.el 'label', @@ -19,6 +23,9 @@ ImageLoader = type: 'header' el: prefetch order: 104 + + thread: -> + ImageLoader.thread = @ node: -> return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage @@ -38,5 +45,5 @@ ImageLoader = toggle: -> enabled = Conf['prefetch'] = @checked if enabled - ImageLoader.node.call post for id, post of g.threads["#{g.BOARD.ID}.#{g.THREADID}"].posts + ImageLoader.thread.posts.forEach ImageLoader.node.call return \ No newline at end of file diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index bed91a082..69accf4af 100755 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -19,9 +19,8 @@ ExpandThread = onIndexRefresh: -> ExpandThread.disconnect true - for threadID, thread of g.BOARD.threads + g.BOARD.threads.forEach (thread) -> ExpandThread.setButton thread - return text: (status, posts, files) -> "#{status} #{posts} post#{if posts > 1 then 's' else ''}" + diff --git a/src/Monitoring/ThreadStats.coffee b/src/Monitoring/ThreadStats.coffee index 6d9e46fd4..6212f6021 100755 --- a/src/Monitoring/ThreadStats.coffee +++ b/src/Monitoring/ThreadStats.coffee @@ -26,7 +26,7 @@ ThreadStats = node: -> postCount = 0 fileCount = 0 - for ID, post of @posts + @posts.forEach (post) -> postCount++ fileCount++ if post.file ThreadStats.thread = @ diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index 59ae4d6c3..43b61e8da 100755 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -290,7 +290,9 @@ ThreadUpdater = deletedFiles = [] # Check for deleted posts/files. - for ID, post of ThreadUpdater.thread.posts + {posts} = ThreadUpdater.thread + for ID in posts.keys + post = posts[ID] # XXX tmp fix for 4chan's racing condition # giving us false-positive dead posts. # continue if post.isDead diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index ca6d15873..80197bcf3 100755 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -96,7 +96,7 @@ ThreadWatcher = onIndexRefresh: -> {db} = ThreadWatcher boardID = g.BOARD.ID - for threadID, data of db.data.boards[boardID] when not data.isDead and threadID not of g.BOARD.threads + for threadID, data of db.data.boards[boardID] when not data.isDead and threadID not in g.BOARD.threads.keys if Conf['Auto Prune'] ThreadWatcher.db.delete {boardID, threadID} else @@ -180,7 +180,9 @@ ThreadWatcher = $.rmAll list $.add list, nodes - for threadID, thread of g.BOARD.threads + {threads} = g.BOARD + for threadID in threads.keys + thread = threads[threadID] toggler = $ '.watch-thread-link', thread.OP.nodes.post watched = ThreadWatcher.db.get {boardID: thread.board.ID, threadID} helper = if watched then ['addClass', 'Unwatch'] else ['rmClass', 'Watch'] diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 3c23e6de3..ba9cb5fca 100755 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -42,7 +42,7 @@ Unread = ready: -> $.off d, '4chanXInitFinished', Unread.ready posts = [] - posts.push post for ID, post of Unread.thread.posts when post.isReply + Unread.thread.posts.forEach (post) -> posts.push post if post.isReply Unread.addPosts posts unless Conf['Quote Threading'] QuoteThreading.force() if Conf['Quote Threading'] Unread.scroll() if Conf['Scroll to Last Read Post'] @@ -58,8 +58,9 @@ Unread = down = true else # Scroll to the last read post. - posts = Object.keys Unread.thread.posts - {root} = Unread.thread.posts[posts[posts.length - 1]].nodes + {posts} = Unread.thread + {keys} = posts + {root} = posts[keys[keys.length - 1]].nodes # Scroll to the target unless we scrolled past it. Header.scrollTo root, down if Header.getBottomOf(root) < 0 diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 6f158edd9..122fb6927 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -326,7 +326,7 @@ QR = return unless QR.nodes list = QR.nodes.thread options = [list.firstChild] - for thread of g.BOARD.threads + for thread in g.BOARD.threads.keys options.push $.el 'option', value: thread textContent: "Thread No.#{thread}" diff --git a/src/Quotelinks/QuoteThreading.coffee b/src/Quotelinks/QuoteThreading.coffee index 85a15cfd2..077dde1e7 100755 --- a/src/Quotelinks/QuoteThreading.coffee +++ b/src/Quotelinks/QuoteThreading.coffee @@ -42,8 +42,8 @@ QuoteThreading = QuoteThreading.force() force: -> - post.cb true for ID, post of g.posts when post.cb - return + g.posts.forEach (post) -> + post.cb true if post.cb node: -> {posts} = g @@ -106,8 +106,10 @@ QuoteThreading = thread = $('.thread') posts = [] nodes = [] + + g.posts.forEach (post) -> + posts.push post unless post is post.thread.OP or post.isClone - posts.push post for ID, post of g.posts when not (post is post.thread.OP or post.isClone) posts.sort (a, b) -> a.ID - b.ID nodes.push post.nodes.root for post in posts