diff --git a/CHANGELOG.md b/CHANGELOG.md index d50887ade..a5b0ebd9d 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ Sometimes the changelog has notes (not comprehensive) acknowledging people's wor ### v1.11.14 +**v1.11.14.3** *(2015-10-25)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.14.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.14.3/builds/4chan-X-noupdate.crx "Chromium version")] +- Support opening a blank canvas from boards other than /i/. With no file selected, oekaki button in QR shows/hides the new image controls. +- Support resume (retaining layers and undo history) in Tegaki when possible. Only available when re-opening the last image edited. +- The edit button now quotes the post with the image being edited. +- Minor bugfixes. + **v1.11.14.2** *(2015-10-25)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.14.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.14.2/builds/4chan-X-noupdate.crx "Chromium version")] - Merge v1.11.13.10: (MayhemYDG) Update archive list: Update fireden.net and remove poweredby.moe. diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx index b60505492..0a1a57870 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 681f945d9..213c1db86 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.11.14.2 +// @version 1.11.14.3 // @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 60da1a670..5aa8b569a 100644 --- a/builds/4chan-X-beta.user.js +++ b/builds/4chan-X-beta.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X beta -// @version 1.11.14.2 +// @version 1.11.14.3 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -181,7 +181,7 @@ 'Gallery': [true, 'Adds a simple and cute image gallery.'], 'Fullscreen Gallery': [false, 'Open gallery in fullscreen mode.', 1], 'PDF in Gallery': [false, 'Show PDF files in gallery.', 1], - 'Oekaki Links': [true, 'Add links to edit images in oekaki painter.'], + 'Oekaki Links': [true, 'Add links to edit images in Tegaki, /i/\'s painting program.'], 'Sauce': [true, 'Add sauce links to images.'], 'WEBM Metadata': [true, 'Add link to fetch title metadata from webm videos.'], 'Reveal Spoiler Thumbnails': [false, 'Replace spoiler thumbnails with the original image.'], @@ -414,7 +414,7 @@ doc = d.documentElement; g = { - VERSION: '1.11.14.2', + VERSION: '1.11.14.3', NAMESPACE: '4chan X.', boards: {} }; @@ -2526,16 +2526,20 @@ if (HTMLCanvasElement.prototype.toBlob) { return; } - HTMLCanvasElement.prototype.toBlob = function(cb) { - var data, i, k, l, ref, ui8a; - data = atob(this.toDataURL().slice(22)); + HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { + var data, i, k, 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; ui8a = new Uint8Array(l); for (i = k = 0, ref = l; k < ref; i = k += 1) { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: 'image/png' + type: type })); }; return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); @@ -4465,6 +4469,9 @@ }, postFromRoot: function(root) { var boardID, index, link, post, postID; + if (root == null) { + return null; + } link = $('.postNum > a[href*="#"]', root); boardID = link.pathname.split(/\/+/)[1]; postID = link.hash.slice(2); @@ -7276,11 +7283,14 @@ return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0); }, setFile: function(e) { - var file, name, ref; - ref = e.detail, file = ref.file, name = ref.name; + var file, name, ref, source; + ref = e.detail, file = ref.file, name = ref.name, source = ref.source; if (name != null) { file.name = name; } + if (source != null) { + file.source = source; + } QR.open(); return QR.handleFiles([file]); }, @@ -7440,7 +7450,7 @@ var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { - innerHTML: "
×
+
No selected file
" + innerHTML: "
×
+
No selected file
" }) }; setNode = function(name, query) { @@ -7448,36 +7458,35 @@ }; setNode('move', '.move'); setNode('autohide', '#autohide'); - setNode('thread', 'select'); - setNode('threadPar', '#qr-thread-select'); setNode('close', '.close'); + setNode('thread', 'select'); setNode('form', 'form'); - setNode('dumpButton', '#dump-button'); - setNode('pasteArea', '#paste-area'); - setNode('urlButton', '#url-button'); setNode('sjisToggle', '#sjis-toggle'); setNode('texButton', '#tex-preview-button'); setNode('name', '[data-name=name]'); setNode('email', '[data-name=email]'); setNode('sub', '[data-name=sub]'); setNode('com', '[data-name=com]'); + setNode('charCount', '#char-count'); setNode('texPreview', '#tex-preview'); setNode('dumpList', '#dump-list'); setNode('addPost', '#add-post'); - setNode('charCount', '#char-count'); + setNode('oekaki', '.oekaki'); + setNode('drawButton', '#qr-draw-button'); setNode('fileSubmit', '#file-n-submit'); setNode('fileButton', '#qr-file-button'); setNode('noFile', '#qr-no-file'); setNode('filename', '#qr-filename'); - setNode('fileRM', '#qr-filerm'); setNode('spoiler', '#qr-file-spoiler'); - setNode('spoilerPar', '#qr-spoiler-label'); - setNode('status', '[type=submit]'); - setNode('fileInput', '[type=file]'); + setNode('oekakiButton', '#qr-oekaki-button'); + setNode('fileRM', '#qr-filerm'); + setNode('urlButton', '#url-button'); + setNode('pasteArea', '#paste-area'); setNode('customCooldown', '#custom-cooldown-button'); + setNode('dumpButton', '#dump-button'); + setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); - setNode('drawButton', '#qr-draw-button'); - setNode('editButton', '#qr-edit-button'); + 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+).+/); @@ -7519,6 +7528,16 @@ return $.sync('customCooldownEnabled', QR.setCustomCooldown); }); } + $.on(nodes.autohide, 'change', QR.toggleHide); + $.on(nodes.close, 'click', QR.close); + $.on(nodes.form, 'submit', QR.submit); + $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); + $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); + $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); + $.on(nodes.addPost, 'click', function() { + return new QR.post(true); + }); + $.on(nodes.drawButton, 'click', QR.oekaki.draw); $.on(nodes.fileButton, 'click', QR.openFileInput); $.on(nodes.noFile, 'click', QR.openFileInput); $.on(nodes.filename, 'focus', function() { @@ -7527,31 +7546,21 @@ $.on(nodes.filename, 'blur', function() { return $.rmClass(this.parentNode, 'focus'); }); - $.on(nodes.autohide, 'change', QR.toggleHide); - $.on(nodes.close, 'click', QR.close); - $.on(nodes.dumpButton, 'click', function() { - return nodes.el.classList.toggle('dump'); + $.on(nodes.spoiler, 'change', function() { + return QR.selected.nodes.spoiler.click(); + }); + $.on(nodes.oekakiButton, 'click', QR.oekaki.button); + $.on(nodes.fileRM, 'click', function() { + return QR.selected.rmFile(); }); $.on(nodes.urlButton, 'click', function() { return QR.handleUrl(''); }); - $.on(nodes.addPost, 'click', function() { - return new QR.post(true); - }); - $.on(nodes.form, 'submit', QR.submit); - $.on(nodes.fileRM, 'click', function() { - return QR.selected.rmFile(); - }); - $.on(nodes.spoiler, 'change', function() { - return QR.selected.nodes.spoiler.click(); + $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown); + $.on(nodes.dumpButton, 'click', function() { + return nodes.el.classList.toggle('dump'); }); $.on(nodes.fileInput, 'change', QR.handleFiles); - $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); - $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); - $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); - $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown); - $.on(nodes.drawButton, 'click', QR.oekaki.draw); - $.on(nodes.editButton, 'click', QR.oekaki.edit); window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); @@ -7592,6 +7601,7 @@ QR.captcha.init(); $.add(d.body, dialog); QR.captcha.setup(); + QR.oekaki.setup(); return $.event('QRDialogCreation', null, dialog); }, submit: function(e) { @@ -8946,12 +8956,12 @@ return; } if (this.isClone) { - link = $('.file-edit', this.file.text); + link = $('.file-oekaki', this.file.text); } else { link = $.el('a', { - className: 'file-edit', + className: 'file-oekaki', href: 'javascript:;', - title: 'Edit image' + title: 'Edit in Tegaki' }); $.extend(link, { innerHTML: "" @@ -8962,15 +8972,12 @@ }, editFile: function() { var post; + if (!QR.postingIsEnabled) { + return; + } + QR.quote.call(this); post = Get.postFromNode(this); return CrossOrigin.file(post.file.url, function(blob) { - var com, ref, thread; - QR.openPost(); - ref = QR.nodes, com = ref.com, thread = ref.thread; - if (!com.value) { - thread.value = (post.context || post).thread.ID; - } - QR.selected.save(thread); if (blob) { blob.name = post.file.name; QR.handleFiles([blob]); @@ -8980,6 +8987,30 @@ } }); }, + setup: function() { + return $.global(function() { + var FCX; + FCX = window.FCX; + FCX.oekakiCB = function() { + return window.Tegaki.flatten().toBlob(function(file) { + var source; + source = "oekaki-" + (Date.now()); + FCX.oekakiLatest = source; + return document.dispatchEvent(new CustomEvent('QRSetFile', { + bubbles: true, + detail: { + file: file, + name: FCX.oekakiName, + source: source + } + })); + }); + }; + if (window.Tegaki) { + return document.querySelector('#qr .oekaki').hidden = false; + } + }); + }, load: function(cb) { var n, onload, script, style; if ($('script[src^="//s.4cdn.org/js/painter"]', d.head)) { @@ -9005,32 +9036,34 @@ }, draw: function() { return $.global(function() { - var Tegaki; - Tegaki = window.Tegaki; + var FCX, Tegaki; + Tegaki = window.Tegaki, FCX = window.FCX; + if (Tegaki.bg) { + Tegaki.destroy(); + } + FCX.oekakiName = 'tegaki.png'; return Tegaki.open({ - onDone: function() { - return Tegaki.flatten().toBlob(function(file) { - return document.dispatchEvent(new CustomEvent('QRSetFile', { - bubbles: true, - detail: { - file: file, - name: 'tegaki.png' - } - })); - }); - }, + onDone: FCX.oekakiCB, onCancel: function() {}, width: +document.querySelector('#qr [name=oekaki-width]').value, height: +document.querySelector('#qr [name=oekaki-height]').value }); }); }, + button: function() { + if (QR.selected.file) { + return QR.oekaki.edit(); + } else { + return QR.oekaki.toggle(); + } + }, edit: function() { return QR.oekaki.load(function() { return $.global(function() { - var Tegaki, cb, error, name; - Tegaki = window.Tegaki; + var FCX, Tegaki, cb, error, name, source; + Tegaki = window.Tegaki, FCX = window.FCX; name = document.getElementById('qr-filename').value.replace(/\.\w+$/, '') + '.png'; + source = document.getElementById('file-n-submit').dataset.source; error = function(content) { return document.dispatchEvent(new CustomEvent('CreateNotification', { bubbles: true, @@ -9058,18 +9091,9 @@ if (Tegaki.bg) { Tegaki.destroy(); } + FCX.oekakiName = name; Tegaki.open({ - onDone: function() { - return Tegaki.flatten().toBlob(function(file) { - return document.dispatchEvent(new CustomEvent('QRSetFile', { - bubbles: true, - detail: { - file: file, - name: name - } - })); - }); - }, + onDone: FCX.oekakiCB, onCancel: function() {}, width: img.naturalWidth, height: img.naturalHeight, @@ -9079,12 +9103,22 @@ }; return img.src = URL.createObjectURL(e.detail); }; - document.addEventListener('QRFile', cb, false); - return document.dispatchEvent(new CustomEvent('QRGetFile', { - bubbles: true - })); + if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) { + FCX.oekakiName = name; + return Tegaki.resume(); + } else { + document.addEventListener('QRFile', cb, false); + return document.dispatchEvent(new CustomEvent('QRGetFile', { + bubbles: true + })); + } }); }); + }, + toggle: function() { + return QR.oekaki.load(function() { + return QR.nodes.oekaki.hidden = !QR.nodes.oekaki.hidden; + }); } }; @@ -9620,13 +9654,21 @@ }; _Class.prototype.showFileData = function() { + var ref; if (this.file) { this.updateFilename(); QR.nodes.filename.value = this.filename; + $.addClass(QR.nodes.oekaki, 'has-file'); $.addClass(QR.nodes.fileSubmit, 'has-file'); } else { + $.rmClass(QR.nodes.oekaki, 'has-file'); $.rmClass(QR.nodes.fileSubmit, 'has-file'); } + if (((ref = this.file) != null ? ref.source : void 0) != null) { + QR.nodes.fileSubmit.dataset.source = this.file.source; + } else { + QR.nodes.fileSubmit.removeAttribute('data-source'); + } return QR.nodes.spoiler.checked = this.spoiler; }; @@ -17235,7 +17277,10 @@ initFeatures: function() { var err, feature, k, len1, match, name, pathname, ref, ref1, ref2; if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'sys.4chan.org' || ref === 'www.4chan.org') { - $.globalEval('document.documentElement.classList.add("js-enabled");'); + $.global(function() { + document.documentElement.classList.add('js-enabled'); + return window.FCX = {}; + }); } switch (location.hostname) { case 'www.4chan.org': @@ -19446,7 +19491,6 @@ "#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_i) .oekaki,\n" + "body:not(.board_jp) #sjis-toggle,\n" + "body:not(.board_sci) #tex-preview-button,\n" + "#qr.tex-preview .textarea > :not(#tex-preview),\n" + @@ -19611,6 +19655,9 @@ "#qr-no-file {\n" + " color: #AAA;\n" + "}\n" + +".oekaki.has-file {\n" + +" display: none;\n" + +"}\n" + ".oekaki > label {\n" + " -webkit-flex: 1 1 auto;\n" + " flex: 1 1 auto;\n" + @@ -19643,7 +19690,6 @@ "}\n" + "#qr:not(.has-spoiler) #qr-spoiler-label,\n" + "#file-n-submit:not(.has-file) #qr-spoiler-label,\n" + -"#file-n-submit:not(.has-file) #qr-edit-button,\n" + ".has-file #paste-area,\n" + ".has-file #url-button,\n" + "#file-n-submit:not(.custom-cooldown) #custom-cooldown-button {\n" + diff --git a/builds/4chan-X-noupdate.crx b/builds/4chan-X-noupdate.crx index 46c57ab77..a463df0ea 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 bffa2cb50..786e191d7 100644 --- a/builds/4chan-X-noupdate.user.js +++ b/builds/4chan-X-noupdate.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X -// @version 1.11.14.2 +// @version 1.11.14.3 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -181,7 +181,7 @@ 'Gallery': [true, 'Adds a simple and cute image gallery.'], 'Fullscreen Gallery': [false, 'Open gallery in fullscreen mode.', 1], 'PDF in Gallery': [false, 'Show PDF files in gallery.', 1], - 'Oekaki Links': [true, 'Add links to edit images in oekaki painter.'], + 'Oekaki Links': [true, 'Add links to edit images in Tegaki, /i/\'s painting program.'], 'Sauce': [true, 'Add sauce links to images.'], 'WEBM Metadata': [true, 'Add link to fetch title metadata from webm videos.'], 'Reveal Spoiler Thumbnails': [false, 'Replace spoiler thumbnails with the original image.'], @@ -414,7 +414,7 @@ doc = d.documentElement; g = { - VERSION: '1.11.14.2', + VERSION: '1.11.14.3', NAMESPACE: '4chan X.', boards: {} }; @@ -2526,16 +2526,20 @@ if (HTMLCanvasElement.prototype.toBlob) { return; } - HTMLCanvasElement.prototype.toBlob = function(cb) { - var data, i, k, l, ref, ui8a; - data = atob(this.toDataURL().slice(22)); + HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { + var data, i, k, 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; ui8a = new Uint8Array(l); for (i = k = 0, ref = l; k < ref; i = k += 1) { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: 'image/png' + type: type })); }; return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); @@ -4465,6 +4469,9 @@ }, postFromRoot: function(root) { var boardID, index, link, post, postID; + if (root == null) { + return null; + } link = $('.postNum > a[href*="#"]', root); boardID = link.pathname.split(/\/+/)[1]; postID = link.hash.slice(2); @@ -7276,11 +7283,14 @@ return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0); }, setFile: function(e) { - var file, name, ref; - ref = e.detail, file = ref.file, name = ref.name; + var file, name, ref, source; + ref = e.detail, file = ref.file, name = ref.name, source = ref.source; if (name != null) { file.name = name; } + if (source != null) { + file.source = source; + } QR.open(); return QR.handleFiles([file]); }, @@ -7440,7 +7450,7 @@ var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { - innerHTML: "
×
+
No selected file
" + innerHTML: "
×
+
No selected file
" }) }; setNode = function(name, query) { @@ -7448,36 +7458,35 @@ }; setNode('move', '.move'); setNode('autohide', '#autohide'); - setNode('thread', 'select'); - setNode('threadPar', '#qr-thread-select'); setNode('close', '.close'); + setNode('thread', 'select'); setNode('form', 'form'); - setNode('dumpButton', '#dump-button'); - setNode('pasteArea', '#paste-area'); - setNode('urlButton', '#url-button'); setNode('sjisToggle', '#sjis-toggle'); setNode('texButton', '#tex-preview-button'); setNode('name', '[data-name=name]'); setNode('email', '[data-name=email]'); setNode('sub', '[data-name=sub]'); setNode('com', '[data-name=com]'); + setNode('charCount', '#char-count'); setNode('texPreview', '#tex-preview'); setNode('dumpList', '#dump-list'); setNode('addPost', '#add-post'); - setNode('charCount', '#char-count'); + setNode('oekaki', '.oekaki'); + setNode('drawButton', '#qr-draw-button'); setNode('fileSubmit', '#file-n-submit'); setNode('fileButton', '#qr-file-button'); setNode('noFile', '#qr-no-file'); setNode('filename', '#qr-filename'); - setNode('fileRM', '#qr-filerm'); setNode('spoiler', '#qr-file-spoiler'); - setNode('spoilerPar', '#qr-spoiler-label'); - setNode('status', '[type=submit]'); - setNode('fileInput', '[type=file]'); + setNode('oekakiButton', '#qr-oekaki-button'); + setNode('fileRM', '#qr-filerm'); + setNode('urlButton', '#url-button'); + setNode('pasteArea', '#paste-area'); setNode('customCooldown', '#custom-cooldown-button'); + setNode('dumpButton', '#dump-button'); + setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); - setNode('drawButton', '#qr-draw-button'); - setNode('editButton', '#qr-edit-button'); + 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+).+/); @@ -7519,6 +7528,16 @@ return $.sync('customCooldownEnabled', QR.setCustomCooldown); }); } + $.on(nodes.autohide, 'change', QR.toggleHide); + $.on(nodes.close, 'click', QR.close); + $.on(nodes.form, 'submit', QR.submit); + $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); + $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); + $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); + $.on(nodes.addPost, 'click', function() { + return new QR.post(true); + }); + $.on(nodes.drawButton, 'click', QR.oekaki.draw); $.on(nodes.fileButton, 'click', QR.openFileInput); $.on(nodes.noFile, 'click', QR.openFileInput); $.on(nodes.filename, 'focus', function() { @@ -7527,31 +7546,21 @@ $.on(nodes.filename, 'blur', function() { return $.rmClass(this.parentNode, 'focus'); }); - $.on(nodes.autohide, 'change', QR.toggleHide); - $.on(nodes.close, 'click', QR.close); - $.on(nodes.dumpButton, 'click', function() { - return nodes.el.classList.toggle('dump'); + $.on(nodes.spoiler, 'change', function() { + return QR.selected.nodes.spoiler.click(); + }); + $.on(nodes.oekakiButton, 'click', QR.oekaki.button); + $.on(nodes.fileRM, 'click', function() { + return QR.selected.rmFile(); }); $.on(nodes.urlButton, 'click', function() { return QR.handleUrl(''); }); - $.on(nodes.addPost, 'click', function() { - return new QR.post(true); - }); - $.on(nodes.form, 'submit', QR.submit); - $.on(nodes.fileRM, 'click', function() { - return QR.selected.rmFile(); - }); - $.on(nodes.spoiler, 'change', function() { - return QR.selected.nodes.spoiler.click(); + $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown); + $.on(nodes.dumpButton, 'click', function() { + return nodes.el.classList.toggle('dump'); }); $.on(nodes.fileInput, 'change', QR.handleFiles); - $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); - $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); - $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); - $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown); - $.on(nodes.drawButton, 'click', QR.oekaki.draw); - $.on(nodes.editButton, 'click', QR.oekaki.edit); window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); @@ -7592,6 +7601,7 @@ QR.captcha.init(); $.add(d.body, dialog); QR.captcha.setup(); + QR.oekaki.setup(); return $.event('QRDialogCreation', null, dialog); }, submit: function(e) { @@ -8946,12 +8956,12 @@ return; } if (this.isClone) { - link = $('.file-edit', this.file.text); + link = $('.file-oekaki', this.file.text); } else { link = $.el('a', { - className: 'file-edit', + className: 'file-oekaki', href: 'javascript:;', - title: 'Edit image' + title: 'Edit in Tegaki' }); $.extend(link, { innerHTML: "" @@ -8962,15 +8972,12 @@ }, editFile: function() { var post; + if (!QR.postingIsEnabled) { + return; + } + QR.quote.call(this); post = Get.postFromNode(this); return CrossOrigin.file(post.file.url, function(blob) { - var com, ref, thread; - QR.openPost(); - ref = QR.nodes, com = ref.com, thread = ref.thread; - if (!com.value) { - thread.value = (post.context || post).thread.ID; - } - QR.selected.save(thread); if (blob) { blob.name = post.file.name; QR.handleFiles([blob]); @@ -8980,6 +8987,30 @@ } }); }, + setup: function() { + return $.global(function() { + var FCX; + FCX = window.FCX; + FCX.oekakiCB = function() { + return window.Tegaki.flatten().toBlob(function(file) { + var source; + source = "oekaki-" + (Date.now()); + FCX.oekakiLatest = source; + return document.dispatchEvent(new CustomEvent('QRSetFile', { + bubbles: true, + detail: { + file: file, + name: FCX.oekakiName, + source: source + } + })); + }); + }; + if (window.Tegaki) { + return document.querySelector('#qr .oekaki').hidden = false; + } + }); + }, load: function(cb) { var n, onload, script, style; if ($('script[src^="//s.4cdn.org/js/painter"]', d.head)) { @@ -9005,32 +9036,34 @@ }, draw: function() { return $.global(function() { - var Tegaki; - Tegaki = window.Tegaki; + var FCX, Tegaki; + Tegaki = window.Tegaki, FCX = window.FCX; + if (Tegaki.bg) { + Tegaki.destroy(); + } + FCX.oekakiName = 'tegaki.png'; return Tegaki.open({ - onDone: function() { - return Tegaki.flatten().toBlob(function(file) { - return document.dispatchEvent(new CustomEvent('QRSetFile', { - bubbles: true, - detail: { - file: file, - name: 'tegaki.png' - } - })); - }); - }, + onDone: FCX.oekakiCB, onCancel: function() {}, width: +document.querySelector('#qr [name=oekaki-width]').value, height: +document.querySelector('#qr [name=oekaki-height]').value }); }); }, + button: function() { + if (QR.selected.file) { + return QR.oekaki.edit(); + } else { + return QR.oekaki.toggle(); + } + }, edit: function() { return QR.oekaki.load(function() { return $.global(function() { - var Tegaki, cb, error, name; - Tegaki = window.Tegaki; + var FCX, Tegaki, cb, error, name, source; + Tegaki = window.Tegaki, FCX = window.FCX; name = document.getElementById('qr-filename').value.replace(/\.\w+$/, '') + '.png'; + source = document.getElementById('file-n-submit').dataset.source; error = function(content) { return document.dispatchEvent(new CustomEvent('CreateNotification', { bubbles: true, @@ -9058,18 +9091,9 @@ if (Tegaki.bg) { Tegaki.destroy(); } + FCX.oekakiName = name; Tegaki.open({ - onDone: function() { - return Tegaki.flatten().toBlob(function(file) { - return document.dispatchEvent(new CustomEvent('QRSetFile', { - bubbles: true, - detail: { - file: file, - name: name - } - })); - }); - }, + onDone: FCX.oekakiCB, onCancel: function() {}, width: img.naturalWidth, height: img.naturalHeight, @@ -9079,12 +9103,22 @@ }; return img.src = URL.createObjectURL(e.detail); }; - document.addEventListener('QRFile', cb, false); - return document.dispatchEvent(new CustomEvent('QRGetFile', { - bubbles: true - })); + if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) { + FCX.oekakiName = name; + return Tegaki.resume(); + } else { + document.addEventListener('QRFile', cb, false); + return document.dispatchEvent(new CustomEvent('QRGetFile', { + bubbles: true + })); + } }); }); + }, + toggle: function() { + return QR.oekaki.load(function() { + return QR.nodes.oekaki.hidden = !QR.nodes.oekaki.hidden; + }); } }; @@ -9620,13 +9654,21 @@ }; _Class.prototype.showFileData = function() { + var ref; if (this.file) { this.updateFilename(); QR.nodes.filename.value = this.filename; + $.addClass(QR.nodes.oekaki, 'has-file'); $.addClass(QR.nodes.fileSubmit, 'has-file'); } else { + $.rmClass(QR.nodes.oekaki, 'has-file'); $.rmClass(QR.nodes.fileSubmit, 'has-file'); } + if (((ref = this.file) != null ? ref.source : void 0) != null) { + QR.nodes.fileSubmit.dataset.source = this.file.source; + } else { + QR.nodes.fileSubmit.removeAttribute('data-source'); + } return QR.nodes.spoiler.checked = this.spoiler; }; @@ -17235,7 +17277,10 @@ initFeatures: function() { var err, feature, k, len1, match, name, pathname, ref, ref1, ref2; if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'sys.4chan.org' || ref === 'www.4chan.org') { - $.globalEval('document.documentElement.classList.add("js-enabled");'); + $.global(function() { + document.documentElement.classList.add('js-enabled'); + return window.FCX = {}; + }); } switch (location.hostname) { case 'www.4chan.org': @@ -19446,7 +19491,6 @@ "#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_i) .oekaki,\n" + "body:not(.board_jp) #sjis-toggle,\n" + "body:not(.board_sci) #tex-preview-button,\n" + "#qr.tex-preview .textarea > :not(#tex-preview),\n" + @@ -19611,6 +19655,9 @@ "#qr-no-file {\n" + " color: #AAA;\n" + "}\n" + +".oekaki.has-file {\n" + +" display: none;\n" + +"}\n" + ".oekaki > label {\n" + " -webkit-flex: 1 1 auto;\n" + " flex: 1 1 auto;\n" + @@ -19643,7 +19690,6 @@ "}\n" + "#qr:not(.has-spoiler) #qr-spoiler-label,\n" + "#file-n-submit:not(.has-file) #qr-spoiler-label,\n" + -"#file-n-submit:not(.has-file) #qr-edit-button,\n" + ".has-file #paste-area,\n" + ".has-file #url-button,\n" + "#file-n-submit:not(.custom-cooldown) #custom-cooldown-button {\n" + diff --git a/builds/4chan-X.crx b/builds/4chan-X.crx index 7b5e6a552..6f689b6cd 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 82183e5f9..0589d599f 100644 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.11.14.2 +// @version 1.11.14.3 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index f95f8a7c6..5803dfdbf 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X -// @version 1.11.14.2 +// @version 1.11.14.3 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -181,7 +181,7 @@ 'Gallery': [true, 'Adds a simple and cute image gallery.'], 'Fullscreen Gallery': [false, 'Open gallery in fullscreen mode.', 1], 'PDF in Gallery': [false, 'Show PDF files in gallery.', 1], - 'Oekaki Links': [true, 'Add links to edit images in oekaki painter.'], + 'Oekaki Links': [true, 'Add links to edit images in Tegaki, /i/\'s painting program.'], 'Sauce': [true, 'Add sauce links to images.'], 'WEBM Metadata': [true, 'Add link to fetch title metadata from webm videos.'], 'Reveal Spoiler Thumbnails': [false, 'Replace spoiler thumbnails with the original image.'], @@ -414,7 +414,7 @@ doc = d.documentElement; g = { - VERSION: '1.11.14.2', + VERSION: '1.11.14.3', NAMESPACE: '4chan X.', boards: {} }; @@ -2526,16 +2526,20 @@ if (HTMLCanvasElement.prototype.toBlob) { return; } - HTMLCanvasElement.prototype.toBlob = function(cb) { - var data, i, k, l, ref, ui8a; - data = atob(this.toDataURL().slice(22)); + HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { + var data, i, k, 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; ui8a = new Uint8Array(l); for (i = k = 0, ref = l; k < ref; i = k += 1) { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: 'image/png' + type: type })); }; return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); @@ -4465,6 +4469,9 @@ }, postFromRoot: function(root) { var boardID, index, link, post, postID; + if (root == null) { + return null; + } link = $('.postNum > a[href*="#"]', root); boardID = link.pathname.split(/\/+/)[1]; postID = link.hash.slice(2); @@ -7276,11 +7283,14 @@ return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0); }, setFile: function(e) { - var file, name, ref; - ref = e.detail, file = ref.file, name = ref.name; + var file, name, ref, source; + ref = e.detail, file = ref.file, name = ref.name, source = ref.source; if (name != null) { file.name = name; } + if (source != null) { + file.source = source; + } QR.open(); return QR.handleFiles([file]); }, @@ -7440,7 +7450,7 @@ var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { - innerHTML: "
×
+
No selected file
" + innerHTML: "
×
+
No selected file
" }) }; setNode = function(name, query) { @@ -7448,36 +7458,35 @@ }; setNode('move', '.move'); setNode('autohide', '#autohide'); - setNode('thread', 'select'); - setNode('threadPar', '#qr-thread-select'); setNode('close', '.close'); + setNode('thread', 'select'); setNode('form', 'form'); - setNode('dumpButton', '#dump-button'); - setNode('pasteArea', '#paste-area'); - setNode('urlButton', '#url-button'); setNode('sjisToggle', '#sjis-toggle'); setNode('texButton', '#tex-preview-button'); setNode('name', '[data-name=name]'); setNode('email', '[data-name=email]'); setNode('sub', '[data-name=sub]'); setNode('com', '[data-name=com]'); + setNode('charCount', '#char-count'); setNode('texPreview', '#tex-preview'); setNode('dumpList', '#dump-list'); setNode('addPost', '#add-post'); - setNode('charCount', '#char-count'); + setNode('oekaki', '.oekaki'); + setNode('drawButton', '#qr-draw-button'); setNode('fileSubmit', '#file-n-submit'); setNode('fileButton', '#qr-file-button'); setNode('noFile', '#qr-no-file'); setNode('filename', '#qr-filename'); - setNode('fileRM', '#qr-filerm'); setNode('spoiler', '#qr-file-spoiler'); - setNode('spoilerPar', '#qr-spoiler-label'); - setNode('status', '[type=submit]'); - setNode('fileInput', '[type=file]'); + setNode('oekakiButton', '#qr-oekaki-button'); + setNode('fileRM', '#qr-filerm'); + setNode('urlButton', '#url-button'); + setNode('pasteArea', '#paste-area'); setNode('customCooldown', '#custom-cooldown-button'); + setNode('dumpButton', '#dump-button'); + setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); - setNode('drawButton', '#qr-draw-button'); - setNode('editButton', '#qr-edit-button'); + 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+).+/); @@ -7519,6 +7528,16 @@ return $.sync('customCooldownEnabled', QR.setCustomCooldown); }); } + $.on(nodes.autohide, 'change', QR.toggleHide); + $.on(nodes.close, 'click', QR.close); + $.on(nodes.form, 'submit', QR.submit); + $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); + $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); + $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); + $.on(nodes.addPost, 'click', function() { + return new QR.post(true); + }); + $.on(nodes.drawButton, 'click', QR.oekaki.draw); $.on(nodes.fileButton, 'click', QR.openFileInput); $.on(nodes.noFile, 'click', QR.openFileInput); $.on(nodes.filename, 'focus', function() { @@ -7527,31 +7546,21 @@ $.on(nodes.filename, 'blur', function() { return $.rmClass(this.parentNode, 'focus'); }); - $.on(nodes.autohide, 'change', QR.toggleHide); - $.on(nodes.close, 'click', QR.close); - $.on(nodes.dumpButton, 'click', function() { - return nodes.el.classList.toggle('dump'); + $.on(nodes.spoiler, 'change', function() { + return QR.selected.nodes.spoiler.click(); + }); + $.on(nodes.oekakiButton, 'click', QR.oekaki.button); + $.on(nodes.fileRM, 'click', function() { + return QR.selected.rmFile(); }); $.on(nodes.urlButton, 'click', function() { return QR.handleUrl(''); }); - $.on(nodes.addPost, 'click', function() { - return new QR.post(true); - }); - $.on(nodes.form, 'submit', QR.submit); - $.on(nodes.fileRM, 'click', function() { - return QR.selected.rmFile(); - }); - $.on(nodes.spoiler, 'change', function() { - return QR.selected.nodes.spoiler.click(); + $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown); + $.on(nodes.dumpButton, 'click', function() { + return nodes.el.classList.toggle('dump'); }); $.on(nodes.fileInput, 'change', QR.handleFiles); - $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); - $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); - $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); - $.on(nodes.customCooldown, 'click', QR.toggleCustomCooldown); - $.on(nodes.drawButton, 'click', QR.oekaki.draw); - $.on(nodes.editButton, 'click', QR.oekaki.edit); window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); @@ -7592,6 +7601,7 @@ QR.captcha.init(); $.add(d.body, dialog); QR.captcha.setup(); + QR.oekaki.setup(); return $.event('QRDialogCreation', null, dialog); }, submit: function(e) { @@ -8946,12 +8956,12 @@ return; } if (this.isClone) { - link = $('.file-edit', this.file.text); + link = $('.file-oekaki', this.file.text); } else { link = $.el('a', { - className: 'file-edit', + className: 'file-oekaki', href: 'javascript:;', - title: 'Edit image' + title: 'Edit in Tegaki' }); $.extend(link, { innerHTML: "" @@ -8962,15 +8972,12 @@ }, editFile: function() { var post; + if (!QR.postingIsEnabled) { + return; + } + QR.quote.call(this); post = Get.postFromNode(this); return CrossOrigin.file(post.file.url, function(blob) { - var com, ref, thread; - QR.openPost(); - ref = QR.nodes, com = ref.com, thread = ref.thread; - if (!com.value) { - thread.value = (post.context || post).thread.ID; - } - QR.selected.save(thread); if (blob) { blob.name = post.file.name; QR.handleFiles([blob]); @@ -8980,6 +8987,30 @@ } }); }, + setup: function() { + return $.global(function() { + var FCX; + FCX = window.FCX; + FCX.oekakiCB = function() { + return window.Tegaki.flatten().toBlob(function(file) { + var source; + source = "oekaki-" + (Date.now()); + FCX.oekakiLatest = source; + return document.dispatchEvent(new CustomEvent('QRSetFile', { + bubbles: true, + detail: { + file: file, + name: FCX.oekakiName, + source: source + } + })); + }); + }; + if (window.Tegaki) { + return document.querySelector('#qr .oekaki').hidden = false; + } + }); + }, load: function(cb) { var n, onload, script, style; if ($('script[src^="//s.4cdn.org/js/painter"]', d.head)) { @@ -9005,32 +9036,34 @@ }, draw: function() { return $.global(function() { - var Tegaki; - Tegaki = window.Tegaki; + var FCX, Tegaki; + Tegaki = window.Tegaki, FCX = window.FCX; + if (Tegaki.bg) { + Tegaki.destroy(); + } + FCX.oekakiName = 'tegaki.png'; return Tegaki.open({ - onDone: function() { - return Tegaki.flatten().toBlob(function(file) { - return document.dispatchEvent(new CustomEvent('QRSetFile', { - bubbles: true, - detail: { - file: file, - name: 'tegaki.png' - } - })); - }); - }, + onDone: FCX.oekakiCB, onCancel: function() {}, width: +document.querySelector('#qr [name=oekaki-width]').value, height: +document.querySelector('#qr [name=oekaki-height]').value }); }); }, + button: function() { + if (QR.selected.file) { + return QR.oekaki.edit(); + } else { + return QR.oekaki.toggle(); + } + }, edit: function() { return QR.oekaki.load(function() { return $.global(function() { - var Tegaki, cb, error, name; - Tegaki = window.Tegaki; + var FCX, Tegaki, cb, error, name, source; + Tegaki = window.Tegaki, FCX = window.FCX; name = document.getElementById('qr-filename').value.replace(/\.\w+$/, '') + '.png'; + source = document.getElementById('file-n-submit').dataset.source; error = function(content) { return document.dispatchEvent(new CustomEvent('CreateNotification', { bubbles: true, @@ -9058,18 +9091,9 @@ if (Tegaki.bg) { Tegaki.destroy(); } + FCX.oekakiName = name; Tegaki.open({ - onDone: function() { - return Tegaki.flatten().toBlob(function(file) { - return document.dispatchEvent(new CustomEvent('QRSetFile', { - bubbles: true, - detail: { - file: file, - name: name - } - })); - }); - }, + onDone: FCX.oekakiCB, onCancel: function() {}, width: img.naturalWidth, height: img.naturalHeight, @@ -9079,12 +9103,22 @@ }; return img.src = URL.createObjectURL(e.detail); }; - document.addEventListener('QRFile', cb, false); - return document.dispatchEvent(new CustomEvent('QRGetFile', { - bubbles: true - })); + if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) { + FCX.oekakiName = name; + return Tegaki.resume(); + } else { + document.addEventListener('QRFile', cb, false); + return document.dispatchEvent(new CustomEvent('QRGetFile', { + bubbles: true + })); + } }); }); + }, + toggle: function() { + return QR.oekaki.load(function() { + return QR.nodes.oekaki.hidden = !QR.nodes.oekaki.hidden; + }); } }; @@ -9620,13 +9654,21 @@ }; _Class.prototype.showFileData = function() { + var ref; if (this.file) { this.updateFilename(); QR.nodes.filename.value = this.filename; + $.addClass(QR.nodes.oekaki, 'has-file'); $.addClass(QR.nodes.fileSubmit, 'has-file'); } else { + $.rmClass(QR.nodes.oekaki, 'has-file'); $.rmClass(QR.nodes.fileSubmit, 'has-file'); } + if (((ref = this.file) != null ? ref.source : void 0) != null) { + QR.nodes.fileSubmit.dataset.source = this.file.source; + } else { + QR.nodes.fileSubmit.removeAttribute('data-source'); + } return QR.nodes.spoiler.checked = this.spoiler; }; @@ -17235,7 +17277,10 @@ initFeatures: function() { var err, feature, k, len1, match, name, pathname, ref, ref1, ref2; if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'sys.4chan.org' || ref === 'www.4chan.org') { - $.globalEval('document.documentElement.classList.add("js-enabled");'); + $.global(function() { + document.documentElement.classList.add('js-enabled'); + return window.FCX = {}; + }); } switch (location.hostname) { case 'www.4chan.org': @@ -19446,7 +19491,6 @@ "#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_i) .oekaki,\n" + "body:not(.board_jp) #sjis-toggle,\n" + "body:not(.board_sci) #tex-preview-button,\n" + "#qr.tex-preview .textarea > :not(#tex-preview),\n" + @@ -19611,6 +19655,9 @@ "#qr-no-file {\n" + " color: #AAA;\n" + "}\n" + +".oekaki.has-file {\n" + +" display: none;\n" + +"}\n" + ".oekaki > label {\n" + " -webkit-flex: 1 1 auto;\n" + " flex: 1 1 auto;\n" + @@ -19643,7 +19690,6 @@ "}\n" + "#qr:not(.has-spoiler) #qr-spoiler-label,\n" + "#file-n-submit:not(.has-file) #qr-spoiler-label,\n" + -"#file-n-submit:not(.has-file) #qr-edit-button,\n" + ".has-file #paste-area,\n" + ".has-file #url-button,\n" + "#file-n-submit:not(.custom-cooldown) #custom-cooldown-button {\n" + diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip index 6b6ceb4c7..e3ccecc1f 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 669f609d6..6fa8146bd 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 e6f45b3e4..c9893599e 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/package.json b/package.json index 5789a739e..1e7dae233 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "meta": { "name": "4chan X", "fork": "ccd0", - "version": "1.11.14.2", - "date": "2015-10-25T20:27:00.941Z", + "version": "1.11.14.3", + "date": "2015-10-26T06:21:56.818Z", "page": "https://www.4chan-x.net/", "downloads": "https://www.4chan-x.net/builds/", "oldVersions": "https://raw.githubusercontent.com/ccd0/4chan-x/",