diff --git a/CHANGELOG.md b/CHANGELOG.md index 89941408d..ecd964f89 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### v1.7.38 +*2014-05-15* + **ccd0** - Security enhancement: Remove a means by which an archive administrator could inject malicious Javascript into the page when 4chan X fetches a post from the archive. - Rewrite lots of HTML-generating code to make it easier to check for script injection vulnerabilities. diff --git a/LICENSE b/LICENSE index c238b3cf5..70de4049d 100755 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* 4chan X - Version 1.7.37 - 2014-05-14 +* 4chan X - Version 1.7.38 - 2014-05-15 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index a0097752c..d19b5e006 100755 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.7.37 +// @version 1.7.38 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 5d0f0fb86..9a1890e28 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.7.37 +// @version 1.7.38 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -24,7 +24,7 @@ // ==/UserScript== /* -* 4chan X - Version 1.7.37 - 2014-05-14 +* 4chan X - Version 1.7.38 - 2014-05-15 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE @@ -372,7 +372,7 @@ doc = d.documentElement; g = { - VERSION: '1.7.37', + VERSION: '1.7.38', NAMESPACE: '4chan X.', boards: {} }; @@ -1532,7 +1532,7 @@ this.close = __bind(this.close, this); this.add = __bind(this.add, this); this.el = $.el('div', { - innerHTML: '
' + innerHTML: '
' }); this.el.style.opacity = 0; this.setType(type); @@ -1789,30 +1789,14 @@ className: 'menu-button', innerHTML: '' }); - barFixedToggler = $.el('label', { - innerHTML: ' Fixed Header' - }); - headerToggler = $.el('label', { - innerHTML: ' Auto-hide header' - }); - scrollHeaderToggler = $.el('label', { - innerHTML: ' Auto-hide header on scroll' - }); - barPositionToggler = $.el('label', { - innerHTML: ' Bottom header' - }); - linkJustifyToggler = $.el('label', { - innerHTML: " Centered links" - }); - customNavToggler = $.el('label', { - innerHTML: ' Custom board navigation' - }); - footerToggler = $.el('label', { - innerHTML: " Hide bottom board list" - }); - shortcutToggler = $.el('label', { - innerHTML: " Shortcut Icons" - }); + barFixedToggler = UI.checkbox('Fixed Header', ' Fixed Header'); + headerToggler = UI.checkbox('Header auto-hide', ' Auto-hide header'); + scrollHeaderToggler = UI.checkbox('Header auto-hide on scroll', ' Auto-hide header on scroll'); + barPositionToggler = UI.checkbox('Bottom Header', ' Bottom header'); + linkJustifyToggler = UI.checkbox('Centered links', ' Centered links'); + customNavToggler = UI.checkbox('Custom Board Navigation', ' Custom board navigation'); + footerToggler = UI.checkbox('Bottom Board List', ' Hide bottom board list'); + shortcutToggler = UI.checkbox('Shortcut Icons', ' Shortcut Icons'); editCustomNav = $.el('a', { textContent: 'Edit custom board navigation', href: 'javascript:;' @@ -1948,7 +1932,7 @@ fourchannav = $.id('boardNavDesktop'); Header.boardList = boardList = $.el('span', { id: 'board-list', - innerHTML: "" + innerHTML: "" }); _ref = $$('a', boardList); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -2312,7 +2296,7 @@ Index = { init: function() { - var anchorEntry, input, label, modeEntry, name, refNavEntry, repliesEntry, sortEntry, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; + var anchorEntry, input, label, modeEntry, name, refNavEntry, repliesEntry, returnlink, sortEntry, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; if (g.BOARD.ID === 'f' || g.VIEW === 'catalog' || !Conf['JSON Navigation']) { return; } @@ -2332,15 +2316,15 @@ subEntries: [ { el: $.el('label', { - innerHTML: ' Paged' + innerHTML: ' Paged' }) }, { el: $.el('label', { - innerHTML: ' Infinite scrolling' + innerHTML: ' Infinite scrolling' }) }, { el: $.el('label', { - innerHTML: ' All threads' + innerHTML: ' All threads' }) } ] @@ -2360,23 +2344,23 @@ subEntries: [ { el: $.el('label', { - innerHTML: ' Bump order' + innerHTML: ' Bump order' }) }, { el: $.el('label', { - innerHTML: ' Last reply' + innerHTML: ' Last reply' }) }, { el: $.el('label', { - innerHTML: ' Creation date' + innerHTML: ' Creation date' }) }, { el: $.el('label', { - innerHTML: ' Reply count' + innerHTML: ' Reply count' }) }, { el: $.el('label', { - innerHTML: ' File count' + innerHTML: ' File count' }) } ] @@ -2390,28 +2374,21 @@ $.on(input, 'change', this.cb.sort); } repliesEntry = { - el: $.el('label', { - innerHTML: ' Show replies' - }) + el: UI.checkbox('Show Replies', ' Show replies') }; anchorEntry = { - el: $.el('label', { - innerHTML: ' Anchor hidden threads', - title: 'Move hidden threads at the end of the index.' - }) + el: UI.checkbox('Anchor Hidden Threads', ' Anchor hidden threads') }; refNavEntry = { - el: $.el('label', { - innerHTML: ' Refreshed navigation', - title: 'Refresh index when navigating through pages.' - }) + el: UI.checkbox('Refreshed Navigation', ' Refreshed navigation') }; + anchorEntry.el.title = 'Move hidden threads at the end of the index.'; + refNavEntry.el.title = 'Refresh index when navigating through pages.'; _ref2 = [repliesEntry, anchorEntry, refNavEntry]; for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { label = _ref2[_k]; input = label.el.firstChild; name = input.name; - input.checked = Conf[name]; $.on(input, 'change', $.cb.checked); switch (name) { case 'Show Replies': @@ -2436,19 +2413,21 @@ this.pagelist = $.el('div', { className: 'pagelist', hidden: true, - innerHTML: "
Catalog
" + innerHTML: '
' }); this.navLinks = $.el('div', { className: 'navLinks', - innerHTML: "Return Catalog Bottom ×" + innerHTML: 'Return Catalog Bottom ×' }); this.searchInput = $('#index-search', this.navLinks); this.currentPage = this.getCurrentPage(); + returnlink = $('#returnlink a', this.navLinks); + returnlink.href = "/" + g.BOARD + "/"; $.on(d, 'scroll', Index.scroll); $.on(this.pagelist, 'click', this.cb.pageNav); $.on(this.searchInput, 'input', this.onSearchInput); $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch); - $.on($('#returnlink a', this.navLinks), 'click', Navigate.navigate); + $.on(returnlink, 'click', Navigate.navigate); $.on($('#cataloglink a', this.navLinks), 'click', function() { return window.location = "//boards.4chan.org/" + g.BOARD + "/catalog"; }); @@ -3054,9 +3033,34 @@ }; Build = { - staticPath: '//s.4cdn.org/image/', - gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', + h_staticPath: '//s.4cdn.org/image/', + h_gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', spoilerRange: {}, + h_escape: function(text) { + return (text + '').replace(/[&"'<>]/g, function(c) { + return { + '&': '&', + "'": ''', + '"': '"', + '<': '<', + '>': '>' + }[c]; + }); + }, + unescape: function(text) { + if (text == null) { + return text; + } + return text.replace(/&(amp|#039|quot|lt|gt);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>' + }[c]; + }); + }, shortFilename: function(filename, isReply) { var ext, threshold; threshold = isReply ? 30 : 40; @@ -3080,25 +3084,29 @@ postID: data.no, threadID: data.resto || data.no, boardID: boardID, - name: data.name, + name: Build.unescape(data.name), capcode: data.capcode, tripcode: data.trip, uniqueID: data.id, - email: data.email ? encodeURI(data.email.replace(/"/g, '"')) : '', - subject: data.sub, + email: Build.unescape(data.email), + subject: Build.unescape(data.sub), flagCode: data.country, - flagName: data.country_name, + flagName: Build.unescape(data.country_name), date: data.now, dateUTC: data.time, - comment: data.com, + h_comment: data.com || '', isSticky: !!data.sticky, isClosed: !!data.closed }; - if (data.ext || data.filedeleted) { + if (data.filedeleted) { o.file = { - name: data.filename + data.ext, + isDeleted: true + }; + } else if (data.ext) { + o.file = { + name: (Build.unescape(data.filename)) + data.ext, timestamp: "" + data.tim + data.ext, - url: boardID === 'f' ? ("//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext).replace(/'/g, ''') : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, + url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, height: data.h, width: data.w, MD5: data.md5, @@ -3107,7 +3115,7 @@ theight: data.tn_h, twidth: data.tn_w, isSpoiler: !!data.spoiler, - isDeleted: !!data.filedeleted + isDeleted: false }; } return Build.post(o); @@ -3118,51 +3126,82 @@ This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS). @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE */ - var a, boardID, capcode, capcodeClass, capcodeIcon, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, gifIcon, href, imgSrc, isClosed, isOP, isSticky, name, pageIcon, pageNum, postID, quote, replyLink, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref; - postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file; + var E, boardID, capcode, container, date, dateUTC, email, file, fileSize, fileThumb, flagCode, flagName, h_capcodeClass, h_capcodeIcon, h_capcodeStart, h_closed, h_comment, h_emailEnd, h_emailStart, h_file, h_fileDims, h_fileInfo, h_flag, h_gifIcon, h_imgSrc, h_pageIcon, h_postClass, h_quoteLink, h_replyLink, h_sideArrows, h_staticPath, h_sticky, h_tripcode, h_userID, href, isClosed, isOP, isSticky, name, pageNum, postID, quote, shortFilename, spoilerRange, subject, threadID, tripcode, uniqueID, _i, _len, _ref; + E = Build.h_escape; + postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, h_comment = o.h_comment, file = o.file; + name || (name = ''); + subject || (subject = ''); isOP = postID === threadID; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - tripcode = tripcode ? " " + tripcode + "" : ''; - if (email) { - emailStart = ''; - emailEnd = ''; + h_staticPath = Build.h_staticPath, h_gifIcon = Build.h_gifIcon; + if (isOP) { + h_sideArrows = ''; } else { - emailStart = ''; - emailEnd = ''; + h_sideArrows = "
>>
"; + } + h_postClass = "post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : ''); + if (tripcode) { + h_tripcode = " " + (E(tripcode)) + ""; + } else { + h_tripcode = ''; + } + if (email) { + h_emailStart = ""; + h_emailEnd = ''; + } else { + h_emailStart = ''; + h_emailEnd = ''; } switch (capcode) { case 'admin': case 'admin_highlight': - capcodeClass = " capcodeAdmin"; - capcodeStart = " ## Admin"; - capcodeIcon = (" "; + h_capcodeClass = ' capcodeAdmin'; + h_capcodeStart = ' ## Admin'; + h_capcodeIcon = " "; break; case 'mod': - capcodeClass = " capcodeMod"; - capcodeStart = " ## Mod"; - capcodeIcon = (" "; + h_capcodeClass = ' capcodeMod'; + h_capcodeStart = ' ## Mod'; + h_capcodeIcon = " "; break; case 'developer': - capcodeClass = " capcodeDeveloper"; - capcodeStart = " ## Developer"; - capcodeIcon = (" "; + h_capcodeClass = ' capcodeDeveloper'; + h_capcodeStart = ' ## Developer'; + h_capcodeIcon = " "; break; default: - capcodeClass = ''; - capcodeStart = ''; - capcodeIcon = ''; + h_capcodeClass = ''; + h_capcodeStart = ''; + h_capcodeIcon = ''; + } + if (!capcode && uniqueID) { + h_userID = " (ID: " + (E(uniqueID)) + ") "; + } else { + h_userID = ''; + } + if (!flagCode) { + h_flag = ''; + } else if (boardID === 'pol') { + h_flag = " " + (E(flagCode)) + ""; + } else { + h_flag = " "; } - userID = !capcode && uniqueID ? (" (ID: ") + ("" + uniqueID + ") ") : ''; - flag = !flagCode ? '' : boardID === 'pol' ? "  + flagCode + " : " "; if (file != null ? file.isDeleted : void 0) { - fileHTML = isOP ? ("
") + ("File deleted.") + "
" : ("
") + ("File deleted.") + "
"; + if (isOP) { + h_file = "
"; + h_file += "File deleted."; + h_file += '
'; + } else { + h_file = "
"; + h_file += "File deleted."; + h_file += '
'; + } } else if (file) { fileSize = $.bytesToString(file.size); fileThumb = file.turl; if (file.isSpoiler) { fileSize = "Spoiler Image, " + fileSize; if (!isArchived) { - fileThumb = "" + staticPath + "spoiler"; + fileThumb = "" + h_staticPath + "spoiler"; if (spoilerRange = Build.spoilerRange[boardID]) { fileThumb += ("-" + boardID) + Math.floor(1 + spoilerRange * Math.random()); } @@ -3170,38 +3209,63 @@ file.twidth = file.theight = 100; } } - imgSrc = boardID === 'f' ? '' : ("") + ("" + fileSize + "") + ""; - a = $.el('a', { - innerHTML: file.name - }); - filename = a.textContent.replace(/%22/g, '"'); - a.textContent = Build.shortFilename(filename); - shortFilename = a.innerHTML; - a.textContent = filename; - filename = a.innerHTML.replace(/'/g, '''); - fileDims = file.name.slice(-3) === 'pdf' ? 'PDF' : "" + file.width + "x" + file.height; - fileInfo = ("
File: " + file.timestamp + "") + ("-(" + fileSize + ", " + fileDims + (file.isSpoiler ? '' : ", " + shortFilename + "")) + ")
"; - fileHTML = "
" + fileInfo + imgSrc + "
"; + shortFilename = Build.shortFilename(file.name); + if (boardID === 'f') { + h_imgSrc = ''; + } else { + h_imgSrc = ""; + h_imgSrc += "" + (E(fileSize)) + ""; + h_imgSrc += ''; + } + if (file.url.slice(-4) === '.pdf') { + h_fileDims = 'PDF'; + } else { + h_fileDims = "" + (+file.width) + "x" + (+file.height); + } + h_fileInfo = "
" + (E(file.timestamp)) + ""; + h_fileInfo += "-(" + (E(fileSize)) + ", " + h_fileDims; + if (!file.isSpoiler) { + h_fileInfo += ", " + (E(shortFilename)) + ""; + } + h_fileInfo += ')
'; + h_file = "
" + h_fileInfo + h_imgSrc + "
"; } else { - fileHTML = ''; + h_file = ''; } - sticky = isSticky ? " Sticky" : ''; - closed = isClosed ? " Closed" : ''; - if (isOP && g.VIEW === 'index' && Conf['JSON Navigation']) { - pageNum = Math.floor(Index.liveThreadIDs.indexOf(postID) / Index.threadsNumPerPage + 1); - pageIcon = " [" + pageNum + "]"; + if (g.VIEW === 'thread' && g.THREADID === +threadID) { + h_quoteLink = "javascript:quote(" + (+postID) + ")"; } else { - pageIcon = ''; + h_quoteLink = "/" + (E(boardID)) + "/thread/" + (+threadID) + "\#q" + (+postID); + } + if (isSticky) { + h_sticky = " Sticky"; + } else { + h_sticky = ''; + } + if (isClosed) { + h_closed = " Closed"; + } else { + h_closed = ''; + } + if (isOP && g.VIEW === 'index' && Conf['JSON Navigation']) { + pageNum = Math.floor(Index.liveThreadIDs.indexOf(postID) / Index.threadsNumPerPage) + 1; + h_pageIcon = " [" + (+pageNum) + "]"; + } else { + h_pageIcon = ''; } if (isOP && g.VIEW === 'index') { - replyLink = "   [Reply]"; + h_replyLink = "   [Reply]"; } else { - replyLink = ''; + h_replyLink = ''; } container = $.el('div', { id: "pc" + postID, className: "postContainer " + (isOP ? 'op' : 'reply') + "Container", - innerHTML: "" + (isOP ? '' : "
>>
") + "
" + (isOP ? fileHTML : '') + "
" + ' ' + "" + (subject || '') + "" + ' ' + "" + emailStart + "" + (name || '') + "" + (tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag) + "" + " " + "" + date + "" + ' ' + "No." + postID + "" + (pageIcon + sticky + closed + replyLink) + "
" + (isOP ? '' : fileHTML) + "
" + (comment || '') + "
" + ' ' + "
" + innerHTML: "" + h_sideArrows + "
" + (isOP ? h_file : '') + "
" + ' ' + "" + (E(subject)) + "" + ' ' + "" + h_emailStart + "" + (E(name)) + "" + h_tripcode + h_capcodeStart + h_emailEnd + h_capcodeIcon + h_userID + h_flag + "" + ' ' + "" + (E(date)) + "" + ' ' + "No." + (+postID) + "" + h_pageIcon + h_sticky + h_closed + h_replyLink + "
" + (isOP ? '' : h_file) + "
" + h_comment + "
" + ' ' + "
" }); _ref = $$('.quotelink', container); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -3443,7 +3507,7 @@ return Get.insert(post, root, context); }, archivedPost: function(req, boardID, postID, root, context) { - var board, bq, comment, data, o, post, thread, threadID, _ref; + var board, data, h_comment, o, post, thread, threadID, _ref; if (post = g.posts["" + boardID + "." + postID]) { Get.insert(post, root, context); return; @@ -3454,17 +3518,15 @@ root.textContent = data.error; return; } - bq = $.el('blockquote', { - textContent: data.comment - }); - bq.innerHTML = bq.innerHTML.replace(/\n|\[\/?[a-z]+(:lit)?\]/g, Get.parseMarkup); - comment = bq.innerHTML.replace(/(^|>)(>[^<$]*)(<|$)/g, '$1$2$3').replace(/((>){2}(>\/[a-z\d]+\/)?\d+)/g, '$1'); + h_comment = Build.h_escape(data.comment || ''); + h_comment = h_comment.replace(/\n|\[\/?[a-z]+(:lit)?\]/g, Get.parseMarkup); + h_comment = h_comment.replace(/(^|>)(>[^<$]*)(<|$)/g, '$1$2$3').replace(/((>){2}(>\/[a-z\d]+\/)?\d+)/g, '$1'); threadID = +data.thread_num; o = { postID: postID, threadID: threadID, boardID: boardID, - name: data.name_processed, + name: data.name, capcode: (function() { switch (data.capcode) { case 'M': @@ -3477,17 +3539,17 @@ })(), tripcode: data.trip, uniqueID: data.poster_hash, - email: data.email ? encodeURI(data.email) : '', - subject: data.title_processed, + email: data.email || '', + subject: data.title, flagCode: data.poster_country, - flagName: data.poster_country_name_processed, + flagName: data.poster_country_name, date: data.fourchan_date, dateUTC: data.timestamp, - comment: comment + h_comment: h_comment }; if ((_ref = data.media) != null ? _ref.media_filename : void 0) { o.file = { - name: data.media.media_filename_processed, + name: data.media.media_filename, timestamp: data.media.media_orig, url: data.media.media_link || data.media.remote_media_link, height: data.media.media_h, @@ -3515,7 +3577,7 @@ '[/b]': '', '[spoiler]': '', '[/spoiler]': '', - '[code]': '
',
+        '[code]': '
',
         '[/code]': '
', '[moot]': '
', '[/moot]': '
', @@ -3526,7 +3588,7 @@ }; UI = (function() { - var Menu, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove; + var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove; dialog = function(id, position, properties) { var child, el, move, _i, _len, _ref; el = $.el('div', { @@ -3946,10 +4008,25 @@ return this.cb.call(this); } }; + checkbox = function(name, text, checked) { + var input, label; + if (checked == null) { + checked = Conf[name]; + } + label = $.el('label'); + input = $.el('input', { + type: 'checkbox', + name: name, + checked: checked + }); + $.add(label, [input, $.tn(text)]); + return label; + }; return { dialog: dialog, Menu: Menu, - hover: hoverstart + hover: hoverstart, + checkbox: checkbox }; })(); @@ -4306,15 +4383,9 @@ href: 'javascript:;' }); $.on(apply, 'click', PostHiding.menu.hide); - thisPost = $.el('label', { - innerHTML: ' This post' - }); - replies = $.el('label', { - innerHTML: " Hide replies" - }); - makeStub = $.el('label', { - innerHTML: " Make stub" - }); + thisPost = UI.checkbox('thisPost', ' This post', true); + replies = UI.checkbox('replies', ' Hide replies', Conf['Recursive Hiding']); + makeStub = UI.checkbox('makeStub', ' Make stub', Conf['Stubs']); $.event('AddMenuEntry', { type: 'post', el: div, @@ -4347,12 +4418,8 @@ href: 'javascript:;' }); $.on(apply, 'click', PostHiding.menu.show); - thisPost = $.el('label', { - innerHTML: ' This post' - }); - replies = $.el('label', { - innerHTML: " Show replies" - }); + thisPost = UI.checkbox('thisPost', ' This post', false); + replies = UI.checkbox('replies', ' Show replies', false); hideStubLink = $.el('a', { textContent: 'Hide stub', href: 'javascript:;' @@ -4727,9 +4794,7 @@ href: 'javascript:;' }); $.on(apply, 'click', ThreadHiding.menu.hide); - makeStub = $.el('label', { - innerHTML: " Make stub" - }); + makeStub = UI.checkbox('Stubs', ' Make stub'); $.event('AddMenuEntry', { type: 'post', el: div, @@ -5307,7 +5372,7 @@ } this.enabled = true; this.controls = $.el('span', { - innerHTML: '' + innerHTML: '' }); input = $('input', this.controls); $.on(input, 'change', this.toggle); @@ -5711,7 +5776,7 @@ return; } link = $.el('h1', { - innerHTML: "" + (g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread') + "", + innerHTML: "" + (g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread') + "", className: "qr-link-container" }); QR.link = link.firstElementChild; @@ -6214,7 +6279,7 @@ var dialog, elm, event, i, items, name, node, nodes, prop, rules, save, setNode, _, _i, _len, _ref, _ref1, _ref2; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top:0;right:0;', { - innerHTML: "
×
No selected file
" + innerHTML: '
×
No selected file
' }) }; setNode = function(name, query) { @@ -6464,7 +6529,7 @@ QR.cooldown.auto = false; QR.status(); return QR.error($.el('span', { - innerHTML: '4chan X encountered an error while posting. \n[Banned?] [More info]' + innerHTML: '4chan X encountered an error while posting. \n[Banned?] [More info]' })); } }; @@ -6489,16 +6554,17 @@ return QR.status(); }, response: function() { - var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; + var URL, ban, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; req = QR.req; delete QR.req; post = QR.posts[0]; post.unlock(); resDoc = req.response; if (ban = $('.banType', resDoc)) { - board = $('.board', resDoc).innerHTML; - err = $.el('span', { - innerHTML: ban.textContent.toLowerCase() === 'banned' ? "You are banned on " + board + "! ;_;
\nClick here to see the reason." : "You were issued a warning on " + board + " as " + ($('.nameBlock', resDoc).innerHTML) + ".
\nReason: " + ($('.reason', resDoc).innerHTML) + err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? { + innerHTML: "You are banned on " + ($('.board', resDoc).innerHTML) + "! ;_;
Click here to see the reason." + } : { + innerHTML: "You were issued a warning on " + ($('.board', resDoc).innerHTML) + " as " + ($('.nameBlock', resDoc).innerHTML) + ".
Reason: " + ($('.reason', resDoc).innerHTML) }); } else if (err = resDoc.getElementById('errmsg')) { if ((_ref = $('a', err)) != null) { @@ -7026,9 +7092,9 @@ } }, getPassword: function() { - var input, m; + var input, m, _ref; if (!QR.persona.pwd) { - QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : $.id('delPassword').value; + QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : ((_ref = $.id('delPassword')) != null ? _ref.value : void 0) || ''; } return QR.persona.pwd; }, @@ -7062,7 +7128,7 @@ className: 'qr-preview', draggable: true, href: 'javascript:;', - innerHTML: '' + innerHTML: '' }); this.nodes = { el: el, @@ -7450,10 +7516,8 @@ continue; } lc = type.toLowerCase(); - el = $.el('label', { - innerHTML: " " + type + " Tyme", - title: "" + type + " Tyme" - }); + el = UI.checkbox(lc, " " + type + " Tyme", false); + el.title = "" + type + " Tyme"; FappeTyme[lc] = input = el.firstElementChild; $.on(input, 'change', FappeTyme.cb.toggle.bind(input)); $.event('AddMenuEntry', { @@ -7527,7 +7591,7 @@ nodes = Gallery.nodes = {}; nodes.el = dialog = $.el('div', { id: 'a-gallery', - innerHTML: '
\n \n \n ×\n \n \n / \n
\n
\n \n
\n
\n
\n
' + innerHTML: '
× /
' }); _ref = { frame: '.gal-image', @@ -7801,14 +7865,11 @@ }, createSubEntry: function(name) { var input, label; - label = $.el('label', { - innerHTML: " " + name - }); + label = UI.checkbox(name, " " + name); input = label.firstElementChild; if (name === 'Fit Width' || name === 'Fit Height' || name === 'Hide Thumbnails') { $.on(input, 'change', Gallery.cb.setFitness); } - input.checked = Conf[name]; $.event('change', null, input); $.on(input, 'change', $.cb.checked); return { @@ -8149,15 +8210,12 @@ }, createSubEntry: function(name, desc) { var input, label; - label = $.el('label', { - innerHTML: " " + name, - title: desc - }); + label = UI.checkbox(name, " " + name); + label.title = desc; input = label.firstElementChild; if (name === 'Fit width' || name === 'Fit height') { $.on(input, 'change', ImageExpand.cb.setFitness); } - input.checked = Conf[name]; $.event('change', null, input); $.on(input, 'change', $.cb.checked); return { @@ -8312,7 +8370,7 @@ return; } prefetch = $.el('label', { - innerHTML: ' Prefetch Images' + innerHTML: ' Prefetch Images' }); this.el = prefetch.firstElementChild; $.on(this.el, 'change', this.toggle); @@ -9436,7 +9494,7 @@ } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { - innerHTML: "0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : ''), + innerHTML: "0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : ''), id: 'thread-stats', title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "") }); @@ -9445,7 +9503,7 @@ }); } else { this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', { - innerHTML: "
0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : '') + "
" + innerHTML: "
0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : '') + "
" }); $.ready((function(_this) { return function() { @@ -9551,13 +9609,13 @@ ThreadUpdater = { init: function() { - var checked, conf, el, input, name, sc, subEntries, _ref; + var conf, el, input, name, sc, subEntries, _ref; if (g.VIEW !== 'thread' || !Conf['Thread Updater']) { return; } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { - innerHTML: '', + innerHTML: '', id: 'updater' }); $.ready(function() { @@ -9565,7 +9623,7 @@ }); } else { this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', { - innerHTML: '
' + innerHTML: '
' }); $.addClass(doc, 'float'); $.ready((function(_this) { @@ -9585,11 +9643,8 @@ _ref = Config.updater.checkbox; for (name in _ref) { conf = _ref[name]; - checked = Conf[name] ? 'checked' : ''; - el = $.el('label', { - title: "" + conf[1], - innerHTML: " " + name - }); + el = UI.checkbox(name, " " + name); + el.title = conf[1]; input = el.firstElementChild; $.on(input, 'change', $.cb.checked); if (input.name === 'Scroll BG') { @@ -9603,7 +9658,7 @@ }); } this.settings = $.el('span', { - innerHTML: 'Interval' + innerHTML: 'Interval' }); $.on(this.settings, 'click', this.intervalShortcut); subEntries.push({ @@ -9825,7 +9880,7 @@ } }, update: function() { - var url, _ref; + var _ref; if (!navigator.onLine) { return; } @@ -9838,10 +9893,8 @@ if ((_ref = ThreadUpdater.req) != null) { _ref.abort(); } - url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json"; - return ThreadUpdater.req = $.ajax(url, { - onloadend: ThreadUpdater.cb.load - }, { + return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { + onloadend: ThreadUpdater.cb.load, whenModified: true }); }, @@ -9969,7 +10022,7 @@ }); this.db = new DataBoard('watchedThreads', this.refresh, true); this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', { - innerHTML: "
Thread Watcher ×
" + innerHTML: '
Thread Watcher ×
' }); this.status = $('#watcher-status', this.dialog); this.list = this.dialog.lastElementChild; @@ -10437,13 +10490,10 @@ var entry, input; entry = { type: 'thread watcher', - el: $.el('label', { - innerHTML: " " + name, - title: desc - }) + el: UI.checkbox(name, " " + name) }; + entry.el.title = desc; input = entry.el.firstElementChild; - input.checked = Conf[name]; $.on(input, 'change', $.cb.checked); if (name === 'Current Board') { $.on(input, 'change', ThreadWatcher.refresh); @@ -10862,7 +10912,7 @@ $.event('AddMenuEntry', entry); $.on(entry.el, 'click', PSAHiding.toggle); PSAHiding.btn = btn = $.el('span', { - innerHTML: '[Dismiss]', + innerHTML: '[Dismiss]', title: 'Mark announcement as read and hide.', className: 'hide-announcement', href: 'javascript:;' @@ -11007,11 +11057,8 @@ if (!Conf['Catalog Links']) { return; } - CatalogLinks.el = el = $.el('label', { - id: 'toggleCatalog', - href: 'javascript:;', - innerHTML: " Catalog Links" - }); + CatalogLinks.el = el = UI.checkbox('Header catalog links', ' Catalog Links'); + el.id = 'toggleCatalog'; input = $('input', el); $.on(input, 'change', this.toggle); $.sync('Header catalog links', CatalogLinks.set); @@ -11436,65 +11483,42 @@ if (!this.file || this.isClone) { return; } - return this.file.text.innerHTML = "" + (FileInfo.format(Conf['fileInfo'], this)) + ""; + return this.file.text.innerHTML = "" + (FileInfo.h_format(Conf['fileInfo'], this)) + ""; }, - format: function(formatString, post) { + h_format: function(formatString, post) { return formatString.replace(/%([A-Za-z])|[^%]+/g, function(s, c) { - if (c in FileInfo.formatters) { - return FileInfo.formatters[c].call(post); + if (c in FileInfo.h_formatters) { + return FileInfo.h_formatters[c].call(post); } else { - return FileInfo.escape(s); + return Build.h_escape(s); } }); }, - convertUnit: function(size, unit) { - var i; - if (unit === 'B') { - return "" + (size.toFixed()) + " Bytes"; - } - i = 1 + ['KB', 'MB'].indexOf(unit); - while (i--) { - size /= 1024; - } - size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); - return "" + size + " " + unit; - }, - escape: function(name) { - return name.replace(/[&"'<>]/g, function(c) { - return { - '&': '&', - "'": ''', - '"': '"', - '<': '<', - '>': '>' - }[c]; - }); - }, - formatters: { + h_formatters: { t: function() { - return FileInfo.escape(this.file.URL.match(/\d+\..+$/)[0]); + return Build.h_escape(this.file.URL.match(/\d+\..+$/)[0]); }, T: function() { - return "" + (FileInfo.formatters.t.call(this)) + ""; + return "" + (FileInfo.h_formatters.t.call(this)) + ""; }, l: function() { - return "" + (FileInfo.formatters.n.call(this)) + ""; + return "" + (FileInfo.h_formatters.n.call(this)) + ""; }, L: function() { - return "" + (FileInfo.formatters.N.call(this)) + ""; + return "" + (FileInfo.h_formatters.N.call(this)) + ""; }, n: function() { var fullname, shortname; fullname = this.file.name; shortname = Build.shortFilename(this.file.name, this.isReply); if (fullname === shortname) { - return FileInfo.escape(fullname); + return Build.h_escape(fullname); } else { - return "" + (FileInfo.escape(shortname)) + "" + (FileInfo.escape(fullname)) + ""; + return "" + (Build.h_escape(shortname)) + "" + (Build.h_escape(fullname)) + ""; } }, N: function() { - return FileInfo.escape(this.file.name); + return Build.h_escape(this.file.name); }, p: function() { if (this.file.isSpoiler) { @@ -11504,19 +11528,19 @@ } }, s: function() { - return FileInfo.escape(this.file.size); + return Build.h_escape(this.file.size); }, B: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); + return "" + (+this.file.sizeInBytes) + " Bytes"; }, K: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); + return "" + (+Math.round(this.file.sizeInBytes / 1024)) + " KB"; }, M: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); + return "" + (+Math.round(this.file.sizeInBytes / 1048576 * 100) / 100) + " MB"; }, r: function() { - return FileInfo.escape(this.file.dimensions || 'PDF'); + return Build.h_escape(this.file.dimensions || 'PDF'); } } }; @@ -11529,14 +11553,14 @@ } board = g.BOARD.ID; if (board === 'g') { - $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); + $.globalEval('window.addEventListener(\'prettyprint\', function(e) {\n window.dispatchEvent(new CustomEvent(\'prettyprint:cb\', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);'); Post.callbacks.push({ name: 'Parse /g/ code', cb: this.code }); } if (board === 'sci') { - $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); + $.globalEval('window.addEventListener(\'jsmath\', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push(\'ProcessBeforeShowing\', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);'); return Post.callbacks.push({ name: 'Parse /sci/ math', cb: this.math @@ -12509,7 +12533,7 @@ } } if (Conf['Quick Reply']) { - QR.link.textContent = g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread'; + QR.link.textContent = view === 'thread' ? 'Reply to Thread' : 'Start a Thread'; QR.status(); _ref = QR.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -12576,11 +12600,11 @@ Favicon.update(); findStyle = function(type, base) { var style; - style = d.cookie.match(new RegExp("\b" + type + "\_style\=([^;]+);\b")); + style = d.cookie.match(new RegExp("\\b" + type + "_style=([^;]+)")); return ["" + type + "_style", (style ? style[1] : base)]; }; style = sfw ? findStyle('ws', 'Yotsuba B New') : findStyle('nws', 'Yotsuba New'); - $.globalEval("var style_group = '" + style[0] + "'"); + $.globalEval("var style_group = " + (JSON.stringify(style[0]))); $('link[title=switch]', d.head).href = $("link[title='" + style[1] + "']", d.head).href; return Main.setClass(); }, @@ -12799,19 +12823,18 @@ return localStorage.setItem('4chan-settings', JSON.stringify(settings)); }, open: function(openSection) { - var dialog, html, link, links, overlay, section, sectionToOpen, _i, _len, _ref; + var dialog, link, links, overlay, section, sectionToOpen, _i, _len, _ref; if (Settings.dialog) { return; } $.event('CloseMenu'); - html = "
"; Settings.overlay = overlay = $.el('div', { id: 'overlay' }); Settings.dialog = dialog = $.el('div', { id: 'fourchanx-settings', className: 'dialog', - innerHTML: html + innerHTML: '
' }); $.on($('.export', Settings.dialog), 'click', Settings["export"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]); @@ -12883,14 +12906,19 @@ for (key in _ref) { obj = _ref[key]; fs = $.el('fieldset', { - innerHTML: "" + key + "" + innerHTML: '' }); + fs.firstElementChild.textContent = key; for (key in obj) { arr = obj[key]; description = arr[1]; - div = $.el('div', { - innerHTML: ": " + description + "" - }); + div = $.el('div'); + $.add(div, [ + UI.checkbox(key, key, false), $.el('span', { + "class": 'description', + textContent: ": " + description + }) + ]); input = $('input', div); $.on(input, 'change', $.cb.checked); items[key] = Conf[key]; @@ -12907,7 +12935,7 @@ } }); div = $.el('div', { - innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." + innerHTML: ': Clear manually-hidden threads and posts on all boards. Reload the page to apply.' }); button = $('button', div); $.get({ @@ -13104,7 +13132,7 @@ }, filter: function(section) { var select; - section.innerHTML = "
"; + section.innerHTML = '
'; select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); @@ -13126,11 +13154,13 @@ $.add(div, ta); return; } - return div.innerHTML = "
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
MD5 filtering uses exact string matching, not regular expressions.

    You can use these settings with each regular expression, separate them with semicolons:
  • Per boards, separate them with commas. It is global if not specified.
    For example: boards:a,jp;.
  • Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).
    For example: op:only;, op:no; or op:yes;.
  • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
    For example: stub:yes; or stub:no;.
  • Highlight instead of hiding. You can specify a class name to use with a userstyle.
    For example: highlight; or highlight:wallpaper;.
  • Highlighted OPs will have their threads put on top of the board index by default.
    For example: top:yes; or top:no;.
"; + div.innerHTML = '
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
MD5 filtering uses exact string matching, not regular expressions.

    You can use these settings with each regular expression, separate them with semicolons:
  • Per boards, separate them with commas. It is global if not specified.
    For example: boards:a,jp;.
  • Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).
    For example: op:only;, op:no; or op:yes;.
  • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
    For example: stub:yes; or stub:no;.
  • Highlight instead of hiding. You can specify a class name to use with a userstyle.
    For example: highlight; or highlight:wallpaper;.
  • Highlighted OPs will have their threads put on top of the board index by default.
    For example: top:yes; or top:no;.
'; + return $('.warning', div).hidden = Conf['Filter']; }, sauce: function(section) { var ta; - section.innerHTML = "
Sauce is disabled.
Lines starting with a # will be ignored.
You can specify a display text by appending ;text:[text] to the URL.
    These parameters will be replaced by their corresponding values:
  • %TURL: Thumbnail URL.
  • %URL: Full image URL.
  • %MD5: MD5 hash.
  • %name: Original file name.
  • %board: Current board.
"; + section.innerHTML = '
Sauce is disabled.
Lines starting with a # will be ignored.
You can specify a display text by appending ;text:[text] to the URL.
    These parameters will be replaced by their corresponding values:
  • %TURL: Thumbnail URL.
  • %URL: Full image URL.
  • %MD5: MD5 hash.
  • %name: Original file name.
  • %board: Current board.
'; + $('.warning', section).hidden = Conf['Sauce']; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { return ta.value = item['sauces']; @@ -13138,13 +13168,18 @@ return $.on(ta, 'change', $.cb.value); }, advanced: function(section) { - var archBoards, boardID, boardOptions, boardSelect, boards, event, files, i, input, inputs, item, items, name, o, row, rows, software, ta, table, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; - section.innerHTML = "
Archiver
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection
Disabled selections indicate that only one archive is available for that board and redirection type.
Custom Board Navigation
New lines will be converted into spaces.

In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: g
Title link: g-title
Board link (Replace with title when on that board): g-replace
Full text link: g-full
Custom text link: g-text:\"Install Gentoo\"
Index-only link: g-index
Catalog-only link: g-catalog
External link: external-text:\"Google\",\"http://www.google.com\"
Combinations are possible: g-index-text:\"Technology Index\"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
if you are on /g/.
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas

One item per line.
Items will be added in the relevant input's auto-completion list.
Password items will always be used, since there is no password input.
Lines starting with a # will be ignored.

    You can use these settings with each item, separate them with semicolons:
  • Possible items are: name, email, subject and password.
  • Wrap values of items with quotes, like this: email:\"sage\".
  • Force values as defaults with the always keyword, for example: email:\"sage\";always.
  • Select specific boards for an item, separated with commas, for example: email:\"sage\";boards:jp;always.
Unread Favicon is disabled.
Emoji is disabled.
Sage Icon:
Position:
Thread Updater is disabled.
Interval:
"; + var archBoards, boardID, boardOptions, boardSelect, boards, customCSS, event, files, i, input, inputs, interval, item, items, name, o, row, rows, software, ta, table, warning, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; + section.innerHTML = '
Archiver
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection
Disabled selections indicate that only one archive is available for that board and redirection type.
Custom Board Navigation
New lines will be converted into spaces.

In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: g
Title link: g-title
Board link (Replace with title when on that board): g-replace
Full text link: g-full
Custom text link: g-text:"Install Gentoo"
Index-only link: g-index
Catalog-only link: g-catalog
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
if you are on /g/.
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays \'PDF\' for PDF files)
Quick Reply Personas

One item per line.
Items will be added in the relevant input\'s auto-completion list.
Password items will always be used, since there is no password input.
Lines starting with a # will be ignored.

    You can use these settings with each item, separate them with semicolons:
  • Possible items are: name, email, subject and password.
  • Wrap values of items with quotes, like this: email:"sage".
  • Force values as defaults with the always keyword, for example: email:"sage";always.
  • Select specific boards for an item, separated with commas, for example: email:"sage";boards:jp;always.
Unread Favicon is disabled.
Emoji is disabled.
Sage Icon:
Position:
Thread Updater is disabled.
Interval:
'; + _ref = $$('.warning', section); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + warning = _ref[_i]; + warning.hidden = Conf[warning.dataset.feature]; + } items = {}; inputs = {}; - _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - name = _ref[_i]; + _ref1 = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + name = _ref1[_j]; input = $("[name=" + name + "]", section); items[name] = Conf[name]; inputs[name] = input; @@ -13172,15 +13207,20 @@ Settings[key].call(input); } }); - $.on($('input[name=Interval]', section), 'change', ThreadUpdater.cb.interval); - $.on($('input[name="Custom CSS"]', section), 'change', Settings.togglecss); + interval = $('input[name="Interval"]', section); + customCSS = $('input[name="Custom CSS"]', section); + interval.value = Conf['Interval']; + customCSS.checked = Conf['Custom CSS']; + inputs['usercss'].disabled = !Conf['Custom CSS']; + $.on(interval, 'change', ThreadUpdater.cb.interval); + $.on(customCSS, 'change', Settings.togglecss); $.on($.id('apply-css'), 'click', Settings.usercss); archBoards = {}; - _ref1 = Redirect.archives; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - _ref2 = _ref1[_j], name = _ref2.name, boards = _ref2.boards, files = _ref2.files, software = _ref2.software, withCredentials = _ref2.withCredentials; - for (_k = 0, _len2 = boards.length; _k < _len2; _k++) { - boardID = boards[_k]; + _ref2 = Redirect.archives; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + _ref3 = _ref2[_k], name = _ref3.name, boards = _ref3.boards, files = _ref3.files, software = _ref3.software, withCredentials = _ref3.withCredentials; + for (_l = 0, _len3 = boards.length; _l < _len3; _l++) { + boardID = boards[_l]; o = archBoards[boardID] || (archBoards[boardID] = { thread: [[], []], post: [[], []], @@ -13198,9 +13238,9 @@ } for (boardID in archBoards) { o = archBoards[boardID]; - _ref3 = ['thread', 'post', 'file']; - for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { - item = _ref3[_l]; + _ref4 = ['thread', 'post', 'file']; + for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { + item = _ref4[_m]; if (o[item][0].length === 0 && o[item][1].length !== 0) { o[item][0].push('disabled'); } @@ -13209,9 +13249,9 @@ } rows = []; boardOptions = []; - _ref4 = Object.keys(archBoards).sort(); - for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { - boardID = _ref4[_m]; + _ref5 = Object.keys(archBoards).sort(); + for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) { + boardID = _ref5[_n]; row = $.el('tr', { className: "board-" + boardID }); @@ -13222,9 +13262,9 @@ selected: boardID === g.BOARD.ID })); o = archBoards[boardID]; - _ref5 = ['thread', 'post', 'file']; - for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) { - item = _ref5[_n]; + _ref6 = ['thread', 'post', 'file']; + for (_o = 0, _len6 = _ref6.length; _o < _len6; _o++) { + item = _ref6[_o]; $.add(row, Settings.addArchiveCell(boardID, o, item)); } rows.push(row); @@ -13313,17 +13353,22 @@ isSpoiler: true } }; - return this.nextElementSibling.innerHTML = FileInfo.format(this.value, data); + return this.nextElementSibling.innerHTML = FileInfo.h_format(this.value, data); }, favicon: function() { + var img; Favicon["switch"](); if (g.VIEW === 'thread' && Conf['Unread Favicon']) { Unread.update(); } - return this.nextElementSibling.innerHTML = "\n\n\n"; + img = this.nextElementSibling.children; + img[0].src = Favicon["default"]; + img[1].src = Favicon.unreadSFW; + img[2].src = Favicon.unreadNSFW; + return img[3].src = Favicon.unreadDead; }, sageEmoji: function() { - return this.nextElementSibling.innerHTML = ""; + return this.nextElementSibling.firstElementChild.src = "data:image/png;base64," + Emoji.sage[this.value]; }, togglecss: function() { if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = !this.checked) { @@ -13338,7 +13383,8 @@ }, keybinds: function(section) { var arr, input, inputs, items, key, tbody, tr, _ref; - section.innerHTML = "
Keybinds are disabled.
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
Press Backspace to disable a keybind.
ActionsKeybinds
"; + section.innerHTML = '
Keybinds are disabled.
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
Press Backspace to disable a keybind.
ActionsKeybinds
'; + $('.warning', section).hidden = Conf['Keybinds']; tbody = $('tbody', section); items = {}; inputs = {}; @@ -13346,8 +13392,9 @@ for (key in _ref) { arr = _ref[key]; tr = $.el('tr', { - innerHTML: "" + arr[1] + "" + innerHTML: '' }); + tr.firstElementChild.textContent = arr[1]; input = $('input', tr); input.name = key; input.spellcheck = false; @@ -13619,15 +13666,14 @@ $.event('4chanXInitFinished'); } return $.get('previousversion', null, function(_arg) { - var changelog, el, previousversion; + var el, previousversion; previousversion = _arg.previousversion; if (previousversion === g.VERSION) { return; } if (previousversion) { - changelog = 'https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md'; el = $.el('span', { - innerHTML: "4chan X has been updated to version " + g.VERSION + "." + innerHTML: '4chan X has been updated to version 1.7.38.' }); new Notice('info', el, 15); } else { @@ -13701,7 +13747,7 @@ return; } div = $.el('div', { - innerHTML: "" + errors.length + " errors occurred. [show]" + innerHTML: "" + (+errors.length) + " errors occurred. [show]" }); $.on(div.lastElementChild, 'click', function() { var _ref; diff --git a/builds/crx.crx b/builds/crx.crx index f935000c9..86d033bfd 100644 Binary files a/builds/crx.crx and b/builds/crx.crx differ diff --git a/builds/crx/manifest.json b/builds/crx/manifest.json index dcf1d0a4a..de146cffc 100755 --- a/builds/crx/manifest.json +++ b/builds/crx/manifest.json @@ -1,6 +1,6 @@ { "name": "4chan X", - "version": "1.7.37", + "version": "1.7.38", "manifest_version": 2, "description": "Cross-browser userscript for maximum lurking on 4chan.", "icons": { diff --git a/builds/crx/script.js b/builds/crx/script.js index 3a7d5cf7d..829542b1f 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript /* -* 4chan X - Version 1.7.37 - 2014-05-14 +* 4chan X - Version 1.7.38 - 2014-05-15 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE @@ -348,7 +348,7 @@ doc = d.documentElement; g = { - VERSION: '1.7.37', + VERSION: '1.7.38', NAMESPACE: '4chan X.', boards: {} }; @@ -1569,7 +1569,7 @@ this.close = __bind(this.close, this); this.add = __bind(this.add, this); this.el = $.el('div', { - innerHTML: '
' + innerHTML: '
' }); this.el.style.opacity = 0; this.setType(type); @@ -1830,30 +1830,14 @@ className: 'menu-button', innerHTML: '' }); - barFixedToggler = $.el('label', { - innerHTML: ' Fixed Header' - }); - headerToggler = $.el('label', { - innerHTML: ' Auto-hide header' - }); - scrollHeaderToggler = $.el('label', { - innerHTML: ' Auto-hide header on scroll' - }); - barPositionToggler = $.el('label', { - innerHTML: ' Bottom header' - }); - linkJustifyToggler = $.el('label', { - innerHTML: " Centered links" - }); - customNavToggler = $.el('label', { - innerHTML: ' Custom board navigation' - }); - footerToggler = $.el('label', { - innerHTML: " Hide bottom board list" - }); - shortcutToggler = $.el('label', { - innerHTML: " Shortcut Icons" - }); + barFixedToggler = UI.checkbox('Fixed Header', ' Fixed Header'); + headerToggler = UI.checkbox('Header auto-hide', ' Auto-hide header'); + scrollHeaderToggler = UI.checkbox('Header auto-hide on scroll', ' Auto-hide header on scroll'); + barPositionToggler = UI.checkbox('Bottom Header', ' Bottom header'); + linkJustifyToggler = UI.checkbox('Centered links', ' Centered links'); + customNavToggler = UI.checkbox('Custom Board Navigation', ' Custom board navigation'); + footerToggler = UI.checkbox('Bottom Board List', ' Hide bottom board list'); + shortcutToggler = UI.checkbox('Shortcut Icons', ' Shortcut Icons'); editCustomNav = $.el('a', { textContent: 'Edit custom board navigation', href: 'javascript:;' @@ -1989,7 +1973,7 @@ fourchannav = $.id('boardNavDesktop'); Header.boardList = boardList = $.el('span', { id: 'board-list', - innerHTML: "" + innerHTML: "" }); _ref = $$('a', boardList); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -2353,7 +2337,7 @@ Index = { init: function() { - var anchorEntry, input, label, modeEntry, name, refNavEntry, repliesEntry, sortEntry, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; + var anchorEntry, input, label, modeEntry, name, refNavEntry, repliesEntry, returnlink, sortEntry, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; if (g.BOARD.ID === 'f' || g.VIEW === 'catalog' || !Conf['JSON Navigation']) { return; } @@ -2373,15 +2357,15 @@ subEntries: [ { el: $.el('label', { - innerHTML: ' Paged' + innerHTML: ' Paged' }) }, { el: $.el('label', { - innerHTML: ' Infinite scrolling' + innerHTML: ' Infinite scrolling' }) }, { el: $.el('label', { - innerHTML: ' All threads' + innerHTML: ' All threads' }) } ] @@ -2401,23 +2385,23 @@ subEntries: [ { el: $.el('label', { - innerHTML: ' Bump order' + innerHTML: ' Bump order' }) }, { el: $.el('label', { - innerHTML: ' Last reply' + innerHTML: ' Last reply' }) }, { el: $.el('label', { - innerHTML: ' Creation date' + innerHTML: ' Creation date' }) }, { el: $.el('label', { - innerHTML: ' Reply count' + innerHTML: ' Reply count' }) }, { el: $.el('label', { - innerHTML: ' File count' + innerHTML: ' File count' }) } ] @@ -2431,28 +2415,21 @@ $.on(input, 'change', this.cb.sort); } repliesEntry = { - el: $.el('label', { - innerHTML: ' Show replies' - }) + el: UI.checkbox('Show Replies', ' Show replies') }; anchorEntry = { - el: $.el('label', { - innerHTML: ' Anchor hidden threads', - title: 'Move hidden threads at the end of the index.' - }) + el: UI.checkbox('Anchor Hidden Threads', ' Anchor hidden threads') }; refNavEntry = { - el: $.el('label', { - innerHTML: ' Refreshed navigation', - title: 'Refresh index when navigating through pages.' - }) + el: UI.checkbox('Refreshed Navigation', ' Refreshed navigation') }; + anchorEntry.el.title = 'Move hidden threads at the end of the index.'; + refNavEntry.el.title = 'Refresh index when navigating through pages.'; _ref2 = [repliesEntry, anchorEntry, refNavEntry]; for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { label = _ref2[_k]; input = label.el.firstChild; name = input.name; - input.checked = Conf[name]; $.on(input, 'change', $.cb.checked); switch (name) { case 'Show Replies': @@ -2477,19 +2454,21 @@ this.pagelist = $.el('div', { className: 'pagelist', hidden: true, - innerHTML: "
" + innerHTML: '
' }); this.navLinks = $.el('div', { className: 'navLinks', - innerHTML: "Return Catalog Bottom ×" + innerHTML: 'Return Catalog Bottom ×' }); this.searchInput = $('#index-search', this.navLinks); this.currentPage = this.getCurrentPage(); + returnlink = $('#returnlink a', this.navLinks); + returnlink.href = "/" + g.BOARD + "/"; $.on(d, 'scroll', Index.scroll); $.on(this.pagelist, 'click', this.cb.pageNav); $.on(this.searchInput, 'input', this.onSearchInput); $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch); - $.on($('#returnlink a', this.navLinks), 'click', Navigate.navigate); + $.on(returnlink, 'click', Navigate.navigate); $.on($('#cataloglink a', this.navLinks), 'click', function() { return window.location = "//boards.4chan.org/" + g.BOARD + "/catalog"; }); @@ -3095,9 +3074,34 @@ }; Build = { - staticPath: '//s.4cdn.org/image/', - gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', + h_staticPath: '//s.4cdn.org/image/', + h_gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', spoilerRange: {}, + h_escape: function(text) { + return (text + '').replace(/[&"'<>]/g, function(c) { + return { + '&': '&', + "'": ''', + '"': '"', + '<': '<', + '>': '>' + }[c]; + }); + }, + unescape: function(text) { + if (text == null) { + return text; + } + return text.replace(/&(amp|#039|quot|lt|gt);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>' + }[c]; + }); + }, shortFilename: function(filename, isReply) { var ext, threshold; threshold = isReply ? 30 : 40; @@ -3121,25 +3125,29 @@ postID: data.no, threadID: data.resto || data.no, boardID: boardID, - name: data.name, + name: Build.unescape(data.name), capcode: data.capcode, tripcode: data.trip, uniqueID: data.id, - email: data.email ? encodeURI(data.email.replace(/"/g, '"')) : '', - subject: data.sub, + email: Build.unescape(data.email), + subject: Build.unescape(data.sub), flagCode: data.country, - flagName: data.country_name, + flagName: Build.unescape(data.country_name), date: data.now, dateUTC: data.time, - comment: data.com, + h_comment: data.com || '', isSticky: !!data.sticky, isClosed: !!data.closed }; - if (data.ext || data.filedeleted) { + if (data.filedeleted) { o.file = { - name: data.filename + data.ext, + isDeleted: true + }; + } else if (data.ext) { + o.file = { + name: (Build.unescape(data.filename)) + data.ext, timestamp: "" + data.tim + data.ext, - url: boardID === 'f' ? ("//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext).replace(/'/g, ''') : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, + url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, height: data.h, width: data.w, MD5: data.md5, @@ -3148,7 +3156,7 @@ theight: data.tn_h, twidth: data.tn_w, isSpoiler: !!data.spoiler, - isDeleted: !!data.filedeleted + isDeleted: false }; } return Build.post(o); @@ -3159,51 +3167,82 @@ This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS). @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE */ - var a, boardID, capcode, capcodeClass, capcodeIcon, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, gifIcon, href, imgSrc, isClosed, isOP, isSticky, name, pageIcon, pageNum, postID, quote, replyLink, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref; - postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file; + var E, boardID, capcode, container, date, dateUTC, email, file, fileSize, fileThumb, flagCode, flagName, h_capcodeClass, h_capcodeIcon, h_capcodeStart, h_closed, h_comment, h_emailEnd, h_emailStart, h_file, h_fileDims, h_fileInfo, h_flag, h_gifIcon, h_imgSrc, h_pageIcon, h_postClass, h_quoteLink, h_replyLink, h_sideArrows, h_staticPath, h_sticky, h_tripcode, h_userID, href, isClosed, isOP, isSticky, name, pageNum, postID, quote, shortFilename, spoilerRange, subject, threadID, tripcode, uniqueID, _i, _len, _ref; + E = Build.h_escape; + postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, h_comment = o.h_comment, file = o.file; + name || (name = ''); + subject || (subject = ''); isOP = postID === threadID; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - tripcode = tripcode ? " " + tripcode + "" : ''; - if (email) { - emailStart = ''; - emailEnd = ''; + h_staticPath = Build.h_staticPath, h_gifIcon = Build.h_gifIcon; + if (isOP) { + h_sideArrows = ''; } else { - emailStart = ''; - emailEnd = ''; + h_sideArrows = "
>>
"; + } + h_postClass = "post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : ''); + if (tripcode) { + h_tripcode = " " + (E(tripcode)) + ""; + } else { + h_tripcode = ''; + } + if (email) { + h_emailStart = ""; + h_emailEnd = ''; + } else { + h_emailStart = ''; + h_emailEnd = ''; } switch (capcode) { case 'admin': case 'admin_highlight': - capcodeClass = " capcodeAdmin"; - capcodeStart = " ## Admin"; - capcodeIcon = (" "; + h_capcodeClass = ' capcodeAdmin'; + h_capcodeStart = ' ## Admin'; + h_capcodeIcon = " "; break; case 'mod': - capcodeClass = " capcodeMod"; - capcodeStart = " ## Mod"; - capcodeIcon = (" "; + h_capcodeClass = ' capcodeMod'; + h_capcodeStart = ' ## Mod'; + h_capcodeIcon = " "; break; case 'developer': - capcodeClass = " capcodeDeveloper"; - capcodeStart = " ## Developer"; - capcodeIcon = (" "; + h_capcodeClass = ' capcodeDeveloper'; + h_capcodeStart = ' ## Developer'; + h_capcodeIcon = " "; break; default: - capcodeClass = ''; - capcodeStart = ''; - capcodeIcon = ''; + h_capcodeClass = ''; + h_capcodeStart = ''; + h_capcodeIcon = ''; + } + if (!capcode && uniqueID) { + h_userID = " (ID: " + (E(uniqueID)) + ") "; + } else { + h_userID = ''; + } + if (!flagCode) { + h_flag = ''; + } else if (boardID === 'pol') { + h_flag = " " + (E(flagCode)) + ""; + } else { + h_flag = " "; } - userID = !capcode && uniqueID ? (" (ID: ") + ("" + uniqueID + ") ") : ''; - flag = !flagCode ? '' : boardID === 'pol' ? "  + flagCode + " : " "; if (file != null ? file.isDeleted : void 0) { - fileHTML = isOP ? ("
") + ("File deleted.") + "
" : ("
") + ("File deleted.") + "
"; + if (isOP) { + h_file = "
"; + h_file += "File deleted."; + h_file += '
'; + } else { + h_file = "
"; + h_file += "File deleted."; + h_file += '
'; + } } else if (file) { fileSize = $.bytesToString(file.size); fileThumb = file.turl; if (file.isSpoiler) { fileSize = "Spoiler Image, " + fileSize; if (!isArchived) { - fileThumb = "" + staticPath + "spoiler"; + fileThumb = "" + h_staticPath + "spoiler"; if (spoilerRange = Build.spoilerRange[boardID]) { fileThumb += ("-" + boardID) + Math.floor(1 + spoilerRange * Math.random()); } @@ -3211,38 +3250,63 @@ file.twidth = file.theight = 100; } } - imgSrc = boardID === 'f' ? '' : ("") + ("" + fileSize + "") + ""; - a = $.el('a', { - innerHTML: file.name - }); - filename = a.textContent.replace(/%22/g, '"'); - a.textContent = Build.shortFilename(filename); - shortFilename = a.innerHTML; - a.textContent = filename; - filename = a.innerHTML.replace(/'/g, '''); - fileDims = file.name.slice(-3) === 'pdf' ? 'PDF' : "" + file.width + "x" + file.height; - fileInfo = ("
File: " + file.timestamp + "") + ("-(" + fileSize + ", " + fileDims + (file.isSpoiler ? '' : ", " + shortFilename + "")) + ")
"; - fileHTML = "
" + fileInfo + imgSrc + "
"; + shortFilename = Build.shortFilename(file.name); + if (boardID === 'f') { + h_imgSrc = ''; + } else { + h_imgSrc = ""; + h_imgSrc += "" + (E(fileSize)) + ""; + h_imgSrc += ''; + } + if (file.url.slice(-4) === '.pdf') { + h_fileDims = 'PDF'; + } else { + h_fileDims = "" + (+file.width) + "x" + (+file.height); + } + h_fileInfo = "
" + (E(file.timestamp)) + ""; + h_fileInfo += "-(" + (E(fileSize)) + ", " + h_fileDims; + if (!file.isSpoiler) { + h_fileInfo += ", " + (E(shortFilename)) + ""; + } + h_fileInfo += ')
'; + h_file = "
" + h_fileInfo + h_imgSrc + "
"; } else { - fileHTML = ''; + h_file = ''; } - sticky = isSticky ? " Sticky" : ''; - closed = isClosed ? " Closed" : ''; - if (isOP && g.VIEW === 'index' && Conf['JSON Navigation']) { - pageNum = Math.floor(Index.liveThreadIDs.indexOf(postID) / Index.threadsNumPerPage + 1); - pageIcon = " [" + pageNum + "]"; + if (g.VIEW === 'thread' && g.THREADID === +threadID) { + h_quoteLink = "javascript:quote(" + (+postID) + ")"; } else { - pageIcon = ''; + h_quoteLink = "/" + (E(boardID)) + "/thread/" + (+threadID) + "\#q" + (+postID); + } + if (isSticky) { + h_sticky = " Sticky"; + } else { + h_sticky = ''; + } + if (isClosed) { + h_closed = " Closed"; + } else { + h_closed = ''; + } + if (isOP && g.VIEW === 'index' && Conf['JSON Navigation']) { + pageNum = Math.floor(Index.liveThreadIDs.indexOf(postID) / Index.threadsNumPerPage) + 1; + h_pageIcon = " [" + (+pageNum) + "]"; + } else { + h_pageIcon = ''; } if (isOP && g.VIEW === 'index') { - replyLink = "   [Reply]"; + h_replyLink = "   [Reply]"; } else { - replyLink = ''; + h_replyLink = ''; } container = $.el('div', { id: "pc" + postID, className: "postContainer " + (isOP ? 'op' : 'reply') + "Container", - innerHTML: "" + (isOP ? '' : "
>>
") + "
" + (isOP ? fileHTML : '') + "" + (isOP ? '' : fileHTML) + "
" + (comment || '') + "
" + ' ' + "
" + innerHTML: "" + h_sideArrows + "
" + (isOP ? h_file : '') + "" + (isOP ? '' : h_file) + "
" + h_comment + "
" + ' ' + "
" }); _ref = $$('.quotelink', container); for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -3484,7 +3548,7 @@ return Get.insert(post, root, context); }, archivedPost: function(req, boardID, postID, root, context) { - var board, bq, comment, data, o, post, thread, threadID, _ref; + var board, data, h_comment, o, post, thread, threadID, _ref; if (post = g.posts["" + boardID + "." + postID]) { Get.insert(post, root, context); return; @@ -3495,17 +3559,15 @@ root.textContent = data.error; return; } - bq = $.el('blockquote', { - textContent: data.comment - }); - bq.innerHTML = bq.innerHTML.replace(/\n|\[\/?[a-z]+(:lit)?\]/g, Get.parseMarkup); - comment = bq.innerHTML.replace(/(^|>)(>[^<$]*)(<|$)/g, '$1$2$3').replace(/((>){2}(>\/[a-z\d]+\/)?\d+)/g, '$1'); + h_comment = Build.h_escape(data.comment || ''); + h_comment = h_comment.replace(/\n|\[\/?[a-z]+(:lit)?\]/g, Get.parseMarkup); + h_comment = h_comment.replace(/(^|>)(>[^<$]*)(<|$)/g, '$1$2$3').replace(/((>){2}(>\/[a-z\d]+\/)?\d+)/g, '$1'); threadID = +data.thread_num; o = { postID: postID, threadID: threadID, boardID: boardID, - name: data.name_processed, + name: data.name, capcode: (function() { switch (data.capcode) { case 'M': @@ -3518,17 +3580,17 @@ })(), tripcode: data.trip, uniqueID: data.poster_hash, - email: data.email ? encodeURI(data.email) : '', - subject: data.title_processed, + email: data.email || '', + subject: data.title, flagCode: data.poster_country, - flagName: data.poster_country_name_processed, + flagName: data.poster_country_name, date: data.fourchan_date, dateUTC: data.timestamp, - comment: comment + h_comment: h_comment }; if ((_ref = data.media) != null ? _ref.media_filename : void 0) { o.file = { - name: data.media.media_filename_processed, + name: data.media.media_filename, timestamp: data.media.media_orig, url: data.media.media_link || data.media.remote_media_link, height: data.media.media_h, @@ -3556,7 +3618,7 @@ '[/b]': '', '[spoiler]': '', '[/spoiler]': '', - '[code]': '
',
+        '[code]': '
',
         '[/code]': '
', '[moot]': '
', '[/moot]': '
', @@ -3567,7 +3629,7 @@ }; UI = (function() { - var Menu, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove; + var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove; dialog = function(id, position, properties) { var child, el, move, _i, _len, _ref; el = $.el('div', { @@ -3980,10 +4042,25 @@ return this.cb.call(this); } }; + checkbox = function(name, text, checked) { + var input, label; + if (checked == null) { + checked = Conf[name]; + } + label = $.el('label'); + input = $.el('input', { + type: 'checkbox', + name: name, + checked: checked + }); + $.add(label, [input, $.tn(text)]); + return label; + }; return { dialog: dialog, Menu: Menu, - hover: hoverstart + hover: hoverstart, + checkbox: checkbox }; })(); @@ -4340,15 +4417,9 @@ href: 'javascript:;' }); $.on(apply, 'click', PostHiding.menu.hide); - thisPost = $.el('label', { - innerHTML: ' This post' - }); - replies = $.el('label', { - innerHTML: " Hide replies" - }); - makeStub = $.el('label', { - innerHTML: " Make stub" - }); + thisPost = UI.checkbox('thisPost', ' This post', true); + replies = UI.checkbox('replies', ' Hide replies', Conf['Recursive Hiding']); + makeStub = UI.checkbox('makeStub', ' Make stub', Conf['Stubs']); $.event('AddMenuEntry', { type: 'post', el: div, @@ -4381,12 +4452,8 @@ href: 'javascript:;' }); $.on(apply, 'click', PostHiding.menu.show); - thisPost = $.el('label', { - innerHTML: ' This post' - }); - replies = $.el('label', { - innerHTML: " Show replies" - }); + thisPost = UI.checkbox('thisPost', ' This post', false); + replies = UI.checkbox('replies', ' Show replies', false); hideStubLink = $.el('a', { textContent: 'Hide stub', href: 'javascript:;' @@ -4761,9 +4828,7 @@ href: 'javascript:;' }); $.on(apply, 'click', ThreadHiding.menu.hide); - makeStub = $.el('label', { - innerHTML: " Make stub" - }); + makeStub = UI.checkbox('Stubs', ' Make stub'); $.event('AddMenuEntry', { type: 'post', el: div, @@ -5341,7 +5406,7 @@ } this.enabled = true; this.controls = $.el('span', { - innerHTML: '' + innerHTML: '' }); input = $('input', this.controls); $.on(input, 'change', this.toggle); @@ -5745,7 +5810,7 @@ return; } link = $.el('h1', { - innerHTML: "" + (g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread') + "", + innerHTML: "" + (g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread') + "", className: "qr-link-container" }); QR.link = link.firstElementChild; @@ -6250,7 +6315,7 @@ var dialog, elm, event, i, items, name, node, nodes, prop, rules, save, setNode, _, _i, _len, _ref, _ref1, _ref2; QR.nodes = nodes = { el: dialog = UI.dialog('qr', 'top:0;right:0;', { - innerHTML: "
×
No selected file
" + innerHTML: '
×
No selected file
' }) }; setNode = function(name, query) { @@ -6489,7 +6554,7 @@ QR.cooldown.auto = false; QR.status(); return QR.error($.el('span', { - innerHTML: '4chan X encountered an error while posting. \n[Banned?] [More info]' + innerHTML: '4chan X encountered an error while posting. \n[Banned?] [More info]' })); } }; @@ -6514,16 +6579,17 @@ return QR.status(); }, response: function() { - var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; + var URL, ban, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; req = QR.req; delete QR.req; post = QR.posts[0]; post.unlock(); resDoc = req.response; if (ban = $('.banType', resDoc)) { - board = $('.board', resDoc).innerHTML; - err = $.el('span', { - innerHTML: ban.textContent.toLowerCase() === 'banned' ? "You are banned on " + board + "! ;_;
\nClick here to see the reason." : "You were issued a warning on " + board + " as " + ($('.nameBlock', resDoc).innerHTML) + ".
\nReason: " + ($('.reason', resDoc).innerHTML) + err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? { + innerHTML: "You are banned on " + ($('.board', resDoc).innerHTML) + "! ;_;
Click here to see the reason." + } : { + innerHTML: "You were issued a warning on " + ($('.board', resDoc).innerHTML) + " as " + ($('.nameBlock', resDoc).innerHTML) + ".
Reason: " + ($('.reason', resDoc).innerHTML) }); } else if (err = resDoc.getElementById('errmsg')) { if ((_ref = $('a', err)) != null) { @@ -7051,9 +7117,9 @@ } }, getPassword: function() { - var input, m; + var input, m, _ref; if (!QR.persona.pwd) { - QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : $.id('delPassword').value; + QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : ((_ref = $.id('delPassword')) != null ? _ref.value : void 0) || ''; } return QR.persona.pwd; }, @@ -7087,7 +7153,7 @@ className: 'qr-preview', draggable: true, href: 'javascript:;', - innerHTML: '' + innerHTML: '' }); this.nodes = { el: el, @@ -7469,10 +7535,8 @@ continue; } lc = type.toLowerCase(); - el = $.el('label', { - innerHTML: " " + type + " Tyme", - title: "" + type + " Tyme" - }); + el = UI.checkbox(lc, " " + type + " Tyme", false); + el.title = "" + type + " Tyme"; FappeTyme[lc] = input = el.firstElementChild; $.on(input, 'change', FappeTyme.cb.toggle.bind(input)); $.event('AddMenuEntry', { @@ -7546,7 +7610,7 @@ nodes = Gallery.nodes = {}; nodes.el = dialog = $.el('div', { id: 'a-gallery', - innerHTML: '
\n \n \n ×\n \n \n / \n
\n
\n \n
\n
\n
\n
' + innerHTML: '
× /
' }); _ref = { frame: '.gal-image', @@ -7820,14 +7884,11 @@ }, createSubEntry: function(name) { var input, label; - label = $.el('label', { - innerHTML: " " + name - }); + label = UI.checkbox(name, " " + name); input = label.firstElementChild; if (name === 'Fit Width' || name === 'Fit Height' || name === 'Hide Thumbnails') { $.on(input, 'change', Gallery.cb.setFitness); } - input.checked = Conf[name]; $.event('change', null, input); $.on(input, 'change', $.cb.checked); return { @@ -8157,15 +8218,12 @@ }, createSubEntry: function(name, desc) { var input, label; - label = $.el('label', { - innerHTML: " " + name, - title: desc - }); + label = UI.checkbox(name, " " + name); + label.title = desc; input = label.firstElementChild; if (name === 'Fit width' || name === 'Fit height') { $.on(input, 'change', ImageExpand.cb.setFitness); } - input.checked = Conf[name]; $.event('change', null, input); $.on(input, 'change', $.cb.checked); return { @@ -8309,7 +8367,7 @@ return; } prefetch = $.el('label', { - innerHTML: ' Prefetch Images' + innerHTML: ' Prefetch Images' }); this.el = prefetch.firstElementChild; $.on(this.el, 'change', this.toggle); @@ -9433,7 +9491,7 @@ } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { - innerHTML: "0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : ''), + innerHTML: "0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : ''), id: 'thread-stats', title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "") }); @@ -9442,7 +9500,7 @@ }); } else { this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', { - innerHTML: "
0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : '') + "
" + innerHTML: "
0 / 0" + (Conf['Page Count in Stats'] ? ' / 0' : '') + "
" }); $.ready((function(_this) { return function() { @@ -9548,13 +9606,13 @@ ThreadUpdater = { init: function() { - var checked, conf, el, input, name, sc, subEntries, _ref; + var conf, el, input, name, sc, subEntries, _ref; if (g.VIEW !== 'thread' || !Conf['Thread Updater']) { return; } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { - innerHTML: '', + innerHTML: '', id: 'updater' }); $.ready(function() { @@ -9562,7 +9620,7 @@ }); } else { this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', { - innerHTML: '
' + innerHTML: '
' }); $.addClass(doc, 'float'); $.ready((function(_this) { @@ -9582,11 +9640,8 @@ _ref = Config.updater.checkbox; for (name in _ref) { conf = _ref[name]; - checked = Conf[name] ? 'checked' : ''; - el = $.el('label', { - title: "" + conf[1], - innerHTML: " " + name - }); + el = UI.checkbox(name, " " + name); + el.title = conf[1]; input = el.firstElementChild; $.on(input, 'change', $.cb.checked); if (input.name === 'Scroll BG') { @@ -9600,7 +9655,7 @@ }); } this.settings = $.el('span', { - innerHTML: 'Interval' + innerHTML: 'Interval' }); $.on(this.settings, 'click', this.intervalShortcut); subEntries.push({ @@ -9822,7 +9877,7 @@ } }, update: function() { - var url, _ref; + var _ref; if (!navigator.onLine) { return; } @@ -9835,10 +9890,8 @@ if ((_ref = ThreadUpdater.req) != null) { _ref.abort(); } - url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json"; - return ThreadUpdater.req = $.ajax(url, { - onloadend: ThreadUpdater.cb.load - }, { + return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { + onloadend: ThreadUpdater.cb.load, whenModified: true }); }, @@ -9966,7 +10019,7 @@ }); this.db = new DataBoard('watchedThreads', this.refresh, true); this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', { - innerHTML: "
Thread Watcher ×
" + innerHTML: '
Thread Watcher ×
' }); this.status = $('#watcher-status', this.dialog); this.list = this.dialog.lastElementChild; @@ -10434,13 +10487,10 @@ var entry, input; entry = { type: 'thread watcher', - el: $.el('label', { - innerHTML: " " + name, - title: desc - }) + el: UI.checkbox(name, " " + name) }; + entry.el.title = desc; input = entry.el.firstElementChild; - input.checked = Conf[name]; $.on(input, 'change', $.cb.checked); if (name === 'Current Board') { $.on(input, 'change', ThreadWatcher.refresh); @@ -10865,7 +10915,7 @@ $.event('AddMenuEntry', entry); $.on(entry.el, 'click', PSAHiding.toggle); PSAHiding.btn = btn = $.el('span', { - innerHTML: '[Dismiss]', + innerHTML: '[Dismiss]', title: 'Mark announcement as read and hide.', className: 'hide-announcement', href: 'javascript:;' @@ -11010,11 +11060,8 @@ if (!Conf['Catalog Links']) { return; } - CatalogLinks.el = el = $.el('label', { - id: 'toggleCatalog', - href: 'javascript:;', - innerHTML: " Catalog Links" - }); + CatalogLinks.el = el = UI.checkbox('Header catalog links', ' Catalog Links'); + el.id = 'toggleCatalog'; input = $('input', el); $.on(input, 'change', this.toggle); $.sync('Header catalog links', CatalogLinks.set); @@ -11439,65 +11486,42 @@ if (!this.file || this.isClone) { return; } - return this.file.text.innerHTML = "" + (FileInfo.format(Conf['fileInfo'], this)) + ""; + return this.file.text.innerHTML = "" + (FileInfo.h_format(Conf['fileInfo'], this)) + ""; }, - format: function(formatString, post) { + h_format: function(formatString, post) { return formatString.replace(/%([A-Za-z])|[^%]+/g, function(s, c) { - if (c in FileInfo.formatters) { - return FileInfo.formatters[c].call(post); + if (c in FileInfo.h_formatters) { + return FileInfo.h_formatters[c].call(post); } else { - return FileInfo.escape(s); + return Build.h_escape(s); } }); }, - convertUnit: function(size, unit) { - var i; - if (unit === 'B') { - return "" + (size.toFixed()) + " Bytes"; - } - i = 1 + ['KB', 'MB'].indexOf(unit); - while (i--) { - size /= 1024; - } - size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); - return "" + size + " " + unit; - }, - escape: function(name) { - return name.replace(/[&"'<>]/g, function(c) { - return { - '&': '&', - "'": ''', - '"': '"', - '<': '<', - '>': '>' - }[c]; - }); - }, - formatters: { + h_formatters: { t: function() { - return FileInfo.escape(this.file.URL.match(/\d+\..+$/)[0]); + return Build.h_escape(this.file.URL.match(/\d+\..+$/)[0]); }, T: function() { - return "" + (FileInfo.formatters.t.call(this)) + ""; + return "" + (FileInfo.h_formatters.t.call(this)) + ""; }, l: function() { - return "" + (FileInfo.formatters.n.call(this)) + ""; + return "" + (FileInfo.h_formatters.n.call(this)) + ""; }, L: function() { - return "" + (FileInfo.formatters.N.call(this)) + ""; + return "" + (FileInfo.h_formatters.N.call(this)) + ""; }, n: function() { var fullname, shortname; fullname = this.file.name; shortname = Build.shortFilename(this.file.name, this.isReply); if (fullname === shortname) { - return FileInfo.escape(fullname); + return Build.h_escape(fullname); } else { - return "" + (FileInfo.escape(shortname)) + "" + (FileInfo.escape(fullname)) + ""; + return "" + (Build.h_escape(shortname)) + "" + (Build.h_escape(fullname)) + ""; } }, N: function() { - return FileInfo.escape(this.file.name); + return Build.h_escape(this.file.name); }, p: function() { if (this.file.isSpoiler) { @@ -11507,19 +11531,19 @@ } }, s: function() { - return FileInfo.escape(this.file.size); + return Build.h_escape(this.file.size); }, B: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); + return "" + (+this.file.sizeInBytes) + " Bytes"; }, K: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); + return "" + (+Math.round(this.file.sizeInBytes / 1024)) + " KB"; }, M: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); + return "" + (+Math.round(this.file.sizeInBytes / 1048576 * 100) / 100) + " MB"; }, r: function() { - return FileInfo.escape(this.file.dimensions || 'PDF'); + return Build.h_escape(this.file.dimensions || 'PDF'); } } }; @@ -11532,14 +11556,14 @@ } board = g.BOARD.ID; if (board === 'g') { - $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); + $.globalEval('window.addEventListener(\'prettyprint\', function(e) {\n window.dispatchEvent(new CustomEvent(\'prettyprint:cb\', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);'); Post.callbacks.push({ name: 'Parse /g/ code', cb: this.code }); } if (board === 'sci') { - $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); + $.globalEval('window.addEventListener(\'jsmath\', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push(\'ProcessBeforeShowing\', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);'); return Post.callbacks.push({ name: 'Parse /sci/ math', cb: this.math @@ -12517,7 +12541,7 @@ } } if (Conf['Quick Reply']) { - QR.link.textContent = g.VIEW === 'thread' ? 'Reply to Thread' : 'Start a Thread'; + QR.link.textContent = view === 'thread' ? 'Reply to Thread' : 'Start a Thread'; QR.status(); _ref = QR.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -12584,11 +12608,11 @@ Favicon.update(); findStyle = function(type, base) { var style; - style = d.cookie.match(new RegExp("\b" + type + "\_style\=([^;]+);\b")); + style = d.cookie.match(new RegExp("\\b" + type + "_style=([^;]+)")); return ["" + type + "_style", (style ? style[1] : base)]; }; style = sfw ? findStyle('ws', 'Yotsuba B New') : findStyle('nws', 'Yotsuba New'); - $.globalEval("var style_group = '" + style[0] + "'"); + $.globalEval("var style_group = " + (JSON.stringify(style[0]))); $('link[title=switch]', d.head).href = $("link[title='" + style[1] + "']", d.head).href; return Main.setClass(); }, @@ -12807,19 +12831,18 @@ return localStorage.setItem('4chan-settings', JSON.stringify(settings)); }, open: function(openSection) { - var dialog, html, link, links, overlay, section, sectionToOpen, _i, _len, _ref; + var dialog, link, links, overlay, section, sectionToOpen, _i, _len, _ref; if (Settings.dialog) { return; } $.event('CloseMenu'); - html = "
"; Settings.overlay = overlay = $.el('div', { id: 'overlay' }); Settings.dialog = dialog = $.el('div', { id: 'fourchanx-settings', className: 'dialog', - innerHTML: html + innerHTML: '
' }); $.on($('.export', Settings.dialog), 'click', Settings["export"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]); @@ -12891,14 +12914,19 @@ for (key in _ref) { obj = _ref[key]; fs = $.el('fieldset', { - innerHTML: "" + key + "" + innerHTML: '' }); + fs.firstElementChild.textContent = key; for (key in obj) { arr = obj[key]; description = arr[1]; - div = $.el('div', { - innerHTML: ": " + description + "" - }); + div = $.el('div'); + $.add(div, [ + UI.checkbox(key, key, false), $.el('span', { + "class": 'description', + textContent: ": " + description + }) + ]); input = $('input', div); $.on(input, 'change', $.cb.checked); items[key] = Conf[key]; @@ -12915,7 +12943,7 @@ } }); div = $.el('div', { - innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." + innerHTML: ': Clear manually-hidden threads and posts on all boards. Reload the page to apply.' }); button = $('button', div); $.get({ @@ -13109,7 +13137,7 @@ }, filter: function(section) { var select; - section.innerHTML = "
"; + section.innerHTML = '
'; select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); @@ -13131,11 +13159,13 @@ $.add(div, ta); return; } - return div.innerHTML = "
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
MD5 filtering uses exact string matching, not regular expressions.

    You can use these settings with each regular expression, separate them with semicolons:
  • Per boards, separate them with commas. It is global if not specified.
    For example: boards:a,jp;.
  • Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).
    For example: op:only;, op:no; or op:yes;.
  • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
    For example: stub:yes; or stub:no;.
  • Highlight instead of hiding. You can specify a class name to use with a userstyle.
    For example: highlight; or highlight:wallpaper;.
  • Highlighted OPs will have their threads put on top of the board index by default.
    For example: top:yes; or top:no;.
"; + div.innerHTML = '
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
MD5 filtering uses exact string matching, not regular expressions.

    You can use these settings with each regular expression, separate them with semicolons:
  • Per boards, separate them with commas. It is global if not specified.
    For example: boards:a,jp;.
  • Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).
    For example: op:only;, op:no; or op:yes;.
  • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
    For example: stub:yes; or stub:no;.
  • Highlight instead of hiding. You can specify a class name to use with a userstyle.
    For example: highlight; or highlight:wallpaper;.
  • Highlighted OPs will have their threads put on top of the board index by default.
    For example: top:yes; or top:no;.
'; + return $('.warning', div).hidden = Conf['Filter']; }, sauce: function(section) { var ta; - section.innerHTML = "
Sauce is disabled.
Lines starting with a # will be ignored.
You can specify a display text by appending ;text:[text] to the URL.
    These parameters will be replaced by their corresponding values:
  • %TURL: Thumbnail URL.
  • %URL: Full image URL.
  • %MD5: MD5 hash.
  • %name: Original file name.
  • %board: Current board.
"; + section.innerHTML = '
Sauce is disabled.
Lines starting with a # will be ignored.
You can specify a display text by appending ;text:[text] to the URL.
    These parameters will be replaced by their corresponding values:
  • %TURL: Thumbnail URL.
  • %URL: Full image URL.
  • %MD5: MD5 hash.
  • %name: Original file name.
  • %board: Current board.
'; + $('.warning', section).hidden = Conf['Sauce']; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { return ta.value = item['sauces']; @@ -13143,13 +13173,18 @@ return $.on(ta, 'change', $.cb.value); }, advanced: function(section) { - var archBoards, boardID, boardOptions, boardSelect, boards, event, files, i, input, inputs, item, items, name, o, row, rows, software, ta, table, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; - section.innerHTML = "
Archiver
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection
Disabled selections indicate that only one archive is available for that board and redirection type.
Custom Board Navigation
New lines will be converted into spaces.

In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: g
Title link: g-title
Board link (Replace with title when on that board): g-replace
Full text link: g-full
Custom text link: g-text:\"Install Gentoo\"
Index-only link: g-index
Catalog-only link: g-catalog
External link: external-text:\"Google\",\"http://www.google.com\"
Combinations are possible: g-index-text:\"Technology Index\"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
if you are on /g/.
Time Formatting is disabled.
:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas

One item per line.
Items will be added in the relevant input's auto-completion list.
Password items will always be used, since there is no password input.
Lines starting with a # will be ignored.

    You can use these settings with each item, separate them with semicolons:
  • Possible items are: name, email, subject and password.
  • Wrap values of items with quotes, like this: email:\"sage\".
  • Force values as defaults with the always keyword, for example: email:\"sage\";always.
  • Select specific boards for an item, separated with commas, for example: email:\"sage\";boards:jp;always.
Unread Favicon is disabled.
Emoji is disabled.
Sage Icon:
Position:
Thread Updater is disabled.
Interval:
"; + var archBoards, boardID, boardOptions, boardSelect, boards, customCSS, event, files, i, input, inputs, interval, item, items, name, o, row, rows, software, ta, table, warning, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; + section.innerHTML = '
Archiver
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection
Disabled selections indicate that only one archive is available for that board and redirection type.
Custom Board Navigation
New lines will be converted into spaces.

In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: g
Title link: g-title
Board link (Replace with title when on that board): g-replace
Full text link: g-full
Custom text link: g-text:"Install Gentoo"
Index-only link: g-index
Catalog-only link: g-catalog
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
if you are on /g/.
Time Formatting is disabled.
:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays \'PDF\' for PDF files)
Quick Reply Personas

One item per line.
Items will be added in the relevant input\'s auto-completion list.
Password items will always be used, since there is no password input.
Lines starting with a # will be ignored.

    You can use these settings with each item, separate them with semicolons:
  • Possible items are: name, email, subject and password.
  • Wrap values of items with quotes, like this: email:"sage".
  • Force values as defaults with the always keyword, for example: email:"sage";always.
  • Select specific boards for an item, separated with commas, for example: email:"sage";boards:jp;always.
Unread Favicon is disabled.
Emoji is disabled.
Sage Icon:
Position:
Thread Updater is disabled.
Interval:
'; + _ref = $$('.warning', section); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + warning = _ref[_i]; + warning.hidden = Conf[warning.dataset.feature]; + } items = {}; inputs = {}; - _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - name = _ref[_i]; + _ref1 = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + name = _ref1[_j]; input = $("[name=" + name + "]", section); items[name] = Conf[name]; inputs[name] = input; @@ -13177,15 +13212,20 @@ Settings[key].call(input); } }); - $.on($('input[name=Interval]', section), 'change', ThreadUpdater.cb.interval); - $.on($('input[name="Custom CSS"]', section), 'change', Settings.togglecss); + interval = $('input[name="Interval"]', section); + customCSS = $('input[name="Custom CSS"]', section); + interval.value = Conf['Interval']; + customCSS.checked = Conf['Custom CSS']; + inputs['usercss'].disabled = !Conf['Custom CSS']; + $.on(interval, 'change', ThreadUpdater.cb.interval); + $.on(customCSS, 'change', Settings.togglecss); $.on($.id('apply-css'), 'click', Settings.usercss); archBoards = {}; - _ref1 = Redirect.archives; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - _ref2 = _ref1[_j], name = _ref2.name, boards = _ref2.boards, files = _ref2.files, software = _ref2.software, withCredentials = _ref2.withCredentials; - for (_k = 0, _len2 = boards.length; _k < _len2; _k++) { - boardID = boards[_k]; + _ref2 = Redirect.archives; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + _ref3 = _ref2[_k], name = _ref3.name, boards = _ref3.boards, files = _ref3.files, software = _ref3.software, withCredentials = _ref3.withCredentials; + for (_l = 0, _len3 = boards.length; _l < _len3; _l++) { + boardID = boards[_l]; o = archBoards[boardID] || (archBoards[boardID] = { thread: [[], []], post: [[], []], @@ -13203,9 +13243,9 @@ } for (boardID in archBoards) { o = archBoards[boardID]; - _ref3 = ['thread', 'post', 'file']; - for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { - item = _ref3[_l]; + _ref4 = ['thread', 'post', 'file']; + for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { + item = _ref4[_m]; if (o[item][0].length === 0 && o[item][1].length !== 0) { o[item][0].push('disabled'); } @@ -13214,9 +13254,9 @@ } rows = []; boardOptions = []; - _ref4 = Object.keys(archBoards).sort(); - for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { - boardID = _ref4[_m]; + _ref5 = Object.keys(archBoards).sort(); + for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) { + boardID = _ref5[_n]; row = $.el('tr', { className: "board-" + boardID }); @@ -13227,9 +13267,9 @@ selected: boardID === g.BOARD.ID })); o = archBoards[boardID]; - _ref5 = ['thread', 'post', 'file']; - for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) { - item = _ref5[_n]; + _ref6 = ['thread', 'post', 'file']; + for (_o = 0, _len6 = _ref6.length; _o < _len6; _o++) { + item = _ref6[_o]; $.add(row, Settings.addArchiveCell(boardID, o, item)); } rows.push(row); @@ -13318,17 +13358,22 @@ isSpoiler: true } }; - return this.nextElementSibling.innerHTML = FileInfo.format(this.value, data); + return this.nextElementSibling.innerHTML = FileInfo.h_format(this.value, data); }, favicon: function() { + var img; Favicon["switch"](); if (g.VIEW === 'thread' && Conf['Unread Favicon']) { Unread.update(); } - return this.nextElementSibling.innerHTML = "\n\n\n"; + img = this.nextElementSibling.children; + img[0].src = Favicon["default"]; + img[1].src = Favicon.unreadSFW; + img[2].src = Favicon.unreadNSFW; + return img[3].src = Favicon.unreadDead; }, sageEmoji: function() { - return this.nextElementSibling.innerHTML = ""; + return this.nextElementSibling.firstElementChild.src = "data:image/png;base64," + Emoji.sage[this.value]; }, togglecss: function() { if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = !this.checked) { @@ -13343,7 +13388,8 @@ }, keybinds: function(section) { var arr, input, inputs, items, key, tbody, tr, _ref; - section.innerHTML = "
Keybinds are disabled.
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
Press Backspace to disable a keybind.
ActionsKeybinds
"; + section.innerHTML = '
Keybinds are disabled.
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
Press Backspace to disable a keybind.
ActionsKeybinds
'; + $('.warning', section).hidden = Conf['Keybinds']; tbody = $('tbody', section); items = {}; inputs = {}; @@ -13351,8 +13397,9 @@ for (key in _ref) { arr = _ref[key]; tr = $.el('tr', { - innerHTML: "" + arr[1] + "" + innerHTML: '' }); + tr.firstElementChild.textContent = arr[1]; input = $('input', tr); input.name = key; input.spellcheck = false; @@ -13614,15 +13661,14 @@ $.event('4chanXInitFinished'); } return $.get('previousversion', null, function(_arg) { - var changelog, el, previousversion; + var el, previousversion; previousversion = _arg.previousversion; if (previousversion === g.VERSION) { return; } if (previousversion) { - changelog = 'https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md'; el = $.el('span', { - innerHTML: "4chan X has been updated to version " + g.VERSION + "." + innerHTML: '4chan X has been updated to version 1.7.38.' }); new Notice('info', el, 15); } else { @@ -13696,7 +13742,7 @@ return; } div = $.el('div', { - innerHTML: "" + errors.length + " errors occurred. [show]" + innerHTML: "" + (+errors.length) + " errors occurred. [show]" }); $.on(div.lastElementChild, 'click', function() { var _ref; diff --git a/builds/updates.xml b/builds/updates.xml index 452cf7b03..25bbe97bc 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/package.json b/package.json index 4669a9db4..b0f3e89bd 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "4chan-X", - "version": "1.7.37", + "version": "1.7.38", "description": "Cross-browser userscript for maximum lurking on 4chan.", "meta": { "name": "4chan X",