diff --git a/4chan_x.user.js b/4chan_x.user.js index 895d20262..a4dad4a7e 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -100,7 +100,6 @@ 'Quick Reply': [true, 'WMD.'], 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'], 'Auto Hide QR': [false, 'Automatically hide the quick reply when posting.'], - 'Open Reply in New Tab': [false, 'Open replies posted from the board pages in a new tab.'], 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'], 'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.'] @@ -1825,40 +1824,51 @@ QR = { init: function() { var link; - if (!Conf['Quick Reply']) { + if (g.VIEW === 'catalog' || !Conf['Quick Reply']) { return; } if (Conf['Hide Original Post Form']) { - Main.css += "#postForm, .postingMode {\n display: none;\n}"; + $.addClass(doc, 'hide-original-post-form'); } link = $.el('a', { + className: 'qr-shortcut', textContent: 'Quick Reply', href: 'javascript:;' }); $.on(link, 'click', function() { Header.menu.close(); - return QR.open(); + QR.open(); + if (g.BOARD.ID === 'f') { + if (g.VIEW === 'index') { + QR.threadSelector.value = '9999'; + } + } else if (g.VIEW === 'thread') { + QR.threadSelector.value = g.THREAD; + } else { + QR.threadSelector.value = 'new'; + } + return $('textarea', QR.el).focus(); }); $.event('AddMenuEntry', { type: 'header', el: link }); - Post.prototype.callbacks.push({ + $.on(d, 'dragover', QR.dragOver); + $.on(d, 'drop', QR.dropFile); + $.on(d, 'dragstart dragend', QR.drag); + $.on(d, '4chanXInitFinished', function() { + if (!Conf['Persistent QR']) { + return; + } + QR.open(); + if (Conf['Auto Hide QR']) { + return QR.hide(); + } + }); + return Post.prototype.callbacks.push({ name: 'Quick Reply', cb: this.node }); - return $.ready(this.readyInit); - }, - readyInit: function() { - if (Conf['Persistent QR']) { - QR.open(); - if (Conf['Auto Hide QR']) { - QR.hide(); - } - } - $.on(d, 'dragover', QR.dragOver); - $.on(d, 'drop', QR.dropFile); - return $.on(d, 'dragstart dragend', QR.drag); }, node: function() { return $.on($('a[title="Quote this post"]', this.nodes.info), 'click', QR.quote); @@ -1866,16 +1876,17 @@ open: function() { if (QR.el) { QR.el.hidden = false; - return QR.unhide(); - } else { - try { - return QR.dialog(); - } catch (err) { - return Main.handleErrors({ - message: 'Quick Reply dialog creation crashed.', - error: err - }); - } + QR.unhide(); + return; + } + try { + return QR.dialog(); + } catch (err) { + delete QR.el; + return Main.handleErrors({ + message: 'Quick Reply dialog creation crashed.', + error: err + }); } }, close: function() { @@ -1895,7 +1906,7 @@ if (!Conf['Remember Spoiler'] && (spoiler = $.id('spoiler')).checked) { spoiler.click(); } - return QR.cleanError(); + return QR.cleanNotification(); }, hide: function() { d.activeElement.blur(); @@ -1907,7 +1918,11 @@ return $.id('autohide').checked = false; }, toggleHide: function() { - return this.checked && QR.hide() || QR.unhide(); + if (this.checked) { + return QR.hide(); + } else { + return QR.unhide(); + } }, error: function(err) { var el; @@ -1926,7 +1941,7 @@ } return QR.lastNotification = new Notification('warning', el); }, - cleanError: function() { + cleanNotification: function() { var _ref; if ((_ref = QR.lastNotification) != null) { _ref.close(); @@ -1948,14 +1963,11 @@ } value = data.progress || QR.cooldown.seconds || value; input = QR.status.input; - input.value = QR.cooldown.auto && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit'; + input.value = QR.cooldown.auto ? value ? "Auto " + value : 'Auto' : value || 'Submit'; return input.disabled = disabled || false; }, cooldown: { init: function() { - if (!Conf['Cooldown']) { - return; - } QR.cooldown.types = { thread: (function() { switch (g.BOARD) { @@ -1993,9 +2005,6 @@ }, set: function(data) { var cooldown, hasFile, isReply, isSage, start, type; - if (!Conf['Cooldown']) { - return; - } start = Date.now(); if (data.delay) { cooldown = { @@ -2032,7 +2041,8 @@ QR.status(); return; } - if ((isReply = g.REPLY ? true : QR.threadSelector.value !== 'new')) { + isReply = g.BOARD.ID === 'f' && g.VIEW === 'thread' ? true : QR.threadSelector.value !== 'new'; + if (isReply) { post = QR.replies[0]; isSage = /sage/i.test(post.email); hasFile = !!post.file; @@ -2073,19 +2083,20 @@ } }, quote: function(e) { - var caretPos, id, range, s, sel, ta, text, _ref; + var caretPos, post, range, s, sel, selectionRoot, ta, text; if (e != null) { e.preventDefault(); } QR.open(); ta = $('textarea', QR.el); - if (!(g.REPLY || ta.value)) { + if (QR.threadSelector && !ta.value && g.BOARD.ID !== 'f') { QR.threadSelector.value = $.x('ancestor::div[parent::div[@class="board"]]', this).id.slice(1); } - id = this.previousSibling.hash.slice(2); - text = ">>" + id + "\n"; + post = Get.postFromRoot($.x('ancestor-or-self::div[contains(@class,"postContainer")][1]', this)); + text = ">>" + post + "\n"; sel = d.getSelection(); - if ((s = sel.toString().trim()) && id === ((_ref = $.x('ancestor-or-self::blockquote', sel.anchorNode)) != null ? _ref.id.match(/\d+$/)[0] : void 0)) { + selectionRoot = $.x('ancestor-or-self::div[contains(@class,"postContainer")][1]', sel.anchorNode); + if ((s = sel.toString().trim()) && post.nodes.root === selectionRoot) { s = s.replace(/\n/g, '\n>'); text += ">" + s + "\n"; } @@ -2125,7 +2136,7 @@ }, fileInput: function() { var file, _i, _len, _ref; - QR.cleanError(); + QR.cleanNotification(); if (this.files.length === 1) { file = this.files[0]; if (file.size > this.max) { @@ -2175,7 +2186,7 @@ this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false; this.com = null; this.el = $.el('a', { - className: 'thumbnail', + className: 'qrpreview', draggable: true, href: 'javascript:;', innerHTML: '×' @@ -2333,7 +2344,7 @@ }; _Class.prototype.rm = function() { - var index, _base; + var index, _ref; QR.resetFileInput(); $.rm(this.el); index = QR.replies.indexOf(this); @@ -2343,7 +2354,7 @@ (QR.replies[index - 1] || QR.replies[index + 1]).select(); } QR.replies.splice(index, 1); - return typeof (_base = window.URL || window.webkitURL).revokeObjectURL === "function" ? _base.revokeObjectURL(this.url) : void 0; + return (_ref = window.URL || window.webkitURL) != null ? _ref.revokeObjectURL(this.url) : void 0; }; return _Class; @@ -2418,7 +2429,7 @@ }, load: function() { var challenge; - this.timeout = Date.now() + 4 * $.MINUTE; + this.timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE; challenge = this.challenge.firstChild.value; this.img.alt = challenge; this.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge; @@ -2438,7 +2449,7 @@ return this.input.alt = count; }, reload: function(focus) { - $.globalEval('javascript:Recaptcha.reload("t")'); + $.unsafeWindow.Recaptcha.reload('r'); if (focus) { return QR.captcha.input.focus(); } @@ -2457,25 +2468,8 @@ } }, dialog: function() { - var fileInput, id, mimeTypes, name, spoiler, ta, thread, threads, _i, _j, _len, _len1, _ref, _ref1; - QR.el = UI.dialog('qr', 'top:0;right:0;', '\ -
\ - Quick Reply \ - ×\ -
\ -
\ -
\ -
+
\ -
\ -
\ - \ -
'); - if (Conf['Remember QR size'] && $.engine === 'gecko') { - $.on(ta = $('textarea', QR.el), 'mouseup', function() { - return $.set('QR.size', this.style.cssText); - }); - ta.style.cssText = $.get('QR.size', ''); - } + var fileInput, key, mimeTypes, name, span, spoiler, ta, thread, threads, _i, _len, _ref, _ref1; + QR.el = UI.dialog('qr', 'top:0;right:0;', "
Quick Reply ×
\n
\n
\n
\n
\n
\n \n
"); mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace(/\w+/g, function(type) { switch (type) { case 'jpg': @@ -2500,23 +2494,32 @@ spoiler.hidden = !QR.spoiler; QR.charaCounter = $('#charCount', QR.el); ta = $('textarea', QR.el); - if (!g.REPLY) { - threads = ''; - _ref = $$('.thread'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - thread = _ref[_i]; - id = thread.id.slice(1); - threads += ""; + span = $('.move > span', QR.el); + if (g.BOARD.ID === 'f') { + if (g.VIEW === 'index') { + QR.threadSelector = $('select[name=filetag]').cloneNode(true); } - QR.threadSelector = g.BOARD === 'f' ? $('select[name=filetag]').cloneNode(true) : $.el('select', { - innerHTML: threads, + } else { + QR.threadSelector = $.el('select', { title: 'Create a new thread / Reply to a thread' }); - $.prepend($('.move > span', QR.el), QR.threadSelector); - $.on(QR.threadSelector, 'mousedown', function(e) { - return e.stopPropagation(); - }); + threads = ''; + _ref = g.BOARD.threads; + for (key in _ref) { + thread = _ref[key]; + threads += ""; + } + QR.threadSelector.innerHTML = threads; + if (g.VIEW === 'thread') { + QR.threadSelector.value = g.THREAD; + } } + if (QR.threadSelector) { + $.prepend(span, QR.threadSelector); + } + $.on(span, 'mousedown', function(e) { + return e.stopPropagation(); + }); $.on($('#autohide', QR.el), 'change', QR.toggleHide); $.on($('.close', QR.el), 'click', QR.close); $.on($('#dump', QR.el), 'click', function() { @@ -2541,8 +2544,8 @@ }); new QR.reply().select(); _ref1 = ['name', 'email', 'sub', 'com']; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - name = _ref1[_j]; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + name = _ref1[_i]; $.on($("[name=" + name + "]", QR.el), 'input', function() { var _ref2; QR.selected[this.name] = this.value; @@ -2570,19 +2573,19 @@ } QR.abort(); reply = QR.replies[0]; - if (g.BOARD === 'f' && !g.REPLY) { + if (g.BOARD.ID === 'f' && g.VIEW === 'index') { filetag = QR.threadSelector.value; threadID = 'new'; } else { - threadID = g.THREAD_ID || QR.threadSelector.value; + threadID = QR.threadSelector.value; } if (threadID === 'new') { threadID = null; - if (((_ref = g.BOARD) === 'vg' || _ref === 'q') && !reply.sub) { + if (((_ref = g.BOARD.ID) === 'vg' || _ref === 'q') && !reply.sub) { err = 'New threads require a subject.'; } else if (!(reply.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { err = 'No file selected.'; - } else if (g.BOARD === 'f' && filetag === '9999') { + } else if (g.BOARD.ID === 'f' && filetag === '9999') { err = 'Invalid tag specified.'; } } else if (!(reply.com || reply.file)) { @@ -2619,7 +2622,7 @@ QR.error(err); return; } - QR.cleanError(); + QR.cleanNotification(); QR.cooldown.auto = QR.replies.length > 1; if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { QR.hide(); @@ -2727,13 +2730,11 @@ post: reply, isReply: threadID !== '0' }); + QR.cooldown.auto = QR.replies.length > 1; if (threadID === '0') { - location.pathname = "/" + g.BOARD + "/res/" + postID; - } else { - QR.cooldown.auto = QR.replies.length > 1; - if (Conf['Open Reply in New Tab'] && !g.REPLY && !QR.cooldown.auto) { - $.open("//boards.4chan.org/" + g.BOARD + "/res/" + threadID + "#p" + postID); - } + $.open("/" + g.BOARD + "/res/" + postID); + } else if (g.VIEW === 'reply' && !QR.cooldown.auto) { + $.open("//boards.4chan.org/" + g.BOARD + "/res/" + threadID + "#p" + postID); } if (Conf['Persistent QR'] || QR.cooldown.auto) { reply.rm(); @@ -3346,7 +3347,7 @@ container = $.el('div', { id: "pc" + postID, className: "postContainer " + (isOP ? 'op' : 'reply') + "Container", - innerHTML: (isOP ? '' : "
>>
") + ("
") + ("
") + ("") + ("" + (name || '') + "") + tripcode + capcodeStart + capcode + userID + flag + sticky + closed + ("
" + subject) + ("
" + date) + '
' + ("No.") + ("" + postID + "") + '
' + '
' + (isOP ? fileHTML : '') + ("
") + (" ") + ("" + subject + " ") + ("") + emailStart + ("" + (name || '') + "") + tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + ' ' + ("" + date + " ") + "" + ("No.") + ("" + postID + "") + '' + '
' + (isOP ? '' : fileHTML) + ("
" + (comment || '') + "
") + '
' + innerHTML: (isOP ? '' : "
>>
") + ("
") + ("
") + ("") + ("" + (name || '') + "") + tripcode + capcodeStart + capcode + userID + flag + sticky + closed + ("
" + subject) + ("
" + date) + '
' + ("No.") + ("" + postID + "") + '
' + '
' + (isOP ? fileHTML : '') + ("
") + (" ") + ("" + subject + " ") + ("") + emailStart + ("" + (name || '') + "") + tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + ' ' + ("" + date + " ") + "" + ("No.") + ("" + postID + "") + '' + '
' + (isOP ? '' : fileHTML) + ("
" + (comment || '') + "
") + '
' }); _ref = $$('.quotelink', container); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -4880,14 +4881,17 @@ } Post.prototype.kill = function(img) { - var quotelink, _i, _len, _ref; + var now, quotelink, _i, _len, _ref; + now = Date.now(); if (this.file && !this.file.isDead) { this.file.isDead = true; + this.file.timeOfDeath = now; } if (img) { return; } this.isDead = true; + this.timeOfDeath = now; $.addClass(this.nodes.root, 'dead'); _ref = Get.allQuotelinksLinkingTo(this); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -5112,15 +5116,15 @@ if ((_ref = $('link[href*=mobile]', d.head)) != null) { _ref.disabled = true; } + $.addClass(doc, $.engine); + $.addClass(doc, 'fourchan-x'); $.addStyle(Main.css); - style = null; + style = 'yotsuba-b'; mainStyleSheet = $('link[title=switch]', d.head); styleSheets = $$('link[rel="alternate stylesheet"]', d.head); setStyle = function() { var styleSheet, _i, _len; - if (style) { - $.rmClass(doc, style); - } + $.rmClass(doc, style); for (_i = 0, _len = styleSheets.length; _i < _len; _i++) { styleSheet = styleSheets[_i]; if (styleSheet.href === mainStyleSheet.href) { @@ -5130,9 +5134,10 @@ } return $.addClass(doc, style); }; - $.addClass(doc, $.engine); - $.addClass(doc, 'fourchan-x'); setStyle(); + if (!mainStyleSheet) { + return; + } if (MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.OMutationObserver) { observer = new MutationObserver(setStyle); return observer.observe(mainStyleSheet, { @@ -5144,7 +5149,7 @@ } }, initReady: function() { - var boardChild, errors, posts, thread, threadChild, threads, _i, _j, _len, _len1, _ref, _ref1; + var board, boardChild, errors, posts, thread, threadChild, threads, _i, _j, _len, _len1, _ref, _ref1; if (!$.hasClass(doc, 'fourchan-x')) { Main.initStyle(); } @@ -5158,40 +5163,43 @@ } return; } - threads = []; - posts = []; - _ref = $('.board').children; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - boardChild = _ref[_i]; - if (!$.hasClass(boardChild, 'thread')) { - continue; - } - thread = new Thread(boardChild.id.slice(1), g.BOARD); - threads.push(thread); - _ref1 = boardChild.children; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - threadChild = _ref1[_j]; - if (!$.hasClass(threadChild, 'postContainer')) { + if (board = $('.board')) { + threads = []; + posts = []; + _ref = board.children; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + boardChild = _ref[_i]; + if (!$.hasClass(boardChild, 'thread')) { continue; } - try { - posts.push(new Post(threadChild, thread, g.BOARD)); - } catch (err) { - if (!errors) { - errors = []; + thread = new Thread(boardChild.id.slice(1), g.BOARD); + threads.push(thread); + _ref1 = boardChild.children; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + threadChild = _ref1[_j]; + if (!$.hasClass(threadChild, 'postContainer')) { + continue; + } + try { + posts.push(new Post(threadChild, thread, g.BOARD)); + } catch (err) { + if (!errors) { + errors = []; + } + errors.push({ + message: "Parsing of Post No." + (threadChild.id.match(/\d+/)) + " failed. Post will be skipped.", + error: err + }); } - errors.push({ - message: "Parsing of Post No." + (threadChild.id.match(/\d+/)) + " failed. Post will be skipped.", - error: err - }); } } + if (errors) { + Main.handleErrors(errors); + } + Main.callbackNodes(Thread, threads); + Main.callbackNodes(Post, posts); } - if (errors) { - Main.handleErrors(errors); - } - Main.callbackNodes(Thread, threads); - return Main.callbackNodes(Post, posts); + return $.event('4chanXInitFinished'); }, callbackNodes: function(klass, nodes) { var callback, errors, i, len, node, _i, _j, _len, _ref; @@ -5262,7 +5270,7 @@ }); return [message, error]; }, - css: "/* general */\n.dialog {\nbox-shadow: 0 1px 2px rgba(0, 0, 0, .15);\nborder: 1px solid;\ndisplay: block;\npadding: 0;\n}\n.move {\ncursor: move;\n}\nlabel {\ncursor: pointer;\n}\na[href=\"javascript:;\"] {\ntext-decoration: none;\n}\n.warning {\ncolor: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\ndisplay: block !important;\n}\n.post {\noverflow: visible !important;\n}\n[hidden] {\ndisplay: none !important;\n}\n\n/* fixed, z-index */\n#qp, #ihover,\n#updater, #stats,\n#header,\n#qr, #watcher {\nposition: fixed;\n}\n#notifications {\nz-index: 80;\n}\n#qp, #ihover {\nz-index: 70;\n}\n#menu {\nz-index: 60;\n}\n#updater, #stats {\nz-index: 50;\n}\n#header:hover {\nz-index: 40;\n}\n#qr {\nz-index: 30;\n}\n#header {\nz-index: 20;\n}\n#watcher {\nz-index: 10;\n}\n\n/* Header */\n.fourchan-x body {\nmargin-top: 2em;\n}\n.fourchan-x #boardNavDesktop,\n.fourchan-x #navtopright,\n.fourchan-x #boardNavDesktopFoot {\ndisplay: none !important;\n}\n#header {\ntop: 0;\nright: 0;\nleft: 0;\n}\n#header-bar {\nborder-width: 0 0 1px;\npadding: 4px;\nposition: relative;\ntransition: all .1s ease-in-out;\n-o-transition: all .1s ease-in-out;\n-moz-transition: all .1s ease-in-out;\n-webkit-transition: all .1s ease-in-out;\n}\n#header-bar.autohide:not(:hover) {\nmargin-bottom: -1em;\ntransform: translateY(-100%);\n-o-transform: translateY(-100%);\n-moz-transform: translateY(-100%);\n-webkit-transform: translateY(-100%);\ntransition: all .75s .25s ease-in-out;\n-o-transition: all .75s .25s ease-in-out;\n-moz-transition: all .75s .25s ease-in-out;\n-webkit-transition: all .75s .25s ease-in-out;\n}\n#toggle-header-bar {\ncursor: n-resize;\nleft: 0;\nright: 0;\nbottom: -8px;\nheight: 10px;\nposition: absolute;\n}\n#header-bar.autohide #toggle-header-bar {\ncursor: s-resize;\n}\n#header-bar a {\ntext-decoration: none;\npadding: 1px;\n}\n#header-bar > .menu-button {\nfloat: right;\npadding: 0;\n}\n\n/* notifications */\n#notifications {\ntext-align: center;\n}\n.notification {\ncolor: #FFF;\nfont-weight: 700;\ntext-shadow: 0 1px 2px rgba(0, 0, 0, .5);\nbox-shadow: 0 1px 2px rgba(0, 0, 0, .15);\nborder-radius: 2px;\nmargin: 1px auto;\nwidth: 500px;\nmax-width: 100%;\nposition: relative;\ntransition: all .25s ease-in-out;\n-o-transition: all .25s ease-in-out;\n-moz-transition: all .25s ease-in-out;\n-webkit-transition: all .25s ease-in-out;\n}\n.notification.error {\nbackground-color: hsla(0, 100%, 40%, .9);\n}\n.notification.warning {\nbackground-color: hsla(36, 100%, 40%, .9);\n}\n.notification.info {\nbackground-color: hsla(200, 100%, 40%, .9);\n}\n.notification.success {\nbackground-color: hsla(104, 100%, 40%, .9);\n}\n.notification > .close {\ncolor: white;\npadding: 4px 6px;\ntop: 0;\nright: 0;\nposition: absolute;\n}\n.message {\nbox-sizing: border-box;\npadding: 4px 20px;\nmax-height: 200px;\nwidth: 100%;\noverflow: auto;\n}\n\n/* thread updater */\n#updater {\ntext-align: right;\n}\n#updater:not(:hover) {\nbackground: none;\nborder: none;\n}\n#updater input[type=number] {\nwidth: 4em;\n}\n#updater:not(:hover) > div:not(.move) {\ndisplay: none;\n}\n.new {\ncolor: limegreen;\n}\n\n/* quote */\n.quotelink.deadlink {\ntext-decoration: underline !important;\n}\n.deadlink:not(.quotelink) {\ntext-decoration: none !important;\n}\n.inlined {\nopacity: .5;\n}\n#qp input, .forwarded {\ndisplay: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\ntext-decoration: none;\nborder-bottom: 1px dashed;\n}\n.filtered {\ntext-decoration: underline line-through;\n}\n.inline {\nborder: 1px solid;\ndisplay: table;\nmargin: 2px 0;\n}\n.inline .post {\nborder: 0 !important;\nbackground-color: transparent !important;\ndisplay: table !important;\nmargin: 0 !important;\npadding: 1px 2px !important;\n}\n#qp {\npadding: 2px 2px 5px;\n}\n#qp .post {\nborder: none;\nmargin: 0;\npadding: 0;\n}\n#qp img {\nmax-height: 300px;\nmax-width: 500px;\n}\n.qphl {\nbox-shadow: 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* file */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\ndisplay: none;\n}\n#ihover {\nbox-sizing: border-box;\n-moz-box-sizing: border-box;\nmax-height: 100%;\nmax-width: 75%;\npadding-bottom: 16px;\n}\n\n/* Filter */\n.opContainer.filter-highlight {\nbox-shadow: inset 5px 0 rgba(255, 0, 0, .5);\n}\n.opContainer.filter-highlight.qphl {\nbox-shadow: inset 5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n.filter-highlight > .reply {\nbox-shadow: -5px 0 rgba(255, 0, 0, .5);\n}\n.filter-highlight > .reply.qphl {\nbox-shadow: -5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* Thread & Reply Hiding */\n.hide-thread-button,\n.hide-reply-button {\nfloat: left;\nmargin-right: 2px;\n}\n.stub ~ .sideArrows,\n.stub ~ .hide-reply-button,\n.stub ~ .post {\ndisplay: none !important;\n}\n\n/* QR */\n#qr > .move {\nmin-width: 300px;\noverflow: hidden;\nbox-sizing: border-box;\n-moz-box-sizing: border-box;\npadding: 0 2px;\n}\n#qr > .move > span {\nfloat: right;\n}\n#autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning {\ncursor: pointer;\n}\n#qr select,\n#qr > form {\nmargin: 0;\n}\n#dump {\nbackground: -webkit-linear-gradient(#EEE, #CCC);\nbackground: -moz-linear-gradient(#EEE, #CCC);\nbackground: -o-linear-gradient(#EEE, #CCC);\nbackground: linear-gradient(#EEE, #CCC);\nwidth: 10%;\n}\n.gecko #dump {\npadding: 1px 0 2px;\n}\n#dump:hover, #dump:focus {\nbackground: -webkit-linear-gradient(#FFF, #DDD);\nbackground: -moz-linear-gradient(#FFF, #DDD);\nbackground: -o-linear-gradient(#FFF, #DDD);\nbackground: linear-gradient(#FFF, #DDD);\n}\n#dump:active, .dump #dump:not(:hover):not(:focus) {\nbackground: -webkit-linear-gradient(#CCC, #DDD);\nbackground: -moz-linear-gradient(#CCC, #DDD);\nbackground: -o-linear-gradient(#CCC, #DDD);\nbackground: linear-gradient(#CCC, #DDD);\n}\n#qr:not(.dump) #replies, .dump > form > label {\ndisplay: none;\n}\n#replies {\ndisplay: block;\nheight: 100px;\nposition: relative;\n-webkit-user-select: none;\n-moz-user-select: none;\n-o-user-select: none;\nuser-select: none;\n}\n#replies > div {\ncounter-reset: thumbnails;\ntop: 0; right: 0; bottom: 0; left: 0;\nmargin: 0; padding: 0;\noverflow: hidden;\nposition: absolute;\nwhite-space: pre;\n}\n#replies > div:hover {\nbottom: -10px;\noverflow-x: auto;\nz-index: 1;\n}\n.thumbnail {\nbackground-color: rgba(0,0,0,.2) !important;\nbackground-position: 50% 20% !important;\nbackground-size: cover !important;\nborder: 1px solid #666;\nbox-sizing: border-box;\n-moz-box-sizing: border-box;\ncursor: move;\ndisplay: inline-block;\nheight: 90px; width: 90px;\nmargin: 5px; padding: 2px;\nopacity: .5;\noutline: none;\noverflow: hidden;\nposition: relative;\ntext-shadow: 0 1px 1px #000;\n-webkit-transition: opacity .25s ease-in-out;\n-moz-transition: opacity .25s ease-in-out;\n-o-transition: opacity .25s ease-in-out;\ntransition: opacity .25s ease-in-out;\nvertical-align: top;\n}\n.thumbnail:hover, .thumbnail:focus {\nopacity: .9;\n}\n.thumbnail#selected {\nopacity: 1;\n}\n.thumbnail::before {\ncounter-increment: thumbnails;\ncontent: counter(thumbnails);\ncolor: #FFF;\nfont-weight: 700;\npadding: 3px;\nposition: absolute;\ntop: 0;\nright: 0;\ntext-shadow: 0 0 3px #000, 0 0 8px #000;\n}\n.thumbnail.drag {\nbox-shadow: 0 0 10px rgba(0,0,0,.5);\n}\n.thumbnail.over {\nborder-color: #FFF;\n}\n.thumbnail > span {\ncolor: #FFF;\n}\n.remove {\nbackground: none;\ncolor: #E00;\nfont-weight: 700;\npadding: 3px;\n}\n.remove:hover::after {\ncontent: \" Remove\";\n}\n.thumbnail > label {\nbackground: rgba(0,0,0,.5);\ncolor: #FFF;\nright: 0; bottom: 0; left: 0;\nposition: absolute;\ntext-align: center;\n}\n.thumbnail > label > input {\nmargin: 0;\n}\n#addReply {\ncolor: #333;\nfont-size: 3.5em;\nline-height: 100px;\n}\n#addReply:hover, #addReply:focus {\ncolor: #000;\n}\n.field {\nborder: 1px solid #CCC;\nbox-sizing: border-box;\n-moz-box-sizing: border-box;\ncolor: #333;\nfont: 13px sans-serif;\nmargin: 0;\npadding: 2px 4px 3px;\n-webkit-transition: color .25s, border .25s;\n-moz-transition: color .25s, border .25s;\n-o-transition: color .25s, border .25s;\ntransition: color .25s, border .25s;\n}\n.field:-moz-placeholder,\n.field:hover:-moz-placeholder {\ncolor: #AAA;\n}\n.field:hover, .field:focus {\nborder-color: #999;\ncolor: #000;\noutline: none;\n}\n#qr > form > div:first-child > .field:not(#dump) {\nwidth: 30%;\n}\n#qr textarea.field {\ndisplay: -webkit-box;\nmin-height: 160px;\nmin-width: 100%;\n}\n#qr.captcha textarea.field {\nmin-height: 120px;\n}\n.textarea {\nposition: relative;\n}\n#charCount {\ncolor: #000;\nbackground: hsla(0, 0%, 100%, .5);\nfont-size: 8pt;\nmargin: 1px;\nposition: absolute;\nbottom: 0;\nright: 0;\npointer-events: none;\n}\n#charCount.warning {\ncolor: red;\n}\n.captchainput > .field {\nmin-width: 100%;\n}\n.captchaimg {\nbackground: #FFF;\noutline: 1px solid #CCC;\noutline-offset: -1px;\ntext-align: center;\n}\n.captchaimg > img {\ndisplay: block;\nheight: 57px;\nwidth: 300px;\n}\n#qr [type=file] {\nmargin: 1px 0;\nwidth: 70%;\n}\n#qr [type=submit] {\nmargin: 1px 0;\npadding: 1px; /* not Gecko */\nwidth: 30%;\n}\n.gecko #qr [type=submit] {\npadding: 0 1px; /* Gecko does not respect box-sizing: border-box */\n}\n\n/* Menu */\n.menu-button {\ndisplay: inline-block;\n}\n.menu-button > span {\nborder-top: 6px solid;\nborder-right: 4px solid transparent;\nborder-left: 4px solid transparent;\ndisplay: inline-block;\nmargin: 2px;\nvertical-align: middle;\n}\n#menu {\nposition: absolute;\noutline: none;\n}\n.entry {\ncursor: pointer;\ndisplay: block;\noutline: none;\npadding: 3px 7px;\nposition: relative;\ntext-decoration: none;\nwhite-space: nowrap;\n}\n.entry.has-submenu {\npadding-right: 20px;\n}\n.has-submenu::after {\ncontent: \"\";\nborder-left: 6px solid;\nborder-top: 4px solid transparent;\nborder-bottom: 4px solid transparent;\ndisplay: inline-block;\nmargin: 4px;\nposition: absolute;\nright: 3px;\n}\n.has-submenu:not(.focused) > .submenu {\ndisplay: none;\n}\n.submenu {\nposition: absolute;\nmargin: -1px 0;\n}\n.entry input {\nmargin: 0;\n}\n\n/* general */\n:root.yotsuba .dialog {\nbackground-color: #F0E0D6;\nborder-color: #D9BFB7;\n}\n\n/* Header */\n:root.yotsuba #header-bar {\nfont-size: 9pt;\ncolor: #B86;\n}\n:root.yotsuba #header-bar a {\ncolor: #800000;\n}\n\n/* quote */\n:root.yotsuba .inline {\nborder-color: #D9BFB7;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* Menu */\n:root.yotsuba .entry:not(:last-child) {\nborder-bottom: 1px solid #D9BFB7;\n}\n:root.yotsuba .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.yotsuba-b .dialog {\nbackground-color: #D6DAF0;\nborder-color: #B7C5D9;\n}\n\n/* Header */\n:root.yotsuba-b #header-bar {\nfont-size: 9pt;\ncolor: #89A;\n}\n:root.yotsuba-b #header-bar a {\ncolor: #34345C;\n}\n\n/* quote */\n:root.yotsuba-b .inline {\nborder-color: #B7C5D9;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* Menu */\n:root.yotsuba-b .entry:not(:last-child) {\nborder-bottom: 1px solid #B7C5D9;\n}\n:root.yotsuba-b .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.futaba .dialog {\nbackground-color: #F0E0D6;\nborder-color: #D9BFB7;\n}\n\n/* Header */\n:root.futaba #header-bar {\nfont-size: 11pt;\ncolor: #B86;\n}\n:root.futaba #header-bar a {\ncolor: #800000;\n}\n\n/* quote */\n:root.futaba .inline {\nborder-color: #D9BFB7;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* Menu */\n:root.futaba .entry:not(:last-child) {\nborder-bottom: 1px solid #D9BFB7;\n}\n:root.futaba .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.burichan .dialog {\nbackground-color: #D6DAF0;\nborder-color: #B7C5D9;\n}\n\n/* Header */\n:root.burichan #header-bar {\nfont-size: 11pt;\ncolor: #89A;\n}\n:root.burichan #header-bar a {\ncolor: #34345C;\n}\n\n/* quote */\n:root.burichan .inline {\nborder-color: #B7C5D9;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* Menu */\n:root.burichan .entry:not(:last-child) {\nborder-bottom: 1px solid #B7C5D9;\n}\n:root.burichan .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.tomorrow .dialog {\nbackground-color: #282A2E;\nborder-color: #111;\n}\n\n/* Header */\n:root.tomorrow #header-bar {\nfont-size: 9pt;\ncolor: #C5C8C6;\n}\n:root.tomorrow #header-bar a {\ncolor: #81A2BE;\n}\n\n/* quote */\n:root.tomorrow .inline {\nborder-color: #111;\nbackground-color: rgba(0, 0, 0, .14);\n}\n\n/* Menu */\n:root.tomorrow .entry:not(:last-child) {\nborder-bottom: 1px solid #111;\n}\n:root.tomorrow .focused.entry {\nbackground: rgba(0, 0, 0, .33);\n}\n\n/* general */\n:root.photon .dialog {\nbackground-color: #DDD;\nborder-color: #CCC;\n}\n\n/* Header */\n:root.photon #header-bar {\nfont-size: 9pt;\ncolor: #333;\n}\n:root.photon #header-bar a {\ncolor: #FF6600;\n}\n\n/* quote */\n:root.photon .inline {\nborder-color: #CCC;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* Menu */\n:root.photon .entry:not(:last-child) {\nborder-bottom: 1px solid #CCC;\n}\n:root.photon .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n" + css: "/* general */\n.dialog {\nbox-shadow: 0 1px 2px rgba(0, 0, 0, .15);\nborder: 1px solid;\ndisplay: block;\npadding: 0;\n}\n.field {\nborder: 1px solid #CCC;\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\ncolor: #333;\nfont: 13px sans-serif;\nmargin: 0;\npadding: 2px 4px 3px;\noutline: none;\n-webkit-transition: color .25s, border-color .25s;\ntransition: color .25s, border-color .25s;\n}\n.field:-moz-placeholder,\n.field:hover:-moz-placeholder {\ncolor: #AAA !important;\n}\n.field:hover {\nborder-color: #999;\n}\n.field:hover, .field:focus {\ncolor: #000;\n}\n.move {\ncursor: move;\n}\nlabel {\ncursor: pointer;\n}\na[href=\"javascript:;\"] {\ntext-decoration: none;\n}\n.warning {\ncolor: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\ndisplay: block !important;\n}\n.post {\noverflow: visible !important;\n}\n[hidden] {\ndisplay: none !important;\n}\n\n/* fixed, z-index */\n#qp, #ihover,\n#updater, #stats,\n#header,\n#qr, #watcher {\nposition: fixed;\n}\n#notifications {\nz-index: 80;\n}\n#qp, #ihover {\nz-index: 70;\n}\n#menu {\nz-index: 60;\n}\n#updater, #stats {\nz-index: 50;\n}\n#header:hover {\nz-index: 40;\n}\n#qr {\nz-index: 30;\n}\n#header {\nz-index: 20;\n}\n#watcher {\nz-index: 10;\n}\n\n/* Header */\n.fourchan-x body {\nmargin-top: 2em;\n}\n.fourchan-x #boardNavDesktop,\n.fourchan-x #navtopright,\n.fourchan-x #boardNavDesktopFoot {\ndisplay: none !important;\n}\n#header {\ntop: 0;\nright: 0;\nleft: 0;\n}\n#header-bar {\nborder-width: 0 0 1px;\npadding: 4px;\nposition: relative;\n-webkit-transition: all .1s ease-in-out;\ntransition: all .1s ease-in-out;\n}\n#header-bar.autohide:not(:hover) {\nmargin-bottom: -1em;\n-webkit-transform: translateY(-100%);\ntransform: translateY(-100%);\n-webkit-transition: all .75s .25s ease-in-out;\ntransition: all .75s .25s ease-in-out;\n}\n#toggle-header-bar {\ncursor: n-resize;\nleft: 0;\nright: 0;\nbottom: -8px;\nheight: 10px;\nposition: absolute;\n}\n#header-bar.autohide #toggle-header-bar {\ncursor: s-resize;\n}\n#header-bar a {\ntext-decoration: none;\npadding: 1px;\n}\n#header-bar > .menu-button {\nfloat: right;\npadding: 0;\n}\n\n/* notifications */\n#notifications {\ntext-align: center;\n}\n.notification {\ncolor: #FFF;\nfont-weight: 700;\ntext-shadow: 0 1px 2px rgba(0, 0, 0, .5);\nbox-shadow: 0 1px 2px rgba(0, 0, 0, .15);\nborder-radius: 2px;\nmargin: 1px auto;\nwidth: 500px;\nmax-width: 100%;\nposition: relative;\n-webkit-transition: all .25s ease-in-out;\ntransition: all .25s ease-in-out;\n}\n.notification.error {\nbackground-color: hsla(0, 100%, 40%, .9);\n}\n.notification.warning {\nbackground-color: hsla(36, 100%, 40%, .9);\n}\n.notification.info {\nbackground-color: hsla(200, 100%, 40%, .9);\n}\n.notification.success {\nbackground-color: hsla(104, 100%, 40%, .9);\n}\n.notification > .close {\ncolor: white;\npadding: 4px 6px;\ntop: 0;\nright: 0;\nposition: absolute;\n}\n.message {\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\npadding: 4px 20px;\nmax-height: 200px;\nwidth: 100%;\noverflow: auto;\n}\n\n/* thread updater */\n#updater {\ntext-align: right;\n}\n#updater:not(:hover) {\nbackground: none;\nborder: none;\n}\n#updater input[type=number] {\nwidth: 4em;\n}\n#updater:not(:hover) > div:not(.move) {\ndisplay: none;\n}\n.new {\ncolor: limegreen;\n}\n\n/* quote */\n.quotelink.deadlink {\ntext-decoration: underline !important;\n}\n.deadlink:not(.quotelink) {\ntext-decoration: none !important;\n}\n.inlined {\nopacity: .5;\n}\n#qp input, .forwarded {\ndisplay: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\ntext-decoration: none;\nborder-bottom: 1px dashed;\n}\n.filtered {\ntext-decoration: underline line-through;\n}\n.inline {\nborder: 1px solid;\ndisplay: table;\nmargin: 2px 0;\n}\n.inline .post {\nborder: 0 !important;\nbackground-color: transparent !important;\ndisplay: table !important;\nmargin: 0 !important;\npadding: 1px 2px !important;\n}\n#qp {\npadding: 2px 2px 5px;\n}\n#qp .post {\nborder: none;\nmargin: 0;\npadding: 0;\n}\n#qp img {\nmax-height: 300px;\nmax-width: 500px;\n}\n.qphl {\nbox-shadow: 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* file */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\ndisplay: none;\n}\n#ihover {\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\nmax-height: 100%;\nmax-width: 75%;\npadding-bottom: 16px;\n}\n\n/* Filter */\n.opContainer.filter-highlight {\nbox-shadow: inset 5px 0 rgba(255, 0, 0, .5);\n}\n.opContainer.filter-highlight.qphl {\nbox-shadow: inset 5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n.filter-highlight > .reply {\nbox-shadow: -5px 0 rgba(255, 0, 0, .5);\n}\n.filter-highlight > .reply.qphl {\nbox-shadow: -5px 0 rgba(255, 0, 0, .5),\n 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* Thread & Reply Hiding */\n.hide-thread-button,\n.hide-reply-button {\nfloat: left;\nmargin-right: 2px;\n}\n.stub ~ .sideArrows,\n.stub ~ .hide-reply-button,\n.stub ~ .post {\ndisplay: none !important;\n}\n\n/* QR */\n.hide-original-post-form #postForm,\n.hide-original-post-form .postingMode {\ndisplay: none;\n}\n#qr > .move {\nmin-width: 300px;\noverflow: hidden;\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\npadding: 0 2px;\n}\n#qr > .move > span {\nfloat: right;\n}\n#autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning {\ncursor: pointer;\n}\n#qr select {\nmargin: 0;\n}\n#dump {\nbackground: -webkit-linear-gradient(#EEE, #CCC);\nbackground: linear-gradient(#EEE, #CCC);\nborder: 1px solid #CCC;\nmargin: 0;\npadding: 2px 4px 3px;\noutline: none;\nwidth: 10%;\n}\n.gecko #dump {\npadding: 1px 0 2px;\n}\n#dump:hover, #dump:focus {\nbackground: -webkit-linear-gradient(#FFF, #DDD);\nbackground: linear-gradient(#FFF, #DDD);\n}\n#dump:active, .dump #dump:not(:hover):not(:focus) {\nbackground: -webkit-linear-gradient(#CCC, #DDD);\nbackground: linear-gradient(#CCC, #DDD);\n}\n#qr:not(.dump) #replies, .dump > form > label {\ndisplay: none;\n}\n#replies {\ndisplay: block;\nheight: 100px;\nposition: relative;\n-webkit-user-select: none;\n-moz-user-select: none;\n-o-user-select: none;\nuser-select: none;\n}\n#replies > div {\ncounter-reset: qrpreviews;\ntop: 0; right: 0; bottom: 0; left: 0;\nmargin: 0; padding: 0;\noverflow: hidden;\nposition: absolute;\nwhite-space: pre;\n}\n#replies > div:hover {\nbottom: -10px;\noverflow-x: auto;\nz-index: 1;\n}\n.qrpreview {\nbackground-position: 50% 20%;\nbackground-size: cover;\nborder: 1px solid #808080;\ncolor: #FFF !important;\nfont-size: 12px;\n-moz-box-sizing: border-box;\nbox-sizing: border-box;\ncursor: move;\ndisplay: inline-block;\nheight: 90px; width: 90px;\nmargin: 5px; padding: 2px;\nopacity: .6;\noutline: none;\noverflow: hidden;\nposition: relative;\ntext-shadow: 0 1px 1px #000;\n-webkit-transition: opacity .25s ease-in-out;\ntransition: opacity .25s ease-in-out;\nvertical-align: top;\n}\n.qrpreview:hover, .qrpreview:focus {\nopacity: .9;\ncolor: #FFF !important;\n}\n.qrpreview#selected {\nopacity: 1;\n}\n.qrpreview::before {\ncounter-increment: qrpreviews;\ncontent: counter(qrpreviews);\nfont-weight: 700;\ntext-shadow: 0 0 3px #000, 0 0 5px #000;\nposition: absolute;\ntop: 3px; right: 3px;\n}\n.qrpreview.drag {\nborder-color: red;\nborder-style: dashed;\n}\n.qrpreview.over {\nborder-color: #FFF;\nborder-style: dashed;\n}\n.remove {\ncolor: #E00 !important;\nfont-weight: 700;\npadding: 3px;\n}\n.remove:hover::after {\ncontent: ' Remove';\n}\n.qrpreview > label {\nbackground: rgba(0, 0, 0, .5);\nright: 0; bottom: 0; left: 0;\nposition: absolute;\ntext-align: center;\n}\n.qrpreview > label > input {\nmargin: 1px 0;\nvertical-align: bottom;\n}\n#addReply {\nfont-size: 3.5em;\nline-height: 100px;\n}\n#qr > form > div:first-child > .field:not(#dump) {\nwidth: 30%;\n}\n#qr textarea.field {\ndisplay: -webkit-box;\nmin-height: 160px;\nmin-width: 100%;\n}\n#qr.captcha textarea.field {\nmin-height: 120px;\n}\n.textarea {\nposition: relative;\n}\n#charCount {\ncolor: #000;\nbackground: hsla(0, 0%, 100%, .5);\nfont-size: 8pt;\nmargin: 1px;\nposition: absolute;\nbottom: 0;\nright: 0;\npointer-events: none;\n}\n#charCount.warning {\ncolor: red;\n}\n.captchainput > .field {\nmin-width: 100%;\n}\n.captchaimg {\nbackground: #FFF;\noutline: 1px solid #CCC;\noutline-offset: -1px;\ntext-align: center;\n}\n.captchaimg > img {\ndisplay: block;\nheight: 57px;\nwidth: 300px;\n}\n#qr [type=file] {\nmargin: 1px 0;\nwidth: 70%;\n}\n#qr [type=submit] {\nmargin: 1px 0;\npadding: 1px; /* not Gecko */\nwidth: 30%;\n}\n.gecko #qr [type=submit] {\npadding: 0 1px; /* Gecko does not respect box-sizing: border-box */\n}\n\n/* Menu */\n.menu-button {\ndisplay: inline-block;\n}\n.menu-button > span {\nborder-top: 6px solid;\nborder-right: 4px solid transparent;\nborder-left: 4px solid transparent;\ndisplay: inline-block;\nmargin: 2px;\nvertical-align: middle;\n}\n#menu {\nposition: absolute;\noutline: none;\n}\n.entry {\ncursor: pointer;\ndisplay: block;\noutline: none;\npadding: 3px 7px;\nposition: relative;\ntext-decoration: none;\nwhite-space: nowrap;\n}\n.entry.has-submenu {\npadding-right: 20px;\n}\n.has-submenu::after {\ncontent: \"\";\nborder-left: 6px solid;\nborder-top: 4px solid transparent;\nborder-bottom: 4px solid transparent;\ndisplay: inline-block;\nmargin: 4px;\nposition: absolute;\nright: 3px;\n}\n.has-submenu:not(.focused) > .submenu {\ndisplay: none;\n}\n.submenu {\nposition: absolute;\nmargin: -1px 0;\n}\n.entry input {\nmargin: 0;\n}\n\n/* general */\n:root.yotsuba .dialog {\nbackground-color: #F0E0D6;\nborder-color: #D9BFB7;\n}\n:root.yotsuba .field:focus {\nborder-color: #EA8;\n}\n\n/* Header */\n:root.yotsuba #header-bar {\nfont-size: 9pt;\ncolor: #B86;\n}\n:root.yotsuba #header-bar a {\ncolor: #800000;\n}\n\n/* quote */\n:root.yotsuba .inline {\nborder-color: #D9BFB7;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.yotsuba .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.yotsuba .entry:not(:last-child) {\nborder-bottom: 1px solid #D9BFB7;\n}\n:root.yotsuba .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.yotsuba-b .dialog {\nbackground-color: #D6DAF0;\nborder-color: #B7C5D9;\n}\n:root.yotsuba-b .field:focus {\nborder-color: #98E;\n}\n\n/* Header */\n:root.yotsuba-b #header-bar {\nfont-size: 9pt;\ncolor: #89A;\n}\n:root.yotsuba-b #header-bar a {\ncolor: #34345C;\n}\n\n/* quote */\n:root.yotsuba-b .inline {\nborder-color: #B7C5D9;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.yotsuba-b .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.yotsuba-b .entry:not(:last-child) {\nborder-bottom: 1px solid #B7C5D9;\n}\n:root.yotsuba-b .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.futaba .dialog {\nbackground-color: #F0E0D6;\nborder-color: #D9BFB7;\n}\n:root.futaba .field:focus {\nborder-color: #EA8;\n}\n\n/* Header */\n:root.futaba #header-bar {\nfont-size: 11pt;\ncolor: #B86;\n}\n:root.futaba #header-bar a {\ncolor: #800000;\n}\n\n/* quote */\n:root.futaba .inline {\nborder-color: #D9BFB7;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.futaba .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.futaba .entry:not(:last-child) {\nborder-bottom: 1px solid #D9BFB7;\n}\n:root.futaba .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.burichan .dialog {\nbackground-color: #D6DAF0;\nborder-color: #B7C5D9;\n}\n:root.burichan .field:focus {\nborder-color: #98E;\n}\n\n/* Header */\n:root.burichan #header-bar {\nfont-size: 11pt;\ncolor: #89A;\n}\n:root.burichan #header-bar a {\ncolor: #34345C;\n}\n\n/* quote */\n:root.burichan .inline {\nborder-color: #B7C5D9;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.burichan .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.burichan .entry:not(:last-child) {\nborder-bottom: 1px solid #B7C5D9;\n}\n:root.burichan .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n\n/* general */\n:root.tomorrow .dialog {\nbackground-color: #282A2E;\nborder-color: #111;\n}\n:root.tomorrow .field:focus {\nborder-color: #000;\n}\n\n/* Header */\n:root.tomorrow #header-bar {\nfont-size: 9pt;\ncolor: #C5C8C6;\n}\n:root.tomorrow #header-bar a {\ncolor: #81A2BE;\n}\n\n/* quote */\n:root.tomorrow .inline {\nborder-color: #111;\nbackground-color: rgba(0, 0, 0, .14);\n}\n\n/* QR */\n:root.tomorrow .qrpreview {\nbackground-color: rgba(255, 255, 255, .15);\n}\n\n/* Menu */\n:root.tomorrow .entry:not(:last-child) {\nborder-bottom: 1px solid #111;\n}\n:root.tomorrow .focused.entry {\nbackground: rgba(0, 0, 0, .33);\n}\n\n/* general */\n:root.photon .dialog {\nbackground-color: #DDD;\nborder-color: #CCC;\n}\n:root.photon .field:focus {\nborder-color: #EA8;\n}\n\n/* Header */\n:root.photon #header-bar {\nfont-size: 9pt;\ncolor: #333;\n}\n:root.photon #header-bar a {\ncolor: #FF6600;\n}\n\n/* quote */\n:root.photon .inline {\nborder-color: #CCC;\nbackground-color: rgba(255, 255, 255, .14);\n}\n\n/* QR */\n:root.photon .qrpreview {\nbackground-color: rgba(0, 0, 0, .15);\n}\n\n/* Menu */\n:root.photon .entry:not(:last-child) {\nborder-bottom: 1px solid #CCC;\n}\n:root.photon .focused.entry {\nbackground: rgba(255, 255, 255, .33);\n}\n" }; Main.init(); diff --git a/changelog b/changelog index ccb25c433..ee00e0d90 100644 --- a/changelog +++ b/changelog @@ -5,8 +5,11 @@ alpha Access the list of boards directly from the Header. From the Header's menu, access to: Settings - Quick Reply + Quick Reply shortcut Can be auto-hidden. + QR changes: + Creating threads outside of the index is now possible. + Selection-to-quote also applies to selected text inside the post, not just inside the comment. Added touch and multi-touch support for dragging windows. The Thread Updater will pause when offline, and resume when online. Added Thread & Post Hiding in the Menu, with individual settings. diff --git a/css/burichan.css b/css/burichan.css index 33ad10d4a..48fafa818 100644 --- a/css/burichan.css +++ b/css/burichan.css @@ -3,6 +3,9 @@ background-color: #D6DAF0; border-color: #B7C5D9; } +:root.burichan .field:focus { + border-color: #98E; +} /* Header */ :root.burichan #header-bar { @@ -19,6 +22,11 @@ background-color: rgba(255, 255, 255, .14); } +/* QR */ +:root.burichan .qrpreview { + background-color: rgba(0, 0, 0, .15); +} + /* Menu */ :root.burichan .entry:not(:last-child) { border-bottom: 1px solid #B7C5D9; diff --git a/css/futaba.css b/css/futaba.css index 9e98aee89..6b2486999 100644 --- a/css/futaba.css +++ b/css/futaba.css @@ -3,6 +3,9 @@ background-color: #F0E0D6; border-color: #D9BFB7; } +:root.futaba .field:focus { + border-color: #EA8; +} /* Header */ :root.futaba #header-bar { @@ -19,6 +22,11 @@ background-color: rgba(255, 255, 255, .14); } +/* QR */ +:root.futaba .qrpreview { + background-color: rgba(0, 0, 0, .15); +} + /* Menu */ :root.futaba .entry:not(:last-child) { border-bottom: 1px solid #D9BFB7; diff --git a/css/photon.css b/css/photon.css index d50a8cc14..e509d5d56 100644 --- a/css/photon.css +++ b/css/photon.css @@ -3,6 +3,9 @@ background-color: #DDD; border-color: #CCC; } +:root.photon .field:focus { + border-color: #EA8; +} /* Header */ :root.photon #header-bar { @@ -19,6 +22,11 @@ background-color: rgba(255, 255, 255, .14); } +/* QR */ +:root.photon .qrpreview { + background-color: rgba(0, 0, 0, .15); +} + /* Menu */ :root.photon .entry:not(:last-child) { border-bottom: 1px solid #CCC; diff --git a/css/style.css b/css/style.css index cc3c571a9..bffd10050 100644 --- a/css/style.css +++ b/css/style.css @@ -5,6 +5,28 @@ display: block; padding: 0; } +.field { + border: 1px solid #CCC; + -moz-box-sizing: border-box; + box-sizing: border-box; + color: #333; + font: 13px sans-serif; + margin: 0; + padding: 2px 4px 3px; + outline: none; + -webkit-transition: color .25s, border-color .25s; + transition: color .25s, border-color .25s; +} +.field:-moz-placeholder, +.field:hover:-moz-placeholder { + color: #AAA !important; +} +.field:hover { + border-color: #999; +} +.field:hover, .field:focus { + color: #000; +} .move { cursor: move; } @@ -79,21 +101,15 @@ a[href="javascript:;"] { border-width: 0 0 1px; padding: 4px; position: relative; - transition: all .1s ease-in-out; - -o-transition: all .1s ease-in-out; - -moz-transition: all .1s ease-in-out; -webkit-transition: all .1s ease-in-out; + transition: all .1s ease-in-out; } #header-bar.autohide:not(:hover) { margin-bottom: -1em; - transform: translateY(-100%); - -o-transform: translateY(-100%); - -moz-transform: translateY(-100%); -webkit-transform: translateY(-100%); - transition: all .75s .25s ease-in-out; - -o-transition: all .75s .25s ease-in-out; - -moz-transition: all .75s .25s ease-in-out; + transform: translateY(-100%); -webkit-transition: all .75s .25s ease-in-out; + transition: all .75s .25s ease-in-out; } #toggle-header-bar { cursor: n-resize; @@ -129,10 +145,8 @@ a[href="javascript:;"] { width: 500px; max-width: 100%; position: relative; - transition: all .25s ease-in-out; - -o-transition: all .25s ease-in-out; - -moz-transition: all .25s ease-in-out; -webkit-transition: all .25s ease-in-out; + transition: all .25s ease-in-out; } .notification.error { background-color: hsla(0, 100%, 40%, .9); @@ -154,6 +168,7 @@ a[href="javascript:;"] { position: absolute; } .message { + -moz-box-sizing: border-box; box-sizing: border-box; padding: 4px 20px; max-height: 200px; @@ -234,8 +249,8 @@ a[href="javascript:;"] { display: none; } #ihover { - box-sizing: border-box; -moz-box-sizing: border-box; + box-sizing: border-box; max-height: 100%; max-width: 75%; padding-bottom: 16px; @@ -270,11 +285,15 @@ a[href="javascript:;"] { } /* QR */ +.hide-original-post-form #postForm, +.hide-original-post-form .postingMode { + display: none; +} #qr > .move { min-width: 300px; overflow: hidden; - box-sizing: border-box; -moz-box-sizing: border-box; + box-sizing: border-box; padding: 0 2px; } #qr > .move > span { @@ -283,15 +302,16 @@ a[href="javascript:;"] { #autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning { cursor: pointer; } -#qr select, -#qr > form { +#qr select { margin: 0; } #dump { background: -webkit-linear-gradient(#EEE, #CCC); - background: -moz-linear-gradient(#EEE, #CCC); - background: -o-linear-gradient(#EEE, #CCC); background: linear-gradient(#EEE, #CCC); + border: 1px solid #CCC; + margin: 0; + padding: 2px 4px 3px; + outline: none; width: 10%; } .gecko #dump { @@ -299,14 +319,10 @@ a[href="javascript:;"] { } #dump:hover, #dump:focus { background: -webkit-linear-gradient(#FFF, #DDD); - background: -moz-linear-gradient(#FFF, #DDD); - background: -o-linear-gradient(#FFF, #DDD); background: linear-gradient(#FFF, #DDD); } #dump:active, .dump #dump:not(:hover):not(:focus) { background: -webkit-linear-gradient(#CCC, #DDD); - background: -moz-linear-gradient(#CCC, #DDD); - background: -o-linear-gradient(#CCC, #DDD); background: linear-gradient(#CCC, #DDD); } #qr:not(.dump) #replies, .dump > form > label { @@ -322,7 +338,7 @@ a[href="javascript:;"] { user-select: none; } #replies > div { - counter-reset: thumbnails; + counter-reset: qrpreviews; top: 0; right: 0; bottom: 0; left: 0; margin: 0; padding: 0; overflow: hidden; @@ -334,103 +350,72 @@ a[href="javascript:;"] { overflow-x: auto; z-index: 1; } -.thumbnail { - background-color: rgba(0,0,0,.2) !important; - background-position: 50% 20% !important; - background-size: cover !important; - border: 1px solid #666; - box-sizing: border-box; +.qrpreview { + background-position: 50% 20%; + background-size: cover; + border: 1px solid #808080; + color: #FFF !important; + font-size: 12px; -moz-box-sizing: border-box; + box-sizing: border-box; cursor: move; display: inline-block; height: 90px; width: 90px; margin: 5px; padding: 2px; - opacity: .5; + opacity: .6; outline: none; overflow: hidden; position: relative; text-shadow: 0 1px 1px #000; -webkit-transition: opacity .25s ease-in-out; - -moz-transition: opacity .25s ease-in-out; - -o-transition: opacity .25s ease-in-out; transition: opacity .25s ease-in-out; vertical-align: top; } -.thumbnail:hover, .thumbnail:focus { +.qrpreview:hover, .qrpreview:focus { opacity: .9; + color: #FFF !important; } -.thumbnail#selected { +.qrpreview#selected { opacity: 1; } -.thumbnail::before { - counter-increment: thumbnails; - content: counter(thumbnails); - color: #FFF; +.qrpreview::before { + counter-increment: qrpreviews; + content: counter(qrpreviews); font-weight: 700; - padding: 3px; + text-shadow: 0 0 3px #000, 0 0 5px #000; position: absolute; - top: 0; - right: 0; - text-shadow: 0 0 3px #000, 0 0 8px #000; + top: 3px; right: 3px; } -.thumbnail.drag { - box-shadow: 0 0 10px rgba(0,0,0,.5); +.qrpreview.drag { + border-color: red; + border-style: dashed; } -.thumbnail.over { +.qrpreview.over { border-color: #FFF; -} -.thumbnail > span { - color: #FFF; + border-style: dashed; } .remove { - background: none; - color: #E00; + color: #E00 !important; font-weight: 700; padding: 3px; } .remove:hover::after { - content: " Remove"; + content: ' Remove'; } -.thumbnail > label { - background: rgba(0,0,0,.5); - color: #FFF; +.qrpreview > label { + background: rgba(0, 0, 0, .5); right: 0; bottom: 0; left: 0; position: absolute; text-align: center; } -.thumbnail > label > input { - margin: 0; +.qrpreview > label > input { + margin: 1px 0; + vertical-align: bottom; } #addReply { - color: #333; font-size: 3.5em; line-height: 100px; } -#addReply:hover, #addReply:focus { - color: #000; -} -.field { - border: 1px solid #CCC; - box-sizing: border-box; - -moz-box-sizing: border-box; - color: #333; - font: 13px sans-serif; - margin: 0; - padding: 2px 4px 3px; - -webkit-transition: color .25s, border .25s; - -moz-transition: color .25s, border .25s; - -o-transition: color .25s, border .25s; - transition: color .25s, border .25s; -} -.field:-moz-placeholder, -.field:hover:-moz-placeholder { - color: #AAA; -} -.field:hover, .field:focus { - border-color: #999; - color: #000; - outline: none; -} #qr > form > div:first-child > .field:not(#dump) { width: 30%; } diff --git a/css/tomorrow.css b/css/tomorrow.css index 071be1ffe..1fa04bac5 100644 --- a/css/tomorrow.css +++ b/css/tomorrow.css @@ -3,6 +3,9 @@ background-color: #282A2E; border-color: #111; } +:root.tomorrow .field:focus { + border-color: #000; +} /* Header */ :root.tomorrow #header-bar { @@ -19,6 +22,11 @@ background-color: rgba(0, 0, 0, .14); } +/* QR */ +:root.tomorrow .qrpreview { + background-color: rgba(255, 255, 255, .15); +} + /* Menu */ :root.tomorrow .entry:not(:last-child) { border-bottom: 1px solid #111; diff --git a/css/yotsuba-b.css b/css/yotsuba-b.css index c421c83ed..767289b74 100644 --- a/css/yotsuba-b.css +++ b/css/yotsuba-b.css @@ -3,6 +3,9 @@ background-color: #D6DAF0; border-color: #B7C5D9; } +:root.yotsuba-b .field:focus { + border-color: #98E; +} /* Header */ :root.yotsuba-b #header-bar { @@ -19,6 +22,11 @@ background-color: rgba(255, 255, 255, .14); } +/* QR */ +:root.yotsuba-b .qrpreview { + background-color: rgba(0, 0, 0, .15); +} + /* Menu */ :root.yotsuba-b .entry:not(:last-child) { border-bottom: 1px solid #B7C5D9; diff --git a/css/yotsuba.css b/css/yotsuba.css index 897f198ab..b51f3e1ee 100644 --- a/css/yotsuba.css +++ b/css/yotsuba.css @@ -3,6 +3,9 @@ background-color: #F0E0D6; border-color: #D9BFB7; } +:root.yotsuba .field:focus { + border-color: #EA8; +} /* Header */ :root.yotsuba #header-bar { @@ -19,6 +22,11 @@ background-color: rgba(255, 255, 255, .14); } +/* QR */ +:root.yotsuba .qrpreview { + background-color: rgba(0, 0, 0, .15); +} + /* Menu */ :root.yotsuba .entry:not(:last-child) { border-bottom: 1px solid #D9BFB7; diff --git a/src/config.coffee b/src/config.coffee index 84e55d97d..2237ac603 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -45,7 +45,6 @@ Config = 'Quick Reply': [true, 'WMD.'] 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'] 'Auto Hide QR': [false, 'Automatically hide the quick reply when posting.'] - 'Open Reply in New Tab': [false, 'Open replies posted from the board pages in a new tab.'] 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'] 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'] 'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.'] diff --git a/src/features.coffee b/src/features.coffee index 25d1a70d3..04d5b677e 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -762,38 +762,41 @@ Recursive = QR = init: -> - return unless Conf['Quick Reply'] + return if g.VIEW is 'catalog' or !Conf['Quick Reply'] if Conf['Hide Original Post Form'] - Main.css += """ - #postForm, .postingMode { - display: none; - } - """ + $.addClass doc, 'hide-original-post-form' link = $.el 'a', + className: 'qr-shortcut' textContent: 'Quick Reply' href: 'javascript:;' $.on link, 'click', -> Header.menu.close() QR.open() + if g.BOARD.ID is 'f' + if g.VIEW is 'index' + QR.threadSelector.value = '9999' + else if g.VIEW is 'thread' + QR.threadSelector.value = g.THREAD + else + QR.threadSelector.value = 'new' + $('textarea', QR.el).focus() $.event 'AddMenuEntry', type: 'header' el: link - Post::callbacks.push - name: 'Quick Reply' - cb: @node - - $.ready @readyInit - - readyInit: -> - if Conf['Persistent QR'] - QR.open() - QR.hide() if Conf['Auto Hide QR'] $.on d, 'dragover', QR.dragOver $.on d, 'drop', QR.dropFile $.on d, 'dragstart dragend', QR.drag + $.on d, '4chanXInitFinished', -> + return unless Conf['Persistent QR'] + QR.open() + QR.hide() if Conf['Auto Hide QR'] + + Post::callbacks.push + name: 'Quick Reply' + cb: @node node: -> $.on $('a[title="Quote this post"]', @nodes.info), 'click', QR.quote @@ -802,13 +805,14 @@ QR = if QR.el QR.el.hidden = false QR.unhide() - else - try - QR.dialog() - catch err - Main.handleErrors - message: 'Quick Reply dialog creation crashed.' - error: err + return + try + QR.dialog() + catch err + delete QR.el + Main.handleErrors + message: 'Quick Reply dialog creation crashed.' + error: err close: -> QR.el.hidden = true QR.abort() @@ -821,7 +825,7 @@ QR = QR.resetFileInput() if not Conf['Remember Spoiler'] and (spoiler = $.id 'spoiler').checked spoiler.click() - QR.cleanError() + QR.cleanNotification() hide: -> d.activeElement.blur() $.addClass QR.el, 'autohide' @@ -830,7 +834,10 @@ QR = $.rmClass QR.el, 'autohide' $.id('autohide').checked = false toggleHide: -> - @checked and QR.hide() or QR.unhide() + if @checked + QR.hide() + else + QR.unhide() error: (err) -> QR.open() @@ -844,20 +851,20 @@ QR = $('[autocomplete]', QR.el).focus() alert el.textContent if d.hidden QR.lastNotification = new Notification 'warning', el - cleanError: -> + cleanNotification: -> QR.lastNotification?.close() delete QR.lastNotification status: (data={}) -> return unless QR.el - if g.dead + if g.dead # XXX value = 404 disabled = true QR.cooldown.auto = false value = data.progress or QR.cooldown.seconds or value {input} = QR.status input.value = - if QR.cooldown.auto and Conf['Cooldown'] + if QR.cooldown.auto if value then "Auto #{value}" else 'Auto' else value or 'Submit' @@ -865,7 +872,6 @@ QR = cooldown: init: -> - return unless Conf['Cooldown'] QR.cooldown.types = thread: switch g.BOARD when 'q' then 86400 @@ -883,12 +889,11 @@ QR = QR.cooldown.count() sync: (cooldowns) -> # Add each cooldowns, don't overwrite everything in case we - # still need to purge one in the current tab to auto-post. + # still need to prune one in the current tab to auto-post. for id of cooldowns QR.cooldown.cooldowns[id] = cooldowns[id] QR.cooldown.start() set: (data) -> - return unless Conf['Cooldown'] start = Date.now() if data.delay cooldown = delay: data.delay @@ -926,7 +931,12 @@ QR = QR.status() return - if (isReply = if g.REPLY then true else QR.threadSelector.value isnt 'new') + isReply = + if g.BOARD.ID is 'f' and g.VIEW is 'thread' + true + else + QR.threadSelector.value isnt 'new' + if isReply post = QR.replies[0] isSage = /sage/i.test post.email hasFile = !!post.file @@ -973,14 +983,15 @@ QR = e?.preventDefault() QR.open() ta = $ 'textarea', QR.el - unless g.REPLY or ta.value + if QR.threadSelector and !ta.value and g.BOARD.ID isnt 'f' QR.threadSelector.value = $.x('ancestor::div[parent::div[@class="board"]]', @).id[1..] # Make sure we get the correct number, even with XXX censors - id = @previousSibling.hash[2..] - text = ">>#{id}\n" + post = Get.postFromRoot $.x 'ancestor-or-self::div[contains(@class,"postContainer")][1]', @ + text = ">>#{post}\n" sel = d.getSelection() - if (s = sel.toString().trim()) and id is $.x('ancestor-or-self::blockquote', sel.anchorNode)?.id.match(/\d+$/)[0] + selectionRoot = $.x 'ancestor-or-self::div[contains(@class,"postContainer")][1]', sel.anchorNode + if (s = sel.toString().trim()) and post.nodes.root is selectionRoot # XXX Opera doesn't retain `\n`s? s = s.replace /\n/g, '\n>' text += ">#{s}\n" @@ -1019,7 +1030,7 @@ QR = QR.fileInput.call e.dataTransfer $.addClass QR.el, 'dump' fileInput: -> - QR.cleanError() + QR.cleanNotification() # Set or change current reply's file. if @files.length is 1 file = @files[0] @@ -1063,7 +1074,7 @@ QR = @com = null @el = $.el 'a', - className: 'thumbnail' + className: 'qrpreview' draggable: true href: 'javascript:;' innerHTML: '×' @@ -1187,7 +1198,7 @@ QR = else if @el.id is 'selected' (QR.replies[index-1] or QR.replies[index+1]).select() QR.replies.splice index, 1 - (window.URL or window.webkitURL).revokeObjectURL? @url + (window.URL or window.webkitURL)?.revokeObjectURL @url captcha: init: -> @@ -1235,9 +1246,8 @@ QR = @count captchas.length @reload() load: -> - # Timeout is available at RecaptchaState.timeout in seconds. - # We use 5-1 minutes to give upload some time. - @timeout = Date.now() + 4*$.MINUTE + # -1 minute to give upload some time. + @timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE challenge = @challenge.firstChild.value @img.alt = challenge @img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @@ -1252,8 +1262,8 @@ QR = "Verification (#{count} cached captchas)" @input.alt = count # For XTRM RICE. reload: (focus) -> - # the "t" argument prevents the input from being focused - $.globalEval 'javascript:Recaptcha.reload("t")' + # the 't' argument prevents the input from being focused + $.unsafeWindow.Recaptcha.reload 'r' # Focus if we meant to. QR.captcha.input.focus() if focus keydown: (e) -> @@ -1267,23 +1277,16 @@ QR = e.preventDefault() dialog: -> - QR.el = UI.dialog 'qr', 'top:0;right:0;', ' -
- Quick Reply - × -
-
-
-
+
-
-
- -
' - - if Conf['Remember QR size'] and $.engine is 'gecko' - $.on ta = $('textarea', QR.el), 'mouseup', -> - $.set 'QR.size', @style.cssText - ta.style.cssText = $.get 'QR.size', '' + QR.el = UI.dialog 'qr', 'top:0;right:0;', """ +
Quick Reply ×
+
+
+
+
+
+ +
+ """ # Allow only this board's supported files. mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) -> @@ -1310,21 +1313,24 @@ QR = QR.charaCounter = $ '#charCount', QR.el ta = $ 'textarea', QR.el - unless g.REPLY - # Make a list with visible threads and an option to create a new one. + span = $('.move > span', QR.el) + + # Make a list of visible threads. + if g.BOARD.ID is 'f' + if g.VIEW is 'index' + QR.threadSelector = $('select[name=filetag]').cloneNode true + else + QR.threadSelector = $.el 'select', + title: 'Create a new thread / Reply to a thread' threads = '' - for thread in $$ '.thread' - id = thread.id[1..] - threads += "" - QR.threadSelector = - if g.BOARD is 'f' - $('select[name=filetag]').cloneNode true - else - $.el 'select' - innerHTML: threads - title: 'Create a new thread / Reply to a thread' - $.prepend $('.move > span', QR.el), QR.threadSelector - $.on QR.threadSelector, 'mousedown', (e) -> e.stopPropagation() + for key, thread of g.BOARD.threads + threads += "" + QR.threadSelector.innerHTML = threads + if g.VIEW is 'thread' + QR.threadSelector.value = g.THREAD + if QR.threadSelector + $.prepend span, QR.threadSelector + $.on span, 'mousedown', (e) -> e.stopPropagation() $.on $('#autohide', QR.el), 'change', QR.toggleHide $.on $('.close', QR.el), 'click', QR.close $.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump' @@ -1366,20 +1372,20 @@ QR = QR.abort() reply = QR.replies[0] - if g.BOARD is 'f' and not g.REPLY + if g.BOARD.ID is 'f' and g.VIEW is 'index' filetag = QR.threadSelector.value threadID = 'new' else - threadID = g.THREAD_ID or QR.threadSelector.value + threadID = QR.threadSelector.value # prevent errors if threadID is 'new' threadID = null - if g.BOARD in ['vg', 'q'] and !reply.sub + if g.BOARD.ID in ['vg', 'q'] and !reply.sub err = 'New threads require a subject.' else unless reply.file or textOnly = !!$ 'input[name=textonly]', $.id 'postForm' err = 'No file selected.' - else if g.BOARD is 'f' and filetag is '9999' + else if g.BOARD.ID is 'f' and filetag is '9999' err = 'Invalid tag specified.' else unless reply.com or reply.file err = 'No file selected.' @@ -1412,7 +1418,7 @@ QR = QR.status() QR.error err return - QR.cleanError() + QR.cleanNotification() # Enable auto-posting if we have stuff to post, disable it otherwise. QR.cooldown.auto = QR.replies.length > 1 @@ -1530,14 +1536,13 @@ QR = post: reply isReply: threadID isnt '0' + # Enable auto-posting if we have stuff to post, disable it otherwise. + QR.cooldown.auto = QR.replies.length > 1 + if threadID is '0' # new thread - # auto-noko - location.pathname = "/#{g.BOARD}/res/#{postID}" - else - # Enable auto-posting if we have stuff to post, disable it otherwise. - QR.cooldown.auto = QR.replies.length > 1 - if Conf['Open Reply in New Tab'] and !g.REPLY and !QR.cooldown.auto - $.open "//boards.4chan.org/#{g.BOARD}/res/#{threadID}#p#{postID}" + $.open "/#{g.BOARD}/res/#{postID}" + else if g.VIEW is 'reply' and !QR.cooldown.auto # posting from the index + $.open "//boards.4chan.org/#{g.BOARD}/res/#{threadID}#p#{postID}" if Conf['Persistent QR'] or QR.cooldown.auto reply.rm() @@ -2092,7 +2097,7 @@ Build = '
' + "No." + "No." + " + now = Date.now() if @file and !@file.isDead @file.isDead = true + @file.timeOfDeath = now return if img @isDead = true + @timeOfDeath = now $.addClass @nodes.root, 'dead' # XXX style dead posts. @@ -325,21 +328,22 @@ Main = initStyle: -> # disable the mobile layout $('link[href*=mobile]', d.head)?.disabled = true + $.addClass doc, $.engine + $.addClass doc, 'fourchan-x' $.addStyle Main.css - style = null + style = 'yotsuba-b' mainStyleSheet = $ 'link[title=switch]', d.head styleSheets = $$ 'link[rel="alternate stylesheet"]', d.head setStyle = -> - $.rmClass doc, style if style + $.rmClass doc, style for styleSheet in styleSheets if styleSheet.href is mainStyleSheet.href style = styleSheet.title.toLowerCase().replace('new', '').trim().replace /\s+/g, '-' break $.addClass doc, style - $.addClass doc, $.engine - $.addClass doc, 'fourchan-x' setStyle() + return unless mainStyleSheet if MutationObserver = window.MutationObserver or window.WebKitMutationObserver or window.OMutationObserver observer = new MutationObserver setStyle observer.observe mainStyleSheet, @@ -351,7 +355,7 @@ Main = initReady: -> unless $.hasClass doc, 'fourchan-x' - # Something might go wrong! + # Something might have gone wrong! Main.initStyle() if d.title is '4chan - 404 Not Found' @@ -362,28 +366,31 @@ Main = postID: location.hash return - threads = [] - posts = [] + if board = $ '.board' + threads = [] + posts = [] - for boardChild in $('.board').children - continue unless $.hasClass boardChild, 'thread' - thread = new Thread boardChild.id[1..], g.BOARD - threads.push thread - for threadChild in boardChild.children - continue unless $.hasClass threadChild, 'postContainer' - try - posts.push new Post threadChild, thread, g.BOARD - catch err - # Skip posts that we failed to parse. - unless errors - errors = [] - errors.push - message: "Parsing of Post No.#{threadChild.id.match(/\d+/)} failed. Post will be skipped." - error: err - Main.handleErrors errors if errors + for boardChild in board.children + continue unless $.hasClass boardChild, 'thread' + thread = new Thread boardChild.id[1..], g.BOARD + threads.push thread + for threadChild in boardChild.children + continue unless $.hasClass threadChild, 'postContainer' + try + posts.push new Post threadChild, thread, g.BOARD + catch err + # Skip posts that we failed to parse. + unless errors + errors = [] + errors.push + message: "Parsing of Post No.#{threadChild.id.match(/\d+/)} failed. Post will be skipped." + error: err + Main.handleErrors errors if errors - Main.callbackNodes Thread, threads - Main.callbackNodes Post, posts + Main.callbackNodes Thread, threads + Main.callbackNodes Post, posts + + $.event '4chanXInitFinished' callbackNodes: (klass, nodes) -> # get the nodes' length only once