diff --git a/CHANGELOG.md b/CHANGELOG.md index 174ffbb41..d4ee7cabc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### v1.14.5 +**v1.14.5.2** *(2018-12-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.2/builds/4chan-X-noupdate.crx)] +- All Thread Watcher functionality is now supported on and across Tinyboard/vichan sites, including auto-updating, the unread count, and lighting up upon replies, with the exception that threads from sites without JSON APIs will not be updated when the thread watcher is refreshed. +- The `Unread Count`, `Unread Line`, `Scroll to Last Read Post`, and `Desktop Notifications` are now supported on Tinyboard/vichan sites. +- Replies made AJAX on Tinyboard/vichan sites are now marked as yours. + **v1.14.5.1** *(2018-12-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.1/builds/4chan-X-noupdate.crx)] - Support style switcher and non-default styles on Tinyboard. diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx index 722d68e70..184e8f4e9 100644 Binary files a/builds/4chan-X-beta.crx and b/builds/4chan-X-beta.crx differ diff --git a/builds/4chan-X-beta.meta.js b/builds/4chan-X-beta.meta.js index b05ac408c..d01249435 100644 --- a/builds/4chan-X-beta.meta.js +++ b/builds/4chan-X-beta.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X beta -// @version 1.14.5.1 +// @version 1.14.5.2 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X-beta.user.js b/builds/4chan-X-beta.user.js index cc0170540..6a27f2b73 100644 --- a/builds/4chan-X-beta.user.js +++ b/builds/4chan-X-beta.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X beta -// @version 1.14.5.1 +// @version 1.14.5.2 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -183,7 +183,7 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, UnreadIndex, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, Tinyboard, UI, Unread, UnreadIndex, Volume; var Conf, E, c, d, doc, docSet, g; @@ -198,7 +198,7 @@ docSet = function() { }; g = { - VERSION: '1.14.5.1', + VERSION: '1.14.5.2', NAMESPACE: '4chan X.', boards: {} }; @@ -2317,6 +2317,9 @@ span.hide-announcement {\n\ margin: 0;\n\ border-color: rgb(255,0,0);\n\ }\n\ +.unread-line + br {\n\ + display: none;\n\ +}\n\ .unread-mark-read {\n\ float: right;\n\ clear: both;\n\ @@ -2419,6 +2422,9 @@ span.hide-announcement {\n\ -webkit-flex: 0 1 auto;\n\ flex: 0 1 auto;\n\ }\n\ +.replies-quoting-you > a, #watcher-link.replies-quoting-you {\n\ + color: #F00;\n\ +}\n\ #thread-watcher a {\n\ text-decoration: none;\n\ }\n\ @@ -5472,7 +5478,7 @@ CrossOrigin = (function() { } return delete callbacks[url]; }; - return function(url, cb, bypassCache) { + return function(url, cb, bypassCache, timeout) { var req; if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { if (bypassCache) { @@ -5493,19 +5499,21 @@ CrossOrigin = (function() { } if (bypassCache) { delete results[url]; - } - if (results[url]) { - cb.call(results[url]); - return; - } - if (callbacks[url]) { - callbacks[url].push(cb); - return; + } else { + if (results[url]) { + cb.call(results[url]); + return; + } + if (callbacks[url]) { + callbacks[url].push(cb); + return; + } } callbacks[url] = [cb]; return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)({ method: "GET", url: url + '', + timeout: timeout, onload: function(xhr) { var response, status, statusText; status = xhr.status, statusText = xhr.statusText; @@ -5525,6 +5533,9 @@ CrossOrigin = (function() { }, onabort: function() { return failure(url); + }, + ontimeout: function() { + return failure(url); } }); }; @@ -5928,13 +5939,10 @@ DataBoard = (function() { })(this), cb); }; - DataBoard.prototype.setLastChecked = function(siteID) { - if (siteID == null) { - siteID = Site.hostname; - } + DataBoard.prototype.setLastChecked = function() { return this.save((function(_this) { return function() { - return _this.data[siteID].lastChecked = Date.now(); + return _this.data.lastChecked = Date.now(); }; })(this)); }; @@ -6504,9 +6512,10 @@ Post = (function() { this.boardID = this.board.ID; this.fullID = this.board + "." + this.ID; this.context = this; + this.isReply = this.ID !== this.threadID; root.dataset.fullID = this.fullID; this.nodes = this.parseNodes(root); - if (!(this.isReply = this.ID !== this.threadID)) { + if (!this.isReply) { this.thread.OP = this; ref = ['isSticky', 'isClosed', 'isArchived']; for (j = 0, len = ref.length; j < len; j++) { @@ -6568,6 +6577,7 @@ Post = (function() { info = $(s.infoRoot, post); nodes = { root: root, + bottom: this.isReply || !Site.isOPContainerThread ? root : $(s.opBottom, root), post: post, info: info, comment: $(s.comment, post), @@ -7280,7 +7290,8 @@ SW = {}; SW.tinyboard = { isOPContainerThread: true, - disabledFeatures: ['Board Configuration', 'Normalize URL', 'Captcha Configuration', 'Image Host Rewriting', 'Index Generator', 'Announcement Hiding', 'Fourchan thingies', 'Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Pass Link', 'Index Generator (Menu)', 'Report Link', 'Delete Link', 'Edit Link', 'Archive Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Fappe Tyme', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Unread', 'Quote Threading', 'Thread Stats', 'Thread Updater', 'Mark New IPs', 'Banner', 'Flash Features', 'Reply Pruning'], + mayLackJSON: true, + disabledFeatures: ['Board Configuration', 'Normalize URL', 'Captcha Configuration', 'Image Host Rewriting', 'Index Generator', 'Announcement Hiding', 'Fourchan thingies', 'Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Pass Link', 'Index Generator (Menu)', 'Report Link', 'Delete Link', 'Edit Link', 'Archive Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Fappe Tyme', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Quote Threading', 'Thread Stats', 'Thread Updater', 'Mark New IPs', 'Banner', 'Flash Features', 'Reply Pruning'], detect: function() { var i, len, m, properties, ref, root, script; ref = $$('script:not([src])', d.head); @@ -7306,6 +7317,16 @@ SW = {}; var boardID, ref, siteID, threadID; siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/res/" + threadID + ".html"; + }, + threadJSON: function(arg) { + var boardID, ref, root, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/res/" + threadID + ".json"; + } else { + return ''; + } } }, selectors: { @@ -7314,6 +7335,7 @@ SW = {}; threadDivider: 'div[id^="thread_"] > hr:last-of-type', summary: '.omitted', postContainer: '.reply', + opBottom: '.op', infoRoot: '.intro', info: { subject: '.subject', @@ -7348,7 +7370,8 @@ SW = {}; postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]' }, regexp: { - quotelink: /\/([^\/]+)\/res\/(\d+)\.html#(\d+)$/ + quotelink: /\/([^\/]+)\/res\/(\d+)\.html#(\d+)$/, + quotelinkHTML: /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)\.html#(\d+)"/g }, bgColoredEl: function() { return $.el('div', { @@ -7415,6 +7438,11 @@ SW = {}; var boardID, threadID; boardID = arg.boardID, threadID = arg.threadID; return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + }, + threadJSON: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json"; } }, selectors: { @@ -7462,7 +7490,8 @@ SW = {}; postContainer: 'div[contains(@class,"postContainer")]' }, regexp: { - quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/ + quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/, + quotelinkHTML: /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g }, bgColoredEl: function() { return $.el('div', { @@ -7588,6 +7617,9 @@ SW = {}; node = ref1[k]; $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); } + }, + hasCORS: function(url) { + return url.split('/').slice(0, 3).join('/') === location.protocol + '//a.4cdn.org'; } }; @@ -18874,6 +18906,47 @@ Time = (function() { }).call(this); +Tinyboard = (function() { + var Tinyboard; + + Tinyboard = { + init: function() { + if (Site.software !== 'tinyboard') { + return; + } + if (g.VIEW === 'thread') { + return Main.ready(function() { + return $.global(function() { + var boardID, ref, threadID; + ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID; + threadID = +threadID; + return window.$(document).on('new_post', function(e, post) { + var detail, event, postID; + postID = +post.id.match(/\d*$/)[0]; + detail = { + boardID: boardID, + threadID: threadID, + postID: postID + }; + event = new CustomEvent('QRPostSuccessful', { + bubbles: true, + detail: detail + }); + return document.dispatchEvent(event); + }); + }, { + boardID: g.BOARD.ID, + threadID: g.THREADID + }); + }); + } + } + }; + + return Tinyboard; + +}).call(this); + Favicon = (function() { var Favicon; @@ -19764,9 +19837,6 @@ ThreadWatcher = (function() { $.on(sc, 'click', this.toggleWatcher); $.on(this.refreshButton, 'click', this.buttonFetchAll); $.on(this.closeButton, 'click', this.toggleWatcher); - if (Site.software !== 'yotsuba') { - this.refreshButton.hidden = true; - } this.menu.addHeaderMenuEntry(); $.onExists(doc, 'body', this.addDialog); switch (g.VIEW) { @@ -20039,9 +20109,6 @@ ThreadWatcher = (function() { }, fetchAuto: function() { var db, interval, now, ref; - if (Site.software !== 'yotsuba') { - return; - } clearTimeout(ThreadWatcher.timeout); if (!Conf['Auto Update Thread Watcher']) { return; @@ -20049,7 +20116,7 @@ ThreadWatcher = (function() { db = ThreadWatcher.db; interval = ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] ? 5 * $.MINUTE : 2 * $.HOUR; now = Date.now(); - if (!((now - interval < (ref = db.data[Site.hostname].lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { + if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { ThreadWatcher.fetchAllStatus(); db.setLastChecked(); } @@ -20064,9 +20131,6 @@ ThreadWatcher = (function() { }, fetchAllStatus: function() { var db, dbs, i, len1, n, results; - if (Site.software !== 'yotsuba') { - return; - } dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) { return x; }); @@ -20088,66 +20152,96 @@ ThreadWatcher = (function() { return results; }, fetchStatus: function(thread, force) { - var boardID, data, req, siteID, threadID; + var base, boardID, data, ref, ref1, req, siteID, software, threadID, url; siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data; - if (!(Site.software === 'yotsuba' && siteID === Site.hostname)) { + software = (ref = Conf['siteProperties'][siteID]) != null ? ref.software : void 0; + url = (ref1 = SW[software]) != null ? typeof (base = ref1.urls).threadJSON === "function" ? base.threadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!url) { return; } if (data.isDead && !force) { return; } + if (data.last === -1) { + return; + } if (ThreadWatcher.requests.length === 0) { ThreadWatcher.status.textContent = '...'; $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); } - req = $.ajax(location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { - onloadend: function() { + if ((typeof Site.hasCORS === "function" ? Site.hasCORS(url) : void 0) || url.split('/').slice(0, 3).join('/') === location.origin) { + req = $.ajax(url, { + onloadend: function() { + return ThreadWatcher.parseStatus.call(this, thread); + }, + timeout: $.MINUTE + }); + } else { + req = { + abort: function() { + return req.aborted = true; + } + }; + CrossOrigin.json(url, function() { + if (req.aborted) { + return; + } return ThreadWatcher.parseStatus.call(this, thread); - }, - timeout: $.MINUTE - }, { - whenModified: force ? false : 'ThreadWatcher' - }); + }, true, $.MINUTE); + } return ThreadWatcher.requests.push(req); }, parseStatus: function(arg) { - var boardID, data, i, isDead, lastReadPost, len1, match, postObj, quotesYou, quotingYou, ref, ref1, ref2, regexp, threadID, unread, youOP; - boardID = arg.boardID, threadID = arg.threadID, data = arg.data; + var boardID, data, i, isDead, last, lastReadPost, len1, match, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, siteID, software, threadID, unread, updated, youOP; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, data = arg.data; ThreadWatcher.fetched++; if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { ThreadWatcher.clearRequests(); } else { ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; } + software = (ref = Conf['siteProperties'][siteID]) != null ? ref.software : void 0; if (this.status === 200 && this.response) { + last = this.response.posts[this.response.posts.length - 1].no; isDead = !!this.response.posts[0].archived; if (isDead && Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID }); ThreadWatcher.refresh(); return; } + if (last === data.last && isDead === data.isDead) { + return; + } lastReadPost = ThreadWatcher.unreaddb.get({ + siteID: siteID, boardID: boardID, threadID: threadID, defaultValue: 0 }); unread = 0; quotingYou = false; - youOP = !!((ref = QuoteYou.db) != null ? ref.get({ + youOP = !!((ref1 = QuoteYou.db) != null ? ref1.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: threadID }) : void 0); - ref1 = this.response.posts; - for (i = 0, len1 = ref1.length; i < len1; i++) { - postObj = ref1[i]; + ref2 = this.response.posts; + for (i = 0, len1 = ref2.length; i < len1; i++) { + postObj = ref2[i]; if (!(postObj.no > lastReadPost)) { continue; } - if ((ref2 = QuoteYou.db) != null ? ref2.get({ + if ((ref3 = QuoteYou.db) != null ? ref3.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: postObj.no @@ -20163,9 +20257,11 @@ ThreadWatcher = (function() { continue; } quotesYou = false; - regexp = /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g; + regexp = SW[software].regexp.quotelinkHTML; + regexp.lastIndex = 0; while (match = regexp.exec(postObj.com)) { if (QuoteYou.db.get({ + siteID: siteID, boardID: match[1] || boardID, threadID: match[2] || threadID, postID: match[3] || match[2] || threadID @@ -20178,26 +20274,41 @@ ThreadWatcher = (function() { quotingYou = true; } } - if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) { - ThreadWatcher.db.extend({ - boardID: boardID, - threadID: threadID, - val: { - isDead: isDead, - unread: unread, - quotingYou: quotingYou - } - }); + updated = isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou; + ThreadWatcher.db.extend({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + val: { + last: last, + isDead: isDead, + unread: unread, + quotingYou: quotingYou + } + }); + if (updated) { return ThreadWatcher.refresh(); } } else if (this.status === 404) { - if (Conf['Auto Prune']) { + if (SW[software].mayLackJSON && (data.last == null)) { + ThreadWatcher.db.extend({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + val: { + last: -1 + }, + rm: ['unread', 'quotingYou'] + }); + } else if (Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID }); } else { ThreadWatcher.db.extend({ + siteID: siteID, boardID: boardID, threadID: threadID, val: { @@ -20258,7 +20369,7 @@ ThreadWatcher = (function() { title: excerpt, className: 'watcher-link' }); - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && software === 'yotsuba' && (data.unread != null)) { + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) { count = $.el('span', { textContent: "(" + data.unread + ")", className: 'watcher-unread' @@ -20280,7 +20391,7 @@ ThreadWatcher = (function() { if (data.isDead) { $.addClass(div, 'dead-thread'); } - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && software === 'yotsuba') { + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { if (data.unread === 0) { $.addClass(div, 'replies-read'); } @@ -20701,17 +20812,17 @@ Unread = (function() { } }, scroll: function() { - var hash, position, root; + var bottom, hash, position; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } position = Unread.positionPrev(); while (position) { - root = position.data.nodes.root; - if (!root.getBoundingClientRect().height) { + bottom = position.data.nodes.bottom; + if (!bottom.getBoundingClientRect().height) { position = position.prev; } else { - Header.scrollToIfNeeded(root, true); + Header.scrollToIfNeeded(bottom, true); break; } } @@ -20781,7 +20892,7 @@ Unread = (function() { icon: Favicon.logo }); notif.onclick = function() { - Header.scrollToIfNeeded(post.nodes.root, true); + Header.scrollToIfNeeded(post.nodes.bottom, true); return window.focus(); }; return notif.onshow = function() { @@ -20810,7 +20921,7 @@ Unread = (function() { return Unread.update(); }, read: $.debounce(100, function(e) { - var ID, count, data, ref, root; + var ID, bottom, count, data, ref; if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) { Unread.saveLastReadPost(); } @@ -20820,8 +20931,8 @@ Unread = (function() { count = 0; while (Unread.position) { ref = Unread.position, ID = ref.ID, data = ref.data; - root = data.nodes.root; - if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) { + bottom = data.nodes.bottom; + if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) { break; } count++; @@ -20875,7 +20986,7 @@ Unread = (function() { } if (Unread.hr.hidden || d.hidden || (force === true)) { if ((Unread.linePosition = Unread.positionPrev())) { - $.after(Unread.linePosition.data.nodes.root, Unread.hr); + $.after(Unread.linePosition.data.nodes.bottom, Unread.hr); } else { $.rm(Unread.hr); } @@ -20893,7 +21004,7 @@ Unread = (function() { d.title = "" + titleQuotingYou + titleCount + titleDead; } Unread.saveThreadWatcherCount(); - if (Conf['Unread Favicon']) { + if (Conf['Unread Favicon'] && Site.software === 'yotsuba') { isDead = Unread.thread.isDead; Favicon.el.href = countQuotingYou ? Favicon[isDead ? 'unreadDeadY' : 'unreadY'] : count ? Favicon[isDead ? 'unreadDead' : 'unread'] : Favicon[isDead ? 'dead' : 'default']; return $.add(d.head, Favicon.el); @@ -25580,7 +25691,7 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] + features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] }; return Main; diff --git a/builds/4chan-X-noupdate.crx b/builds/4chan-X-noupdate.crx index 0dc30db50..afab139e1 100644 Binary files a/builds/4chan-X-noupdate.crx and b/builds/4chan-X-noupdate.crx differ diff --git a/builds/4chan-X-noupdate.user.js b/builds/4chan-X-noupdate.user.js index 6d043de2c..894bfa2be 100644 --- a/builds/4chan-X-noupdate.user.js +++ b/builds/4chan-X-noupdate.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.14.5.1 +// @version 1.14.5.2 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -183,7 +183,7 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, UnreadIndex, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, Tinyboard, UI, Unread, UnreadIndex, Volume; var Conf, E, c, d, doc, docSet, g; @@ -198,7 +198,7 @@ docSet = function() { }; g = { - VERSION: '1.14.5.1', + VERSION: '1.14.5.2', NAMESPACE: '4chan X.', boards: {} }; @@ -2317,6 +2317,9 @@ span.hide-announcement {\n\ margin: 0;\n\ border-color: rgb(255,0,0);\n\ }\n\ +.unread-line + br {\n\ + display: none;\n\ +}\n\ .unread-mark-read {\n\ float: right;\n\ clear: both;\n\ @@ -2419,6 +2422,9 @@ span.hide-announcement {\n\ -webkit-flex: 0 1 auto;\n\ flex: 0 1 auto;\n\ }\n\ +.replies-quoting-you > a, #watcher-link.replies-quoting-you {\n\ + color: #F00;\n\ +}\n\ #thread-watcher a {\n\ text-decoration: none;\n\ }\n\ @@ -5472,7 +5478,7 @@ CrossOrigin = (function() { } return delete callbacks[url]; }; - return function(url, cb, bypassCache) { + return function(url, cb, bypassCache, timeout) { var req; if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { if (bypassCache) { @@ -5493,19 +5499,21 @@ CrossOrigin = (function() { } if (bypassCache) { delete results[url]; - } - if (results[url]) { - cb.call(results[url]); - return; - } - if (callbacks[url]) { - callbacks[url].push(cb); - return; + } else { + if (results[url]) { + cb.call(results[url]); + return; + } + if (callbacks[url]) { + callbacks[url].push(cb); + return; + } } callbacks[url] = [cb]; return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)({ method: "GET", url: url + '', + timeout: timeout, onload: function(xhr) { var response, status, statusText; status = xhr.status, statusText = xhr.statusText; @@ -5525,6 +5533,9 @@ CrossOrigin = (function() { }, onabort: function() { return failure(url); + }, + ontimeout: function() { + return failure(url); } }); }; @@ -5928,13 +5939,10 @@ DataBoard = (function() { })(this), cb); }; - DataBoard.prototype.setLastChecked = function(siteID) { - if (siteID == null) { - siteID = Site.hostname; - } + DataBoard.prototype.setLastChecked = function() { return this.save((function(_this) { return function() { - return _this.data[siteID].lastChecked = Date.now(); + return _this.data.lastChecked = Date.now(); }; })(this)); }; @@ -6504,9 +6512,10 @@ Post = (function() { this.boardID = this.board.ID; this.fullID = this.board + "." + this.ID; this.context = this; + this.isReply = this.ID !== this.threadID; root.dataset.fullID = this.fullID; this.nodes = this.parseNodes(root); - if (!(this.isReply = this.ID !== this.threadID)) { + if (!this.isReply) { this.thread.OP = this; ref = ['isSticky', 'isClosed', 'isArchived']; for (j = 0, len = ref.length; j < len; j++) { @@ -6568,6 +6577,7 @@ Post = (function() { info = $(s.infoRoot, post); nodes = { root: root, + bottom: this.isReply || !Site.isOPContainerThread ? root : $(s.opBottom, root), post: post, info: info, comment: $(s.comment, post), @@ -7280,7 +7290,8 @@ SW = {}; SW.tinyboard = { isOPContainerThread: true, - disabledFeatures: ['Board Configuration', 'Normalize URL', 'Captcha Configuration', 'Image Host Rewriting', 'Index Generator', 'Announcement Hiding', 'Fourchan thingies', 'Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Pass Link', 'Index Generator (Menu)', 'Report Link', 'Delete Link', 'Edit Link', 'Archive Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Fappe Tyme', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Unread', 'Quote Threading', 'Thread Stats', 'Thread Updater', 'Mark New IPs', 'Banner', 'Flash Features', 'Reply Pruning'], + mayLackJSON: true, + disabledFeatures: ['Board Configuration', 'Normalize URL', 'Captcha Configuration', 'Image Host Rewriting', 'Index Generator', 'Announcement Hiding', 'Fourchan thingies', 'Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Pass Link', 'Index Generator (Menu)', 'Report Link', 'Delete Link', 'Edit Link', 'Archive Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Fappe Tyme', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Quote Threading', 'Thread Stats', 'Thread Updater', 'Mark New IPs', 'Banner', 'Flash Features', 'Reply Pruning'], detect: function() { var i, len, m, properties, ref, root, script; ref = $$('script:not([src])', d.head); @@ -7306,6 +7317,16 @@ SW = {}; var boardID, ref, siteID, threadID; siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/res/" + threadID + ".html"; + }, + threadJSON: function(arg) { + var boardID, ref, root, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/res/" + threadID + ".json"; + } else { + return ''; + } } }, selectors: { @@ -7314,6 +7335,7 @@ SW = {}; threadDivider: 'div[id^="thread_"] > hr:last-of-type', summary: '.omitted', postContainer: '.reply', + opBottom: '.op', infoRoot: '.intro', info: { subject: '.subject', @@ -7348,7 +7370,8 @@ SW = {}; postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]' }, regexp: { - quotelink: /\/([^\/]+)\/res\/(\d+)\.html#(\d+)$/ + quotelink: /\/([^\/]+)\/res\/(\d+)\.html#(\d+)$/, + quotelinkHTML: /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)\.html#(\d+)"/g }, bgColoredEl: function() { return $.el('div', { @@ -7415,6 +7438,11 @@ SW = {}; var boardID, threadID; boardID = arg.boardID, threadID = arg.threadID; return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + }, + threadJSON: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json"; } }, selectors: { @@ -7462,7 +7490,8 @@ SW = {}; postContainer: 'div[contains(@class,"postContainer")]' }, regexp: { - quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/ + quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/, + quotelinkHTML: /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g }, bgColoredEl: function() { return $.el('div', { @@ -7588,6 +7617,9 @@ SW = {}; node = ref1[k]; $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); } + }, + hasCORS: function(url) { + return url.split('/').slice(0, 3).join('/') === location.protocol + '//a.4cdn.org'; } }; @@ -18874,6 +18906,47 @@ Time = (function() { }).call(this); +Tinyboard = (function() { + var Tinyboard; + + Tinyboard = { + init: function() { + if (Site.software !== 'tinyboard') { + return; + } + if (g.VIEW === 'thread') { + return Main.ready(function() { + return $.global(function() { + var boardID, ref, threadID; + ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID; + threadID = +threadID; + return window.$(document).on('new_post', function(e, post) { + var detail, event, postID; + postID = +post.id.match(/\d*$/)[0]; + detail = { + boardID: boardID, + threadID: threadID, + postID: postID + }; + event = new CustomEvent('QRPostSuccessful', { + bubbles: true, + detail: detail + }); + return document.dispatchEvent(event); + }); + }, { + boardID: g.BOARD.ID, + threadID: g.THREADID + }); + }); + } + } + }; + + return Tinyboard; + +}).call(this); + Favicon = (function() { var Favicon; @@ -19764,9 +19837,6 @@ ThreadWatcher = (function() { $.on(sc, 'click', this.toggleWatcher); $.on(this.refreshButton, 'click', this.buttonFetchAll); $.on(this.closeButton, 'click', this.toggleWatcher); - if (Site.software !== 'yotsuba') { - this.refreshButton.hidden = true; - } this.menu.addHeaderMenuEntry(); $.onExists(doc, 'body', this.addDialog); switch (g.VIEW) { @@ -20039,9 +20109,6 @@ ThreadWatcher = (function() { }, fetchAuto: function() { var db, interval, now, ref; - if (Site.software !== 'yotsuba') { - return; - } clearTimeout(ThreadWatcher.timeout); if (!Conf['Auto Update Thread Watcher']) { return; @@ -20049,7 +20116,7 @@ ThreadWatcher = (function() { db = ThreadWatcher.db; interval = ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] ? 5 * $.MINUTE : 2 * $.HOUR; now = Date.now(); - if (!((now - interval < (ref = db.data[Site.hostname].lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { + if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { ThreadWatcher.fetchAllStatus(); db.setLastChecked(); } @@ -20064,9 +20131,6 @@ ThreadWatcher = (function() { }, fetchAllStatus: function() { var db, dbs, i, len1, n, results; - if (Site.software !== 'yotsuba') { - return; - } dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) { return x; }); @@ -20088,66 +20152,96 @@ ThreadWatcher = (function() { return results; }, fetchStatus: function(thread, force) { - var boardID, data, req, siteID, threadID; + var base, boardID, data, ref, ref1, req, siteID, software, threadID, url; siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data; - if (!(Site.software === 'yotsuba' && siteID === Site.hostname)) { + software = (ref = Conf['siteProperties'][siteID]) != null ? ref.software : void 0; + url = (ref1 = SW[software]) != null ? typeof (base = ref1.urls).threadJSON === "function" ? base.threadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!url) { return; } if (data.isDead && !force) { return; } + if (data.last === -1) { + return; + } if (ThreadWatcher.requests.length === 0) { ThreadWatcher.status.textContent = '...'; $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); } - req = $.ajax(location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { - onloadend: function() { + if ((typeof Site.hasCORS === "function" ? Site.hasCORS(url) : void 0) || url.split('/').slice(0, 3).join('/') === location.origin) { + req = $.ajax(url, { + onloadend: function() { + return ThreadWatcher.parseStatus.call(this, thread); + }, + timeout: $.MINUTE + }); + } else { + req = { + abort: function() { + return req.aborted = true; + } + }; + CrossOrigin.json(url, function() { + if (req.aborted) { + return; + } return ThreadWatcher.parseStatus.call(this, thread); - }, - timeout: $.MINUTE - }, { - whenModified: force ? false : 'ThreadWatcher' - }); + }, true, $.MINUTE); + } return ThreadWatcher.requests.push(req); }, parseStatus: function(arg) { - var boardID, data, i, isDead, lastReadPost, len1, match, postObj, quotesYou, quotingYou, ref, ref1, ref2, regexp, threadID, unread, youOP; - boardID = arg.boardID, threadID = arg.threadID, data = arg.data; + var boardID, data, i, isDead, last, lastReadPost, len1, match, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, siteID, software, threadID, unread, updated, youOP; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, data = arg.data; ThreadWatcher.fetched++; if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { ThreadWatcher.clearRequests(); } else { ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; } + software = (ref = Conf['siteProperties'][siteID]) != null ? ref.software : void 0; if (this.status === 200 && this.response) { + last = this.response.posts[this.response.posts.length - 1].no; isDead = !!this.response.posts[0].archived; if (isDead && Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID }); ThreadWatcher.refresh(); return; } + if (last === data.last && isDead === data.isDead) { + return; + } lastReadPost = ThreadWatcher.unreaddb.get({ + siteID: siteID, boardID: boardID, threadID: threadID, defaultValue: 0 }); unread = 0; quotingYou = false; - youOP = !!((ref = QuoteYou.db) != null ? ref.get({ + youOP = !!((ref1 = QuoteYou.db) != null ? ref1.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: threadID }) : void 0); - ref1 = this.response.posts; - for (i = 0, len1 = ref1.length; i < len1; i++) { - postObj = ref1[i]; + ref2 = this.response.posts; + for (i = 0, len1 = ref2.length; i < len1; i++) { + postObj = ref2[i]; if (!(postObj.no > lastReadPost)) { continue; } - if ((ref2 = QuoteYou.db) != null ? ref2.get({ + if ((ref3 = QuoteYou.db) != null ? ref3.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: postObj.no @@ -20163,9 +20257,11 @@ ThreadWatcher = (function() { continue; } quotesYou = false; - regexp = /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g; + regexp = SW[software].regexp.quotelinkHTML; + regexp.lastIndex = 0; while (match = regexp.exec(postObj.com)) { if (QuoteYou.db.get({ + siteID: siteID, boardID: match[1] || boardID, threadID: match[2] || threadID, postID: match[3] || match[2] || threadID @@ -20178,26 +20274,41 @@ ThreadWatcher = (function() { quotingYou = true; } } - if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) { - ThreadWatcher.db.extend({ - boardID: boardID, - threadID: threadID, - val: { - isDead: isDead, - unread: unread, - quotingYou: quotingYou - } - }); + updated = isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou; + ThreadWatcher.db.extend({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + val: { + last: last, + isDead: isDead, + unread: unread, + quotingYou: quotingYou + } + }); + if (updated) { return ThreadWatcher.refresh(); } } else if (this.status === 404) { - if (Conf['Auto Prune']) { + if (SW[software].mayLackJSON && (data.last == null)) { + ThreadWatcher.db.extend({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + val: { + last: -1 + }, + rm: ['unread', 'quotingYou'] + }); + } else if (Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID }); } else { ThreadWatcher.db.extend({ + siteID: siteID, boardID: boardID, threadID: threadID, val: { @@ -20258,7 +20369,7 @@ ThreadWatcher = (function() { title: excerpt, className: 'watcher-link' }); - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && software === 'yotsuba' && (data.unread != null)) { + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) { count = $.el('span', { textContent: "(" + data.unread + ")", className: 'watcher-unread' @@ -20280,7 +20391,7 @@ ThreadWatcher = (function() { if (data.isDead) { $.addClass(div, 'dead-thread'); } - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && software === 'yotsuba') { + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { if (data.unread === 0) { $.addClass(div, 'replies-read'); } @@ -20701,17 +20812,17 @@ Unread = (function() { } }, scroll: function() { - var hash, position, root; + var bottom, hash, position; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } position = Unread.positionPrev(); while (position) { - root = position.data.nodes.root; - if (!root.getBoundingClientRect().height) { + bottom = position.data.nodes.bottom; + if (!bottom.getBoundingClientRect().height) { position = position.prev; } else { - Header.scrollToIfNeeded(root, true); + Header.scrollToIfNeeded(bottom, true); break; } } @@ -20781,7 +20892,7 @@ Unread = (function() { icon: Favicon.logo }); notif.onclick = function() { - Header.scrollToIfNeeded(post.nodes.root, true); + Header.scrollToIfNeeded(post.nodes.bottom, true); return window.focus(); }; return notif.onshow = function() { @@ -20810,7 +20921,7 @@ Unread = (function() { return Unread.update(); }, read: $.debounce(100, function(e) { - var ID, count, data, ref, root; + var ID, bottom, count, data, ref; if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) { Unread.saveLastReadPost(); } @@ -20820,8 +20931,8 @@ Unread = (function() { count = 0; while (Unread.position) { ref = Unread.position, ID = ref.ID, data = ref.data; - root = data.nodes.root; - if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) { + bottom = data.nodes.bottom; + if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) { break; } count++; @@ -20875,7 +20986,7 @@ Unread = (function() { } if (Unread.hr.hidden || d.hidden || (force === true)) { if ((Unread.linePosition = Unread.positionPrev())) { - $.after(Unread.linePosition.data.nodes.root, Unread.hr); + $.after(Unread.linePosition.data.nodes.bottom, Unread.hr); } else { $.rm(Unread.hr); } @@ -20893,7 +21004,7 @@ Unread = (function() { d.title = "" + titleQuotingYou + titleCount + titleDead; } Unread.saveThreadWatcherCount(); - if (Conf['Unread Favicon']) { + if (Conf['Unread Favicon'] && Site.software === 'yotsuba') { isDead = Unread.thread.isDead; Favicon.el.href = countQuotingYou ? Favicon[isDead ? 'unreadDeadY' : 'unreadY'] : count ? Favicon[isDead ? 'unreadDead' : 'unread'] : Favicon[isDead ? 'dead' : 'default']; return $.add(d.head, Favicon.el); @@ -25580,7 +25691,7 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] + features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] }; return Main; diff --git a/builds/4chan-X.crx b/builds/4chan-X.crx index 8928adce6..0a88435eb 100644 Binary files a/builds/4chan-X.crx and b/builds/4chan-X.crx differ diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index a6b87b76e..90d386b8b 100644 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.14.5.1 +// @version 1.14.5.2 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 6a40d6325..1c1f66ee3 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.14.5.1 +// @version 1.14.5.2 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -183,7 +183,7 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, UnreadIndex, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, Tinyboard, UI, Unread, UnreadIndex, Volume; var Conf, E, c, d, doc, docSet, g; @@ -198,7 +198,7 @@ docSet = function() { }; g = { - VERSION: '1.14.5.1', + VERSION: '1.14.5.2', NAMESPACE: '4chan X.', boards: {} }; @@ -2317,6 +2317,9 @@ span.hide-announcement {\n\ margin: 0;\n\ border-color: rgb(255,0,0);\n\ }\n\ +.unread-line + br {\n\ + display: none;\n\ +}\n\ .unread-mark-read {\n\ float: right;\n\ clear: both;\n\ @@ -2419,6 +2422,9 @@ span.hide-announcement {\n\ -webkit-flex: 0 1 auto;\n\ flex: 0 1 auto;\n\ }\n\ +.replies-quoting-you > a, #watcher-link.replies-quoting-you {\n\ + color: #F00;\n\ +}\n\ #thread-watcher a {\n\ text-decoration: none;\n\ }\n\ @@ -5472,7 +5478,7 @@ CrossOrigin = (function() { } return delete callbacks[url]; }; - return function(url, cb, bypassCache) { + return function(url, cb, bypassCache, timeout) { var req; if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { if (bypassCache) { @@ -5493,19 +5499,21 @@ CrossOrigin = (function() { } if (bypassCache) { delete results[url]; - } - if (results[url]) { - cb.call(results[url]); - return; - } - if (callbacks[url]) { - callbacks[url].push(cb); - return; + } else { + if (results[url]) { + cb.call(results[url]); + return; + } + if (callbacks[url]) { + callbacks[url].push(cb); + return; + } } callbacks[url] = [cb]; return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)({ method: "GET", url: url + '', + timeout: timeout, onload: function(xhr) { var response, status, statusText; status = xhr.status, statusText = xhr.statusText; @@ -5525,6 +5533,9 @@ CrossOrigin = (function() { }, onabort: function() { return failure(url); + }, + ontimeout: function() { + return failure(url); } }); }; @@ -5928,13 +5939,10 @@ DataBoard = (function() { })(this), cb); }; - DataBoard.prototype.setLastChecked = function(siteID) { - if (siteID == null) { - siteID = Site.hostname; - } + DataBoard.prototype.setLastChecked = function() { return this.save((function(_this) { return function() { - return _this.data[siteID].lastChecked = Date.now(); + return _this.data.lastChecked = Date.now(); }; })(this)); }; @@ -6504,9 +6512,10 @@ Post = (function() { this.boardID = this.board.ID; this.fullID = this.board + "." + this.ID; this.context = this; + this.isReply = this.ID !== this.threadID; root.dataset.fullID = this.fullID; this.nodes = this.parseNodes(root); - if (!(this.isReply = this.ID !== this.threadID)) { + if (!this.isReply) { this.thread.OP = this; ref = ['isSticky', 'isClosed', 'isArchived']; for (j = 0, len = ref.length; j < len; j++) { @@ -6568,6 +6577,7 @@ Post = (function() { info = $(s.infoRoot, post); nodes = { root: root, + bottom: this.isReply || !Site.isOPContainerThread ? root : $(s.opBottom, root), post: post, info: info, comment: $(s.comment, post), @@ -7280,7 +7290,8 @@ SW = {}; SW.tinyboard = { isOPContainerThread: true, - disabledFeatures: ['Board Configuration', 'Normalize URL', 'Captcha Configuration', 'Image Host Rewriting', 'Index Generator', 'Announcement Hiding', 'Fourchan thingies', 'Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Pass Link', 'Index Generator (Menu)', 'Report Link', 'Delete Link', 'Edit Link', 'Archive Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Fappe Tyme', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Unread', 'Quote Threading', 'Thread Stats', 'Thread Updater', 'Mark New IPs', 'Banner', 'Flash Features', 'Reply Pruning'], + mayLackJSON: true, + disabledFeatures: ['Board Configuration', 'Normalize URL', 'Captcha Configuration', 'Image Host Rewriting', 'Index Generator', 'Announcement Hiding', 'Fourchan thingies', 'Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Pass Link', 'Index Generator (Menu)', 'Report Link', 'Delete Link', 'Edit Link', 'Archive Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Fappe Tyme', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Quote Threading', 'Thread Stats', 'Thread Updater', 'Mark New IPs', 'Banner', 'Flash Features', 'Reply Pruning'], detect: function() { var i, len, m, properties, ref, root, script; ref = $$('script:not([src])', d.head); @@ -7306,6 +7317,16 @@ SW = {}; var boardID, ref, siteID, threadID; siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/res/" + threadID + ".html"; + }, + threadJSON: function(arg) { + var boardID, ref, root, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/res/" + threadID + ".json"; + } else { + return ''; + } } }, selectors: { @@ -7314,6 +7335,7 @@ SW = {}; threadDivider: 'div[id^="thread_"] > hr:last-of-type', summary: '.omitted', postContainer: '.reply', + opBottom: '.op', infoRoot: '.intro', info: { subject: '.subject', @@ -7348,7 +7370,8 @@ SW = {}; postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]' }, regexp: { - quotelink: /\/([^\/]+)\/res\/(\d+)\.html#(\d+)$/ + quotelink: /\/([^\/]+)\/res\/(\d+)\.html#(\d+)$/, + quotelinkHTML: /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)\.html#(\d+)"/g }, bgColoredEl: function() { return $.el('div', { @@ -7415,6 +7438,11 @@ SW = {}; var boardID, threadID; boardID = arg.boardID, threadID = arg.threadID; return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + }, + threadJSON: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json"; } }, selectors: { @@ -7462,7 +7490,8 @@ SW = {}; postContainer: 'div[contains(@class,"postContainer")]' }, regexp: { - quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/ + quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/, + quotelinkHTML: /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g }, bgColoredEl: function() { return $.el('div', { @@ -7588,6 +7617,9 @@ SW = {}; node = ref1[k]; $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); } + }, + hasCORS: function(url) { + return url.split('/').slice(0, 3).join('/') === location.protocol + '//a.4cdn.org'; } }; @@ -18874,6 +18906,47 @@ Time = (function() { }).call(this); +Tinyboard = (function() { + var Tinyboard; + + Tinyboard = { + init: function() { + if (Site.software !== 'tinyboard') { + return; + } + if (g.VIEW === 'thread') { + return Main.ready(function() { + return $.global(function() { + var boardID, ref, threadID; + ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID; + threadID = +threadID; + return window.$(document).on('new_post', function(e, post) { + var detail, event, postID; + postID = +post.id.match(/\d*$/)[0]; + detail = { + boardID: boardID, + threadID: threadID, + postID: postID + }; + event = new CustomEvent('QRPostSuccessful', { + bubbles: true, + detail: detail + }); + return document.dispatchEvent(event); + }); + }, { + boardID: g.BOARD.ID, + threadID: g.THREADID + }); + }); + } + } + }; + + return Tinyboard; + +}).call(this); + Favicon = (function() { var Favicon; @@ -19764,9 +19837,6 @@ ThreadWatcher = (function() { $.on(sc, 'click', this.toggleWatcher); $.on(this.refreshButton, 'click', this.buttonFetchAll); $.on(this.closeButton, 'click', this.toggleWatcher); - if (Site.software !== 'yotsuba') { - this.refreshButton.hidden = true; - } this.menu.addHeaderMenuEntry(); $.onExists(doc, 'body', this.addDialog); switch (g.VIEW) { @@ -20039,9 +20109,6 @@ ThreadWatcher = (function() { }, fetchAuto: function() { var db, interval, now, ref; - if (Site.software !== 'yotsuba') { - return; - } clearTimeout(ThreadWatcher.timeout); if (!Conf['Auto Update Thread Watcher']) { return; @@ -20049,7 +20116,7 @@ ThreadWatcher = (function() { db = ThreadWatcher.db; interval = ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] ? 5 * $.MINUTE : 2 * $.HOUR; now = Date.now(); - if (!((now - interval < (ref = db.data[Site.hostname].lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { + if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { ThreadWatcher.fetchAllStatus(); db.setLastChecked(); } @@ -20064,9 +20131,6 @@ ThreadWatcher = (function() { }, fetchAllStatus: function() { var db, dbs, i, len1, n, results; - if (Site.software !== 'yotsuba') { - return; - } dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) { return x; }); @@ -20088,66 +20152,96 @@ ThreadWatcher = (function() { return results; }, fetchStatus: function(thread, force) { - var boardID, data, req, siteID, threadID; + var base, boardID, data, ref, ref1, req, siteID, software, threadID, url; siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data; - if (!(Site.software === 'yotsuba' && siteID === Site.hostname)) { + software = (ref = Conf['siteProperties'][siteID]) != null ? ref.software : void 0; + url = (ref1 = SW[software]) != null ? typeof (base = ref1.urls).threadJSON === "function" ? base.threadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!url) { return; } if (data.isDead && !force) { return; } + if (data.last === -1) { + return; + } if (ThreadWatcher.requests.length === 0) { ThreadWatcher.status.textContent = '...'; $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); } - req = $.ajax(location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { - onloadend: function() { + if ((typeof Site.hasCORS === "function" ? Site.hasCORS(url) : void 0) || url.split('/').slice(0, 3).join('/') === location.origin) { + req = $.ajax(url, { + onloadend: function() { + return ThreadWatcher.parseStatus.call(this, thread); + }, + timeout: $.MINUTE + }); + } else { + req = { + abort: function() { + return req.aborted = true; + } + }; + CrossOrigin.json(url, function() { + if (req.aborted) { + return; + } return ThreadWatcher.parseStatus.call(this, thread); - }, - timeout: $.MINUTE - }, { - whenModified: force ? false : 'ThreadWatcher' - }); + }, true, $.MINUTE); + } return ThreadWatcher.requests.push(req); }, parseStatus: function(arg) { - var boardID, data, i, isDead, lastReadPost, len1, match, postObj, quotesYou, quotingYou, ref, ref1, ref2, regexp, threadID, unread, youOP; - boardID = arg.boardID, threadID = arg.threadID, data = arg.data; + var boardID, data, i, isDead, last, lastReadPost, len1, match, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, siteID, software, threadID, unread, updated, youOP; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, data = arg.data; ThreadWatcher.fetched++; if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { ThreadWatcher.clearRequests(); } else { ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; } + software = (ref = Conf['siteProperties'][siteID]) != null ? ref.software : void 0; if (this.status === 200 && this.response) { + last = this.response.posts[this.response.posts.length - 1].no; isDead = !!this.response.posts[0].archived; if (isDead && Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID }); ThreadWatcher.refresh(); return; } + if (last === data.last && isDead === data.isDead) { + return; + } lastReadPost = ThreadWatcher.unreaddb.get({ + siteID: siteID, boardID: boardID, threadID: threadID, defaultValue: 0 }); unread = 0; quotingYou = false; - youOP = !!((ref = QuoteYou.db) != null ? ref.get({ + youOP = !!((ref1 = QuoteYou.db) != null ? ref1.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: threadID }) : void 0); - ref1 = this.response.posts; - for (i = 0, len1 = ref1.length; i < len1; i++) { - postObj = ref1[i]; + ref2 = this.response.posts; + for (i = 0, len1 = ref2.length; i < len1; i++) { + postObj = ref2[i]; if (!(postObj.no > lastReadPost)) { continue; } - if ((ref2 = QuoteYou.db) != null ? ref2.get({ + if ((ref3 = QuoteYou.db) != null ? ref3.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: postObj.no @@ -20163,9 +20257,11 @@ ThreadWatcher = (function() { continue; } quotesYou = false; - regexp = /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g; + regexp = SW[software].regexp.quotelinkHTML; + regexp.lastIndex = 0; while (match = regexp.exec(postObj.com)) { if (QuoteYou.db.get({ + siteID: siteID, boardID: match[1] || boardID, threadID: match[2] || threadID, postID: match[3] || match[2] || threadID @@ -20178,26 +20274,41 @@ ThreadWatcher = (function() { quotingYou = true; } } - if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) { - ThreadWatcher.db.extend({ - boardID: boardID, - threadID: threadID, - val: { - isDead: isDead, - unread: unread, - quotingYou: quotingYou - } - }); + updated = isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou; + ThreadWatcher.db.extend({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + val: { + last: last, + isDead: isDead, + unread: unread, + quotingYou: quotingYou + } + }); + if (updated) { return ThreadWatcher.refresh(); } } else if (this.status === 404) { - if (Conf['Auto Prune']) { + if (SW[software].mayLackJSON && (data.last == null)) { + ThreadWatcher.db.extend({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + val: { + last: -1 + }, + rm: ['unread', 'quotingYou'] + }); + } else if (Conf['Auto Prune']) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID }); } else { ThreadWatcher.db.extend({ + siteID: siteID, boardID: boardID, threadID: threadID, val: { @@ -20258,7 +20369,7 @@ ThreadWatcher = (function() { title: excerpt, className: 'watcher-link' }); - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && software === 'yotsuba' && (data.unread != null)) { + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) { count = $.el('span', { textContent: "(" + data.unread + ")", className: 'watcher-unread' @@ -20280,7 +20391,7 @@ ThreadWatcher = (function() { if (data.isDead) { $.addClass(div, 'dead-thread'); } - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && software === 'yotsuba') { + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { if (data.unread === 0) { $.addClass(div, 'replies-read'); } @@ -20701,17 +20812,17 @@ Unread = (function() { } }, scroll: function() { - var hash, position, root; + var bottom, hash, position; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } position = Unread.positionPrev(); while (position) { - root = position.data.nodes.root; - if (!root.getBoundingClientRect().height) { + bottom = position.data.nodes.bottom; + if (!bottom.getBoundingClientRect().height) { position = position.prev; } else { - Header.scrollToIfNeeded(root, true); + Header.scrollToIfNeeded(bottom, true); break; } } @@ -20781,7 +20892,7 @@ Unread = (function() { icon: Favicon.logo }); notif.onclick = function() { - Header.scrollToIfNeeded(post.nodes.root, true); + Header.scrollToIfNeeded(post.nodes.bottom, true); return window.focus(); }; return notif.onshow = function() { @@ -20810,7 +20921,7 @@ Unread = (function() { return Unread.update(); }, read: $.debounce(100, function(e) { - var ID, count, data, ref, root; + var ID, bottom, count, data, ref; if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) { Unread.saveLastReadPost(); } @@ -20820,8 +20931,8 @@ Unread = (function() { count = 0; while (Unread.position) { ref = Unread.position, ID = ref.ID, data = ref.data; - root = data.nodes.root; - if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) { + bottom = data.nodes.bottom; + if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) { break; } count++; @@ -20875,7 +20986,7 @@ Unread = (function() { } if (Unread.hr.hidden || d.hidden || (force === true)) { if ((Unread.linePosition = Unread.positionPrev())) { - $.after(Unread.linePosition.data.nodes.root, Unread.hr); + $.after(Unread.linePosition.data.nodes.bottom, Unread.hr); } else { $.rm(Unread.hr); } @@ -20893,7 +21004,7 @@ Unread = (function() { d.title = "" + titleQuotingYou + titleCount + titleDead; } Unread.saveThreadWatcherCount(); - if (Conf['Unread Favicon']) { + if (Conf['Unread Favicon'] && Site.software === 'yotsuba') { isDead = Unread.thread.isDead; Favicon.el.href = countQuotingYou ? Favicon[isDead ? 'unreadDeadY' : 'unreadY'] : count ? Favicon[isDead ? 'unreadDead' : 'unread'] : Favicon[isDead ? 'dead' : 'default']; return $.add(d.head, Favicon.el); @@ -25580,7 +25691,7 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] + features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] }; return Main; diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip index 2c4279989..7744a14e3 100644 Binary files a/builds/4chan-X.zip and b/builds/4chan-X.zip differ diff --git a/builds/updates-beta.json b/builds/updates-beta.json index e9dec896f..247efc452 100644 --- a/builds/updates-beta.json +++ b/builds/updates-beta.json @@ -3,7 +3,7 @@ "4chan-x@4chan-x.net": { "updates": [ { - "version": "1.14.5.1", + "version": "1.14.5.2", "update_link": "https://www.4chan-x.net/builds/4chan-X-beta.crx" } ] diff --git a/builds/updates-beta.xml b/builds/updates-beta.xml index 26a10933c..30e3a76a1 100644 --- a/builds/updates-beta.xml +++ b/builds/updates-beta.xml @@ -1,7 +1,7 @@ - + diff --git a/builds/updates.json b/builds/updates.json index 02ec08fdb..e36670270 100644 --- a/builds/updates.json +++ b/builds/updates.json @@ -3,7 +3,7 @@ "4chan-x@4chan-x.net": { "updates": [ { - "version": "1.14.5.1", + "version": "1.14.5.2", "update_link": "https://www.4chan-x.net/builds/4chan-X.crx" } ] diff --git a/builds/updates.xml b/builds/updates.xml index 87720770e..acae2f8a0 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/version.json b/version.json index b97470b20..cf4d35ba7 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "1.14.5.1", - "date": "2018-12-06T17:15:24.488Z" + "version": "1.14.5.2", + "date": "2018-12-07T05:18:16.194Z" } \ No newline at end of file