diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d1a6cef..dd8cd716e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ **Note**: Installing the script from one of the links below will disable automatic updates. If you want automatic updates, install the script from the links on the [main page](https://www.4chan-x.net/). +### v1.12.2 + +**v1.12.2.0** *(2016-07-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.2.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.2.0/builds/4chan-X-noupdate.crx)] +- Based on v1.12.1.5. +- Support image pasting in Firefox 50+ without selecting paste icon in Quick Reply. +- Improve 4chan X's ability to deal with 4chan changes without a script update. + ### v1.12.1 **v1.12.1.5** *(2016-07-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.5/builds/4chan-X-noupdate.crx)] diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx index 63a967cd8..e6b677959 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 11ab9f468..69293d515 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.12.1.5 +// @version 1.12.2.0 // @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 68737aed1..3be121b73 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.12.1.5 +// @version 1.12.2.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -121,7 +121,7 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, 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, Sauce, Settings, ShimSet, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, 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, Sauce, Settings, ShimSet, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; var Conf, E, c, d, doc, docSet, g; @@ -136,7 +136,7 @@ docSet = function() { }; g = { - VERSION: '1.12.1.5', + VERSION: '1.12.2.0', NAMESPACE: '4chan X.', boards: {} }; @@ -2466,8 +2466,8 @@ input[name=\"Default Volume\"] {\n\ #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show),\n\ body:not(.board_f) #qr select[name=\"filetag\"],\n\ #qr.reply-to-thread select[name=\"filetag\"],\n\ -body:not(.board_jp) #sjis-toggle,\n\ -body:not(.board_sci) #tex-preview-button,\n\ +#qr:not(.has-sjis) #sjis-toggle,\n\ +#qr:not(.has-math) #tex-preview-button,\n\ #qr.tex-preview .textarea > :not(#tex-preview),\n\ #qr:not(.tex-preview) #tex-preview {\n\ display: none;\n\ @@ -4602,15 +4602,18 @@ $ = (function() { }).call(this); $$ = (function() { - var slice = [].slice; + var $$, + slice = [].slice; - return function(selector, root) { + $$ = function(selector, root) { if (root == null) { root = d.body; } return slice.call(root.querySelectorAll(selector)); }; + return $$; + }).call(this); CrossOrigin = (function() { @@ -4735,9 +4738,11 @@ Board = (function() { }; function Board(ID) { + var ref; this.ID = ID; this.threads = new SimpleDict(); this.posts = new SimpleDict(); + this.config = ((ref = BoardConfig.boards) != null ? ref[this.ID] : void 0) || {}; g.boards[this] = this; } @@ -7492,6 +7497,77 @@ ThreadHiding = (function() { }).call(this); +BoardConfig = (function() { + var BoardConfig; + + BoardConfig = { + cbs: [], + init: function() { + if ((Conf['boardConfig'].lastChecked || 0) < Date.now() - 2 * $.HOUR) { + return $.ajax('//a.4cdn.org/boards.json', { + onloadend: this.load, + timeout: 5 * $.SECOND + }); + } else { + return this.set(Conf['boardConfig'].boards); + } + }, + load: function() { + var board, boards, err, i, len, ref; + if (this.status === 200 && this.response && this.response.boards) { + boards = {}; + ref = this.response.boards; + for (i = 0, len = ref.length; i < len; i++) { + board = ref[i]; + boards[board.board] = board; + } + $.set('boardConfig', { + boards: boards, + lastChecked: Date.now() + }); + } else { + boards = Conf['boardConfig'].boards; + err = (function() { + switch (this.status) { + case 0: + return 'Connection Error'; + case 200: + return 'Invalid Data'; + default: + return "Error " + this.statusText + " (" + this.status + ")"; + } + }).call(this); + new Notice('warning', "Failed to load board configuration. " + err, 20); + } + return BoardConfig.set(boards); + }, + set: function(boards1) { + var ID, board, cb, i, len, ref, ref1; + this.boards = boards1; + ref = g.boards; + for (ID in ref) { + board = ref[ID]; + board.config = this.boards[ID] || {}; + } + ref1 = this.cbs; + for (i = 0, len = ref1.length; i < len; i++) { + cb = ref1[i]; + $.queueTask(cb); + } + }, + ready: function(cb) { + if (this.boards) { + return cb(); + } else { + return this.cbs.push(cb); + } + } + }; + + return BoardConfig; + +}).call(this); + Build = (function() { var Build, slice = [].slice; @@ -9656,7 +9732,8 @@ Polyfill = (function() { Polyfill = { init: function() { - return this.toBlob(); + this.toBlob(); + $.global(this.toBlob); }, toBlob: function() { if (HTMLCanvasElement.prototype.toBlob) { @@ -9664,9 +9741,6 @@ Polyfill = (function() { } HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { var data, i, j, l, ref, ui8a, url; - if (type == null) { - type = 'image/png'; - } url = this.toDataURL(type, encoderOptions); data = atob(url.slice(url.indexOf(',') + 1)); l = data.length; @@ -9675,10 +9749,9 @@ Polyfill = (function() { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: type + type: type || 'image/png' })); }; - return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); } }; @@ -9959,6 +10032,7 @@ Settings = (function() { }, "export": function() { return $.get(Conf, function(Conf) { + delete Conf['boardConfig']; return Settings.downloadExport({ version: g.VERSION, date: Date.now(), @@ -10669,7 +10743,7 @@ Settings = (function() { }).call(this); UI = (function() { - var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, + var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; @@ -11137,13 +11211,15 @@ UI = (function() { return label; }; - return { + UI = { dialog: dialog, Menu: Menu, hover: hoverstart, checkbox: checkbox }; + return UI; + }).call(this); FappeTyme = (function() { @@ -15747,23 +15823,27 @@ Keybinds = (function() { return QR.nodes.com.focus(); }, tags: function(tag, ta) { - var range, selEnd, selStart, supported, value; - supported = (function() { - switch (tag) { - case 'spoiler': - return !!$('.postForm input[name=spoiler]'); - case 'code': - return g.BOARD.ID === 'g'; - case 'math': - case 'eqn': - return g.BOARD.ID === 'sci'; - case 'sjis': - return g.BOARD.ID === 'jp'; + var range, selEnd, selStart, value; + BoardConfig.ready(function() { + var config, supported; + config = g.BOARD.config; + supported = (function() { + switch (tag) { + case 'spoiler': + return !!config.spoilers; + case 'code': + return !!config.code_tags; + case 'math': + case 'eqn': + return !!config.math_tags; + case 'sjis': + return !!config.sjis_tags; + } + })(); + if (!supported) { + return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); } - })(); - if (!supported) { - new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); - } + }); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; @@ -19411,7 +19491,9 @@ QR = (function() { } version = Conf['Use Recaptcha v1'] && Main.jsEnabled ? 'v1' : 'v2'; this.captcha = Captcha[version]; - $.on(d, '4chanXInitFinished', this.initReady); + $.on(d, '4chanXInitFinished', function() { + return BoardConfig.ready(QR.initReady); + }); Callbacks.Post.push({ name: 'Quick Reply', cb: this.node @@ -19436,12 +19518,26 @@ QR = (function() { return Header.addShortcut('qr', sc, 540); }, initReady: function() { - var link, linkBot, navLinksBot, origToggle; - $.off(d, '4chanXInitFinished', this.initReady); + var config, link, linkBot, navLinksBot, origToggle, prop; QR.postingIsEnabled = !!$.id('postForm'); if (!QR.postingIsEnabled) { return; } + config = g.BOARD.config; + prop = function(key, def) { + var ref; + return +((ref = config[key]) != null ? ref : def); + }; + QR.min_width = prop('min_image_width', 1); + QR.min_height = prop('min_image_height', 1); + QR.max_width = QR.max_height = 10000; + QR.max_size = prop('max_filesize', 4194304); + QR.max_size_video = prop('max_webm_filesize', QR.max_size); + QR.max_comment = prop('max_comment_chars', 2000); + QR.max_width_video = QR.max_height_video = 2048; + QR.max_duration_video = prop('max_webm_duration', 120); + QR.forcedAnon = !!config.forced_anon; + QR.spoiler = !!config.spoilers; link = $.el('h1', { className: "qr-link-container" }); @@ -19796,30 +19892,34 @@ QR = (function() { return QR.handleFiles(e.dataTransfer.files); }, paste: function(e) { - var blob, files, item, j, len, ref; + var blob, file, file2, item, j, len, ref, score, score2, type; if (!e.clipboardData.items) { return; } - files = []; + file = null; + score = -1; ref = e.clipboardData.items; for (j = 0, len = ref.length; j < len; j++) { item = ref[j]; - if (!(item.kind === 'file')) { + if (!(item.kind === 'file' && (file2 = item.getAsFile()))) { continue; } - blob = item.getAsFile(); - blob.name = 'file'; - if (blob.type) { - blob.name += '.' + blob.type.split('/')[1]; + score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png'); + if (score2 > score) { + file = file2; + score = score2; } - files.push(blob); } - if (!files.length) { - return; + if (file) { + type = file.type; + blob = new Blob([file], { + type: type + }); + blob.name = "file." + (QR.extensionFromType[type] || 'jpg'); + QR.open(); + QR.handleFiles([blob]); + $.addClass(QR.nodes.el, 'dump'); } - QR.open(); - QR.handleFiles(files); - return $.addClass(QR.nodes.el, 'dump'); }, pasteFF: function() { var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src; @@ -19930,7 +20030,7 @@ QR = (function() { return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); }, dialog: function() { - var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; + var classList, config, dialog, event, i, items, name, node, nodes, save, setNode; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { innerHTML: "
×
+
No selected file
" @@ -19970,33 +20070,14 @@ QR = (function() { setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); setNode('fileInput', '[type=file]'); - rules = $('ul.rules').textContent.trim(); - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/); - match_max = rules.match(/.+greater than (\d+)x(\d+).+/); - QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1; - QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1; - QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000; - QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000; - scriptData = Get.scriptData(); - QR.max_size = (m = scriptData.match(/\bmaxFilesize *= *(\d+)\b/)) ? +m[1] : 4194304; - QR.max_size_video = (m = scriptData.match(/\bmaxWebmFilesize *= *(\d+)\b/)) ? +m[1] : QR.max_size; - QR.max_comment = (m = scriptData.match(/\bcomlen *= *(\d+)\b/)) ? +m[1] : 2000; - QR.max_width_video = QR.max_height_video = 2048; - QR.max_duration_video = (ref = g.BOARD.ID) === 'gif' || ref === 'wsg' ? 300 : 120; - if (Conf['Show New Thread Option in Threads']) { - $.addClass(QR.nodes.el, 'show-new-thread-option'); - } - QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]'); - if (QR.forcedAnon) { - $.addClass(QR.nodes.el, 'forced-anon'); - } - QR.spoiler = !!$('.postForm input[name=spoiler]'); - if (QR.spoiler) { - $.addClass(QR.nodes.el, 'has-spoiler'); - } - if (g.BOARD.ID === 'jp' && Conf['sjisPreview']) { - $.addClass(QR.nodes.el, 'sjis-preview'); - } + config = g.BOARD.config; + classList = QR.nodes.el.classList; + classList.toggle('forced-anon', QR.forcedAnon); + classList.toggle('has-spoiler', QR.spoiler); + classList.toggle('has-sjis', !!config.sjis_tags); + classList.toggle('has-math', !!config.math_tags); + classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']); + classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']); if (parseInt(Conf['customCooldown'], 10) > 0) { $.addClass(QR.nodes.fileSubmit, 'custom-cooldown'); $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) { @@ -20042,7 +20123,7 @@ QR = (function() { window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); - if ($.engine === 'gecko') { + if ($.engine === 'gecko' && !window.DataTransferItemList) { nodes.pasteArea.hidden = false; new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { childList: true @@ -20083,7 +20164,7 @@ QR = (function() { return $.event('QRDialogCreation', null, dialog); }, submit: function(e) { - var captcha, cb, err, extra, filetag, formData, options, post, ref, textOnly, thread, threadID; + var captcha, cb, err, extra, filetag, formData, options, post, ref, thread, threadID; if (e != null) { e.preventDefault(); } @@ -20105,9 +20186,9 @@ QR = (function() { } if (threadID === 'new') { threadID = null; - if (g.BOARD.ID === 'vg' && !post.sub) { + if (!!g.BOARD.config.require_subject && !post.sub) { err = 'New threads require a subject.'; - } else if (!($.hasClass(d.body, 'text_only') || post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { + } else if (!(!!g.BOARD.config.text_only || post.file)) { err = 'No file selected.'; } } else if (g.BOARD.threads[threadID].isClosed) { @@ -20151,7 +20232,6 @@ QR = (function() { upfile: post.file, filetag: filetag, spoiler: post.spoiler, - textonly: textOnly, mode: 'regist', pwd: QR.persona.getPassword() }; @@ -22402,6 +22482,9 @@ Main = (function() { boards: {} }; } + Conf['boardConfig'] = { + boards: {} + }; Conf['archives'] = Redirect.archives; Conf['selectedArchives'] = {}; Conf['cooldowns'] = {}; @@ -22898,7 +22981,7 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['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], ['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], ['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], ['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], ['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], ['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 c6a6d0cf8..af29cdee4 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 49d440f36..8cd8a0b6a 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.12.1.5 +// @version 1.12.2.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -121,7 +121,7 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, 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, Sauce, Settings, ShimSet, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, 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, Sauce, Settings, ShimSet, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; var Conf, E, c, d, doc, docSet, g; @@ -136,7 +136,7 @@ docSet = function() { }; g = { - VERSION: '1.12.1.5', + VERSION: '1.12.2.0', NAMESPACE: '4chan X.', boards: {} }; @@ -2466,8 +2466,8 @@ input[name=\"Default Volume\"] {\n\ #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show),\n\ body:not(.board_f) #qr select[name=\"filetag\"],\n\ #qr.reply-to-thread select[name=\"filetag\"],\n\ -body:not(.board_jp) #sjis-toggle,\n\ -body:not(.board_sci) #tex-preview-button,\n\ +#qr:not(.has-sjis) #sjis-toggle,\n\ +#qr:not(.has-math) #tex-preview-button,\n\ #qr.tex-preview .textarea > :not(#tex-preview),\n\ #qr:not(.tex-preview) #tex-preview {\n\ display: none;\n\ @@ -4602,15 +4602,18 @@ $ = (function() { }).call(this); $$ = (function() { - var slice = [].slice; + var $$, + slice = [].slice; - return function(selector, root) { + $$ = function(selector, root) { if (root == null) { root = d.body; } return slice.call(root.querySelectorAll(selector)); }; + return $$; + }).call(this); CrossOrigin = (function() { @@ -4735,9 +4738,11 @@ Board = (function() { }; function Board(ID) { + var ref; this.ID = ID; this.threads = new SimpleDict(); this.posts = new SimpleDict(); + this.config = ((ref = BoardConfig.boards) != null ? ref[this.ID] : void 0) || {}; g.boards[this] = this; } @@ -7492,6 +7497,77 @@ ThreadHiding = (function() { }).call(this); +BoardConfig = (function() { + var BoardConfig; + + BoardConfig = { + cbs: [], + init: function() { + if ((Conf['boardConfig'].lastChecked || 0) < Date.now() - 2 * $.HOUR) { + return $.ajax('//a.4cdn.org/boards.json', { + onloadend: this.load, + timeout: 5 * $.SECOND + }); + } else { + return this.set(Conf['boardConfig'].boards); + } + }, + load: function() { + var board, boards, err, i, len, ref; + if (this.status === 200 && this.response && this.response.boards) { + boards = {}; + ref = this.response.boards; + for (i = 0, len = ref.length; i < len; i++) { + board = ref[i]; + boards[board.board] = board; + } + $.set('boardConfig', { + boards: boards, + lastChecked: Date.now() + }); + } else { + boards = Conf['boardConfig'].boards; + err = (function() { + switch (this.status) { + case 0: + return 'Connection Error'; + case 200: + return 'Invalid Data'; + default: + return "Error " + this.statusText + " (" + this.status + ")"; + } + }).call(this); + new Notice('warning', "Failed to load board configuration. " + err, 20); + } + return BoardConfig.set(boards); + }, + set: function(boards1) { + var ID, board, cb, i, len, ref, ref1; + this.boards = boards1; + ref = g.boards; + for (ID in ref) { + board = ref[ID]; + board.config = this.boards[ID] || {}; + } + ref1 = this.cbs; + for (i = 0, len = ref1.length; i < len; i++) { + cb = ref1[i]; + $.queueTask(cb); + } + }, + ready: function(cb) { + if (this.boards) { + return cb(); + } else { + return this.cbs.push(cb); + } + } + }; + + return BoardConfig; + +}).call(this); + Build = (function() { var Build, slice = [].slice; @@ -9656,7 +9732,8 @@ Polyfill = (function() { Polyfill = { init: function() { - return this.toBlob(); + this.toBlob(); + $.global(this.toBlob); }, toBlob: function() { if (HTMLCanvasElement.prototype.toBlob) { @@ -9664,9 +9741,6 @@ Polyfill = (function() { } HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { var data, i, j, l, ref, ui8a, url; - if (type == null) { - type = 'image/png'; - } url = this.toDataURL(type, encoderOptions); data = atob(url.slice(url.indexOf(',') + 1)); l = data.length; @@ -9675,10 +9749,9 @@ Polyfill = (function() { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: type + type: type || 'image/png' })); }; - return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); } }; @@ -9959,6 +10032,7 @@ Settings = (function() { }, "export": function() { return $.get(Conf, function(Conf) { + delete Conf['boardConfig']; return Settings.downloadExport({ version: g.VERSION, date: Date.now(), @@ -10669,7 +10743,7 @@ Settings = (function() { }).call(this); UI = (function() { - var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, + var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; @@ -11137,13 +11211,15 @@ UI = (function() { return label; }; - return { + UI = { dialog: dialog, Menu: Menu, hover: hoverstart, checkbox: checkbox }; + return UI; + }).call(this); FappeTyme = (function() { @@ -15747,23 +15823,27 @@ Keybinds = (function() { return QR.nodes.com.focus(); }, tags: function(tag, ta) { - var range, selEnd, selStart, supported, value; - supported = (function() { - switch (tag) { - case 'spoiler': - return !!$('.postForm input[name=spoiler]'); - case 'code': - return g.BOARD.ID === 'g'; - case 'math': - case 'eqn': - return g.BOARD.ID === 'sci'; - case 'sjis': - return g.BOARD.ID === 'jp'; + var range, selEnd, selStart, value; + BoardConfig.ready(function() { + var config, supported; + config = g.BOARD.config; + supported = (function() { + switch (tag) { + case 'spoiler': + return !!config.spoilers; + case 'code': + return !!config.code_tags; + case 'math': + case 'eqn': + return !!config.math_tags; + case 'sjis': + return !!config.sjis_tags; + } + })(); + if (!supported) { + return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); } - })(); - if (!supported) { - new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); - } + }); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; @@ -19411,7 +19491,9 @@ QR = (function() { } version = Conf['Use Recaptcha v1'] && Main.jsEnabled ? 'v1' : 'v2'; this.captcha = Captcha[version]; - $.on(d, '4chanXInitFinished', this.initReady); + $.on(d, '4chanXInitFinished', function() { + return BoardConfig.ready(QR.initReady); + }); Callbacks.Post.push({ name: 'Quick Reply', cb: this.node @@ -19436,12 +19518,26 @@ QR = (function() { return Header.addShortcut('qr', sc, 540); }, initReady: function() { - var link, linkBot, navLinksBot, origToggle; - $.off(d, '4chanXInitFinished', this.initReady); + var config, link, linkBot, navLinksBot, origToggle, prop; QR.postingIsEnabled = !!$.id('postForm'); if (!QR.postingIsEnabled) { return; } + config = g.BOARD.config; + prop = function(key, def) { + var ref; + return +((ref = config[key]) != null ? ref : def); + }; + QR.min_width = prop('min_image_width', 1); + QR.min_height = prop('min_image_height', 1); + QR.max_width = QR.max_height = 10000; + QR.max_size = prop('max_filesize', 4194304); + QR.max_size_video = prop('max_webm_filesize', QR.max_size); + QR.max_comment = prop('max_comment_chars', 2000); + QR.max_width_video = QR.max_height_video = 2048; + QR.max_duration_video = prop('max_webm_duration', 120); + QR.forcedAnon = !!config.forced_anon; + QR.spoiler = !!config.spoilers; link = $.el('h1', { className: "qr-link-container" }); @@ -19796,30 +19892,34 @@ QR = (function() { return QR.handleFiles(e.dataTransfer.files); }, paste: function(e) { - var blob, files, item, j, len, ref; + var blob, file, file2, item, j, len, ref, score, score2, type; if (!e.clipboardData.items) { return; } - files = []; + file = null; + score = -1; ref = e.clipboardData.items; for (j = 0, len = ref.length; j < len; j++) { item = ref[j]; - if (!(item.kind === 'file')) { + if (!(item.kind === 'file' && (file2 = item.getAsFile()))) { continue; } - blob = item.getAsFile(); - blob.name = 'file'; - if (blob.type) { - blob.name += '.' + blob.type.split('/')[1]; + score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png'); + if (score2 > score) { + file = file2; + score = score2; } - files.push(blob); } - if (!files.length) { - return; + if (file) { + type = file.type; + blob = new Blob([file], { + type: type + }); + blob.name = "file." + (QR.extensionFromType[type] || 'jpg'); + QR.open(); + QR.handleFiles([blob]); + $.addClass(QR.nodes.el, 'dump'); } - QR.open(); - QR.handleFiles(files); - return $.addClass(QR.nodes.el, 'dump'); }, pasteFF: function() { var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src; @@ -19930,7 +20030,7 @@ QR = (function() { return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); }, dialog: function() { - var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; + var classList, config, dialog, event, i, items, name, node, nodes, save, setNode; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { innerHTML: "
×
+
No selected file
" @@ -19970,33 +20070,14 @@ QR = (function() { setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); setNode('fileInput', '[type=file]'); - rules = $('ul.rules').textContent.trim(); - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/); - match_max = rules.match(/.+greater than (\d+)x(\d+).+/); - QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1; - QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1; - QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000; - QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000; - scriptData = Get.scriptData(); - QR.max_size = (m = scriptData.match(/\bmaxFilesize *= *(\d+)\b/)) ? +m[1] : 4194304; - QR.max_size_video = (m = scriptData.match(/\bmaxWebmFilesize *= *(\d+)\b/)) ? +m[1] : QR.max_size; - QR.max_comment = (m = scriptData.match(/\bcomlen *= *(\d+)\b/)) ? +m[1] : 2000; - QR.max_width_video = QR.max_height_video = 2048; - QR.max_duration_video = (ref = g.BOARD.ID) === 'gif' || ref === 'wsg' ? 300 : 120; - if (Conf['Show New Thread Option in Threads']) { - $.addClass(QR.nodes.el, 'show-new-thread-option'); - } - QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]'); - if (QR.forcedAnon) { - $.addClass(QR.nodes.el, 'forced-anon'); - } - QR.spoiler = !!$('.postForm input[name=spoiler]'); - if (QR.spoiler) { - $.addClass(QR.nodes.el, 'has-spoiler'); - } - if (g.BOARD.ID === 'jp' && Conf['sjisPreview']) { - $.addClass(QR.nodes.el, 'sjis-preview'); - } + config = g.BOARD.config; + classList = QR.nodes.el.classList; + classList.toggle('forced-anon', QR.forcedAnon); + classList.toggle('has-spoiler', QR.spoiler); + classList.toggle('has-sjis', !!config.sjis_tags); + classList.toggle('has-math', !!config.math_tags); + classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']); + classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']); if (parseInt(Conf['customCooldown'], 10) > 0) { $.addClass(QR.nodes.fileSubmit, 'custom-cooldown'); $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) { @@ -20042,7 +20123,7 @@ QR = (function() { window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); - if ($.engine === 'gecko') { + if ($.engine === 'gecko' && !window.DataTransferItemList) { nodes.pasteArea.hidden = false; new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { childList: true @@ -20083,7 +20164,7 @@ QR = (function() { return $.event('QRDialogCreation', null, dialog); }, submit: function(e) { - var captcha, cb, err, extra, filetag, formData, options, post, ref, textOnly, thread, threadID; + var captcha, cb, err, extra, filetag, formData, options, post, ref, thread, threadID; if (e != null) { e.preventDefault(); } @@ -20105,9 +20186,9 @@ QR = (function() { } if (threadID === 'new') { threadID = null; - if (g.BOARD.ID === 'vg' && !post.sub) { + if (!!g.BOARD.config.require_subject && !post.sub) { err = 'New threads require a subject.'; - } else if (!($.hasClass(d.body, 'text_only') || post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { + } else if (!(!!g.BOARD.config.text_only || post.file)) { err = 'No file selected.'; } } else if (g.BOARD.threads[threadID].isClosed) { @@ -20151,7 +20232,6 @@ QR = (function() { upfile: post.file, filetag: filetag, spoiler: post.spoiler, - textonly: textOnly, mode: 'regist', pwd: QR.persona.getPassword() }; @@ -22402,6 +22482,9 @@ Main = (function() { boards: {} }; } + Conf['boardConfig'] = { + boards: {} + }; Conf['archives'] = Redirect.archives; Conf['selectedArchives'] = {}; Conf['cooldowns'] = {}; @@ -22898,7 +22981,7 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['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], ['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], ['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], ['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], ['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], ['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 058d187f4..b14e82f5f 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 4f5b01b1e..7498d9d11 100644 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.12.1.5 +// @version 1.12.2.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 7c97c4ef9..dd2ecd5f9 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.12.1.5 +// @version 1.12.2.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -121,7 +121,7 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, 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, Sauce, Settings, ShimSet, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, 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, Sauce, Settings, ShimSet, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; var Conf, E, c, d, doc, docSet, g; @@ -136,7 +136,7 @@ docSet = function() { }; g = { - VERSION: '1.12.1.5', + VERSION: '1.12.2.0', NAMESPACE: '4chan X.', boards: {} }; @@ -2466,8 +2466,8 @@ input[name=\"Default Volume\"] {\n\ #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show),\n\ body:not(.board_f) #qr select[name=\"filetag\"],\n\ #qr.reply-to-thread select[name=\"filetag\"],\n\ -body:not(.board_jp) #sjis-toggle,\n\ -body:not(.board_sci) #tex-preview-button,\n\ +#qr:not(.has-sjis) #sjis-toggle,\n\ +#qr:not(.has-math) #tex-preview-button,\n\ #qr.tex-preview .textarea > :not(#tex-preview),\n\ #qr:not(.tex-preview) #tex-preview {\n\ display: none;\n\ @@ -4602,15 +4602,18 @@ $ = (function() { }).call(this); $$ = (function() { - var slice = [].slice; + var $$, + slice = [].slice; - return function(selector, root) { + $$ = function(selector, root) { if (root == null) { root = d.body; } return slice.call(root.querySelectorAll(selector)); }; + return $$; + }).call(this); CrossOrigin = (function() { @@ -4735,9 +4738,11 @@ Board = (function() { }; function Board(ID) { + var ref; this.ID = ID; this.threads = new SimpleDict(); this.posts = new SimpleDict(); + this.config = ((ref = BoardConfig.boards) != null ? ref[this.ID] : void 0) || {}; g.boards[this] = this; } @@ -7492,6 +7497,77 @@ ThreadHiding = (function() { }).call(this); +BoardConfig = (function() { + var BoardConfig; + + BoardConfig = { + cbs: [], + init: function() { + if ((Conf['boardConfig'].lastChecked || 0) < Date.now() - 2 * $.HOUR) { + return $.ajax('//a.4cdn.org/boards.json', { + onloadend: this.load, + timeout: 5 * $.SECOND + }); + } else { + return this.set(Conf['boardConfig'].boards); + } + }, + load: function() { + var board, boards, err, i, len, ref; + if (this.status === 200 && this.response && this.response.boards) { + boards = {}; + ref = this.response.boards; + for (i = 0, len = ref.length; i < len; i++) { + board = ref[i]; + boards[board.board] = board; + } + $.set('boardConfig', { + boards: boards, + lastChecked: Date.now() + }); + } else { + boards = Conf['boardConfig'].boards; + err = (function() { + switch (this.status) { + case 0: + return 'Connection Error'; + case 200: + return 'Invalid Data'; + default: + return "Error " + this.statusText + " (" + this.status + ")"; + } + }).call(this); + new Notice('warning', "Failed to load board configuration. " + err, 20); + } + return BoardConfig.set(boards); + }, + set: function(boards1) { + var ID, board, cb, i, len, ref, ref1; + this.boards = boards1; + ref = g.boards; + for (ID in ref) { + board = ref[ID]; + board.config = this.boards[ID] || {}; + } + ref1 = this.cbs; + for (i = 0, len = ref1.length; i < len; i++) { + cb = ref1[i]; + $.queueTask(cb); + } + }, + ready: function(cb) { + if (this.boards) { + return cb(); + } else { + return this.cbs.push(cb); + } + } + }; + + return BoardConfig; + +}).call(this); + Build = (function() { var Build, slice = [].slice; @@ -9656,7 +9732,8 @@ Polyfill = (function() { Polyfill = { init: function() { - return this.toBlob(); + this.toBlob(); + $.global(this.toBlob); }, toBlob: function() { if (HTMLCanvasElement.prototype.toBlob) { @@ -9664,9 +9741,6 @@ Polyfill = (function() { } HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { var data, i, j, l, ref, ui8a, url; - if (type == null) { - type = 'image/png'; - } url = this.toDataURL(type, encoderOptions); data = atob(url.slice(url.indexOf(',') + 1)); l = data.length; @@ -9675,10 +9749,9 @@ Polyfill = (function() { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: type + type: type || 'image/png' })); }; - return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); } }; @@ -9959,6 +10032,7 @@ Settings = (function() { }, "export": function() { return $.get(Conf, function(Conf) { + delete Conf['boardConfig']; return Settings.downloadExport({ version: g.VERSION, date: Date.now(), @@ -10669,7 +10743,7 @@ Settings = (function() { }).call(this); UI = (function() { - var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, + var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; @@ -11137,13 +11211,15 @@ UI = (function() { return label; }; - return { + UI = { dialog: dialog, Menu: Menu, hover: hoverstart, checkbox: checkbox }; + return UI; + }).call(this); FappeTyme = (function() { @@ -15747,23 +15823,27 @@ Keybinds = (function() { return QR.nodes.com.focus(); }, tags: function(tag, ta) { - var range, selEnd, selStart, supported, value; - supported = (function() { - switch (tag) { - case 'spoiler': - return !!$('.postForm input[name=spoiler]'); - case 'code': - return g.BOARD.ID === 'g'; - case 'math': - case 'eqn': - return g.BOARD.ID === 'sci'; - case 'sjis': - return g.BOARD.ID === 'jp'; + var range, selEnd, selStart, value; + BoardConfig.ready(function() { + var config, supported; + config = g.BOARD.config; + supported = (function() { + switch (tag) { + case 'spoiler': + return !!config.spoilers; + case 'code': + return !!config.code_tags; + case 'math': + case 'eqn': + return !!config.math_tags; + case 'sjis': + return !!config.sjis_tags; + } + })(); + if (!supported) { + return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); } - })(); - if (!supported) { - new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); - } + }); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; @@ -19411,7 +19491,9 @@ QR = (function() { } version = Conf['Use Recaptcha v1'] && Main.jsEnabled ? 'v1' : 'v2'; this.captcha = Captcha[version]; - $.on(d, '4chanXInitFinished', this.initReady); + $.on(d, '4chanXInitFinished', function() { + return BoardConfig.ready(QR.initReady); + }); Callbacks.Post.push({ name: 'Quick Reply', cb: this.node @@ -19436,12 +19518,26 @@ QR = (function() { return Header.addShortcut('qr', sc, 540); }, initReady: function() { - var link, linkBot, navLinksBot, origToggle; - $.off(d, '4chanXInitFinished', this.initReady); + var config, link, linkBot, navLinksBot, origToggle, prop; QR.postingIsEnabled = !!$.id('postForm'); if (!QR.postingIsEnabled) { return; } + config = g.BOARD.config; + prop = function(key, def) { + var ref; + return +((ref = config[key]) != null ? ref : def); + }; + QR.min_width = prop('min_image_width', 1); + QR.min_height = prop('min_image_height', 1); + QR.max_width = QR.max_height = 10000; + QR.max_size = prop('max_filesize', 4194304); + QR.max_size_video = prop('max_webm_filesize', QR.max_size); + QR.max_comment = prop('max_comment_chars', 2000); + QR.max_width_video = QR.max_height_video = 2048; + QR.max_duration_video = prop('max_webm_duration', 120); + QR.forcedAnon = !!config.forced_anon; + QR.spoiler = !!config.spoilers; link = $.el('h1', { className: "qr-link-container" }); @@ -19796,30 +19892,34 @@ QR = (function() { return QR.handleFiles(e.dataTransfer.files); }, paste: function(e) { - var blob, files, item, j, len, ref; + var blob, file, file2, item, j, len, ref, score, score2, type; if (!e.clipboardData.items) { return; } - files = []; + file = null; + score = -1; ref = e.clipboardData.items; for (j = 0, len = ref.length; j < len; j++) { item = ref[j]; - if (!(item.kind === 'file')) { + if (!(item.kind === 'file' && (file2 = item.getAsFile()))) { continue; } - blob = item.getAsFile(); - blob.name = 'file'; - if (blob.type) { - blob.name += '.' + blob.type.split('/')[1]; + score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png'); + if (score2 > score) { + file = file2; + score = score2; } - files.push(blob); } - if (!files.length) { - return; + if (file) { + type = file.type; + blob = new Blob([file], { + type: type + }); + blob.name = "file." + (QR.extensionFromType[type] || 'jpg'); + QR.open(); + QR.handleFiles([blob]); + $.addClass(QR.nodes.el, 'dump'); } - QR.open(); - QR.handleFiles(files); - return $.addClass(QR.nodes.el, 'dump'); }, pasteFF: function() { var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src; @@ -19930,7 +20030,7 @@ QR = (function() { return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); }, dialog: function() { - var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; + var classList, config, dialog, event, i, items, name, node, nodes, save, setNode; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { innerHTML: "
×
+
No selected file
" @@ -19970,33 +20070,14 @@ QR = (function() { setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); setNode('fileInput', '[type=file]'); - rules = $('ul.rules').textContent.trim(); - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/); - match_max = rules.match(/.+greater than (\d+)x(\d+).+/); - QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1; - QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1; - QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000; - QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000; - scriptData = Get.scriptData(); - QR.max_size = (m = scriptData.match(/\bmaxFilesize *= *(\d+)\b/)) ? +m[1] : 4194304; - QR.max_size_video = (m = scriptData.match(/\bmaxWebmFilesize *= *(\d+)\b/)) ? +m[1] : QR.max_size; - QR.max_comment = (m = scriptData.match(/\bcomlen *= *(\d+)\b/)) ? +m[1] : 2000; - QR.max_width_video = QR.max_height_video = 2048; - QR.max_duration_video = (ref = g.BOARD.ID) === 'gif' || ref === 'wsg' ? 300 : 120; - if (Conf['Show New Thread Option in Threads']) { - $.addClass(QR.nodes.el, 'show-new-thread-option'); - } - QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]'); - if (QR.forcedAnon) { - $.addClass(QR.nodes.el, 'forced-anon'); - } - QR.spoiler = !!$('.postForm input[name=spoiler]'); - if (QR.spoiler) { - $.addClass(QR.nodes.el, 'has-spoiler'); - } - if (g.BOARD.ID === 'jp' && Conf['sjisPreview']) { - $.addClass(QR.nodes.el, 'sjis-preview'); - } + config = g.BOARD.config; + classList = QR.nodes.el.classList; + classList.toggle('forced-anon', QR.forcedAnon); + classList.toggle('has-spoiler', QR.spoiler); + classList.toggle('has-sjis', !!config.sjis_tags); + classList.toggle('has-math', !!config.math_tags); + classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']); + classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']); if (parseInt(Conf['customCooldown'], 10) > 0) { $.addClass(QR.nodes.fileSubmit, 'custom-cooldown'); $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) { @@ -20042,7 +20123,7 @@ QR = (function() { window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); - if ($.engine === 'gecko') { + if ($.engine === 'gecko' && !window.DataTransferItemList) { nodes.pasteArea.hidden = false; new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { childList: true @@ -20083,7 +20164,7 @@ QR = (function() { return $.event('QRDialogCreation', null, dialog); }, submit: function(e) { - var captcha, cb, err, extra, filetag, formData, options, post, ref, textOnly, thread, threadID; + var captcha, cb, err, extra, filetag, formData, options, post, ref, thread, threadID; if (e != null) { e.preventDefault(); } @@ -20105,9 +20186,9 @@ QR = (function() { } if (threadID === 'new') { threadID = null; - if (g.BOARD.ID === 'vg' && !post.sub) { + if (!!g.BOARD.config.require_subject && !post.sub) { err = 'New threads require a subject.'; - } else if (!($.hasClass(d.body, 'text_only') || post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { + } else if (!(!!g.BOARD.config.text_only || post.file)) { err = 'No file selected.'; } } else if (g.BOARD.threads[threadID].isClosed) { @@ -20151,7 +20232,6 @@ QR = (function() { upfile: post.file, filetag: filetag, spoiler: post.spoiler, - textonly: textOnly, mode: 'regist', pwd: QR.persona.getPassword() }; @@ -22402,6 +22482,9 @@ Main = (function() { boards: {} }; } + Conf['boardConfig'] = { + boards: {} + }; Conf['archives'] = Redirect.archives; Conf['selectedArchives'] = {}; Conf['cooldowns'] = {}; @@ -22898,7 +22981,7 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['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], ['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], ['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], ['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], ['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], ['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 4968bc897..53bee736a 100644 Binary files a/builds/4chan-X.zip and b/builds/4chan-X.zip differ diff --git a/builds/updates-beta.xml b/builds/updates-beta.xml index 93cb00040..611ced3bd 100644 --- a/builds/updates-beta.xml +++ b/builds/updates-beta.xml @@ -1,7 +1,7 @@ - + diff --git a/builds/updates.xml b/builds/updates.xml index 37f946614..253f56388 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/version.json b/version.json index 434004df4..02531b18b 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "1.12.1.5", - "date": "2016-07-10T20:36:24.056Z" + "version": "1.12.2.0", + "date": "2016-07-10T23:41:35.741Z" } \ No newline at end of file