diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 6e57cf1fa..5613e6c61 100755 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -29,7 +29,6 @@ module.exports = (grunt) -> return '' unless grunt.config('pkg').tests_enabled "throw new Error 'Assertion failed: ' + `#{JSON.stringify statement}` unless #{statement}" - # Project configuration. grunt.initConfig pkg: grunt.file.readJSON 'package.json' diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index dc9c46552..75fcd53ae 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -3349,6 +3349,7 @@ root: root, post: post, info: info, + nameBlock: $('.nameBlock', info), comment: $('.postMessage', post), links: [], quotelinks: [], @@ -3360,6 +3361,7 @@ this.thread.isClosed = !!$('.closedIcon', info); } this.info = {}; + this.info.nameBlock = Conf['Anonymize'] ? 'Anonymous' : this.nodes.nameBlock.textContent.trim(); if (subject = $('.subject', info)) { this.nodes.subject = subject; this.info.subject = subject.textContent; @@ -4822,16 +4824,14 @@ }); this.pagelist = $.el('div', { className: 'pagelist', - hidden: true, - innerHTML: { - innerHTML: "
\r\r<\r\r
\r
\r
\r\r>\r\r
" - } + hidden: true + }, { + innerHTML: "
\r\r<\r\r
\r
\r
\r\r>\r\r
" }); this.navLinks = $.el('div', { - className: 'navLinks', - innerHTML: { - innerHTML: "\r\r\\uf05c\r \r\r\r\r\r\r\r\r" - } + className: 'navLinks' + }, { + innerHTML: "\r\r\\uf05c\r \r\r\r\r\r\r\r\r" }); this.timeEl = $('time#index-last-refresh', this.navLinks); this.searchInput = $('#index-search', this.navLinks); @@ -5836,9 +5836,23 @@ staticPath: '//s.4cdn.org/image/', gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', spoilerRange: {}, - shortFilename: function(filename, isReply) { + unescape: function(text) { + if (text == null) { + return text; + } + return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>' + }[c]; + }); + }, + shortFilename: function(filename) { var ext, threshold; - threshold = isReply ? 30 : 40; + threshold = 30; ext = filename.match(/\.[^.]+$/)[0]; if (filename.length - ext.length > threshold) { return "" + filename.slice(0, threshold - 5) + "(...)" + ext; @@ -5847,12 +5861,22 @@ } }, thumbRotate: (function() { - var n; - n = 0; + var t; + t = 0; return function() { - return n = (n + 1) % 2; + return t = (t ? 0 : 1); }; })(), + sameThread: function(boardID, threadID) { + return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; + }, + postURL: function(boardID, threadID, postID) { + if (Build.sameThread(boardID, threadID)) { + return "#p" + postID; + } else { + return Build.path(boardID, threadID, postID); + } + }, path: function(boardID, threadID, postID, fragment) { var path; path = "/" + boardID + "/thread/" + threadID; @@ -5870,22 +5894,30 @@ 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, + comment: { + innerHTML: data.com || '' + }, isSticky: !!data.sticky, - isClosed: !!data.closed + isClosed: !!data.closed, + isArchived: !!data.archived }; - if (data.ext || data.filedeleted) { + if (data.filedeleted) { o.file = { + isDeleted: true + }; + } else if (data.ext) { + o.file = { + name: (Build.unescape(data.filename)) + data.ext, name: data.filename + data.ext, timestamp: "" + data.tim + data.ext, url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, @@ -5897,107 +5929,177 @@ theight: data.tn_h, twidth: data.tn_w, isSpoiler: !!data.spoiler, - isDeleted: !!data.filedeleted + isDeleted: false, + tag: data.tag }; } return Build.post(o); }, - post: function(o, isArchived) { + post: function(o) { /* 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 boardID, capcode, capcodeClass, capcodeIcon, capcodeStart, comment, container, date, dateUTC, desktop2, email, emailField, emailProcessed, file, fileBlock, fileCont, fileDims, fileLink, fileSize, fileText, fileThumb, flag, flagCode, flagName, gifIcon, highlightPost, href, icons, isOP, match, message, name, nameBlock, nameClass, postID, postInfo, postLink, quote, quoteLink, replyLink, shortFilename, spoilerRange, staticPath, subject, subjectField, threadID, tripcode, tripcodeField, type, typeLC, uniqueID, userID, wholePost, _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, comment = o.comment, file = o.file; + name || (name = ''); + subject || (subject = ''); isOP = postID === threadID; staticPath = Build.staticPath, gifIcon = Build.gifIcon; - tripcode = tripcode ? " " + tripcode + "" : ''; - if (email) { - emailStart = ''; - emailEnd = ''; - } else { - emailStart = ''; - emailEnd = ''; - } + + /* Name Block */ switch (capcode) { case 'admin': case 'admin_highlight': - capcodeClass = " capcodeAdmin"; - capcodeStart = " ## Admin"; - capcodeIcon = (" "; + capcodeClass = ' capcodeAdmin'; + capcodeStart = { + innerHTML: " ## Admin" + }; + capcodeIcon = { + innerHTML: " \"Admin" + }; break; case 'mod': - capcodeClass = " capcodeMod"; - capcodeStart = " ## Mod"; - capcodeIcon = (" "; - break; - case 'developer': - capcodeClass = " capcodeDeveloper"; - capcodeStart = " ## Developer"; - capcodeIcon = (" "; + capcodeClass = ' capcodeMod'; + capcodeStart = { + innerHTML: " ## Mod" + }; + capcodeIcon = { + innerHTML: " \"Mod" + }; + capcodeClass = ' capcodeDeveloper'; + capcodeStart = { + innerHTML: " ## Developer" + }; + capcodeIcon = { + innerHTML: " \"Developer" + }; break; default: capcodeClass = ''; - capcodeStart = ''; - capcodeIcon = ''; + capcodeStart = { + innerHTML: "" + }; + capcodeIcon = isOP && boardID === 'f' ? { + innerHTML: "" + } : { + innerHTML: " " + }; } - userID = !capcode && uniqueID ? (" (ID: ") + ("" + uniqueID + ") ") : ''; - flag = !flagCode ? '' : boardID === 'pol' ? " " : " "; - if (file != null ? file.isDeleted : void 0) { - fileHTML = isOP ? "
" + ("") + "
" : "
" + ("") + "
"; - } else if (file) { - fileSize = $.bytesToString(file.size); - fileThumb = file.turl; - if (file.isSpoiler) { - fileSize = "Spoiler Image, " + fileSize; - if (!isArchived) { - fileThumb = "" + staticPath + "spoiler"; - if (spoilerRange = Build.spoilerRange[boardID]) { - fileThumb += ("-" + boardID) + Math.floor(1 + spoilerRange * Math.random()); - } - fileThumb += '.png'; - file.twidth = file.theight = 100; + nameClass = capcode ? ' capcode' : ''; + tripcodeField = tripcode ? { + innerHTML: " " + E(tripcode) + "" + } : { + innerHTML: "" + }; + emailField = { + innerHTML: "" + E(name) + "" + tripcodeField.innerHTML + capcodeStart.innerHTML + }; + if (email) { + emailProcessed = encodeURIComponent(email).replace(/%40/g, '@'); + emailField = { + innerHTML: "" + emailField.innerHTML + "" + }; + } + userID = !capcode && uniqueID ? { + innerHTML: " (ID: " + E(uniqueID) + ")" + } : { + innerHTML: "" + }; + flag = !flagCode ? { + innerHTML: "" + } : false ? { + innerHTML: " \""" + } : { + innerHTML: " " + }; + nameBlock = { + innerHTML: "" + emailField.innerHTML + capcodeIcon.innerHTML + userID.innerHTML + flag.innerHTML + " " + }; + + /* Post Info */ + subjectField = isOP || boardID === 'f' ? { + innerHTML: "" + E(subject) + " " + } : { + innerHTML: "" + }; + desktop2 = isOP && boardID === 'f' ? '' : ' desktop'; + postLink = Build.postURL(boardID, threadID, postID); + quoteLink = Build.sameThread(boardID, threadID) ? "javascript:quote('" + (+postID) + "');" : "/" + boardID + "/thread/" + threadID + "#q" + postID; + icons = (function() { + var _i, _len, _ref, _results; + _ref = ['Sticky', 'Closed', 'Archived']; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + if (!(o["is" + type] && !(type === 'Closed' && o.isArchived))) { + continue; } + typeLC = type.toLowerCase(); + _results.push({ + innerHTML: " \""" + }); } - 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.isSpoiler ? 'Spoiler Image' : shortFilename) + "") + (" (" + fileSize + ", " + fileDims + ")
"); - fileHTML = "
" + fileInfo + imgSrc + "
"; - } else { - fileHTML = ''; - } - sticky = isSticky ? " " : ''; - closed = isClosed ? " " : ''; - if (isOP && g.VIEW === 'index') { - pageNum = Math.floor(Index.liveThreadData.keys.indexOf("" + postID) / Index.threadsNumPerPage) + 1; - pageIcon = " Page " + pageNum + ""; - replyLink = "   [Reply]"; - } else { - pageIcon = ''; - replyLink = ''; - } + return _results; + })(); + replyLink = isOP && g.VIEW === 'index' ? { + innerHTML: "   [Reply]" + } : { + innerHTML: "" + }; + postInfo = { + innerHTML: "
" + subjectField.innerHTML + nameBlock.innerHTML + "" + E(date) + " No." + E(postID) + "" + icons.map(function(x) { + return x.innerHTML; + }).join('') + replyLink.innerHTML + "
" + }; + + /* File Info */ + fileCont = (file != null ? file.isDeleted : void 0) ? { + innerHTML: "\"File" + } : file && boardID === 'f' ? { + innerHTML: "
File: " + E(file.name) + "-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ")
" + } : file ? (file.isSpoiler ? (shortFilename = 'Spoiler Image', (spoilerRange = Build.spoilerRange[boardID]) ? fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png" : fileThumb = '//s.4cdn.org/image/spoiler.png', file.twidth = file.theight = 100) : (shortFilename = Build.shortFilename(file.name, !isOP), fileThumb = file.turl), fileSize = $.bytesToString(file.size), fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height, fileLink = file.isSpoiler || file.name === shortFilename ? { + innerHTML: "" + E(shortFilename) + "" + } : { + innerHTML: "" + E(shortFilename) + "" + }, fileText = file.isSpoiler ? { + innerHTML: "
File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
" + } : { + innerHTML: "
File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
" + }, { + innerHTML: fileText.innerHTML + "\""" + }) : void 0; + fileBlock = file ? { + innerHTML: "
" + fileCont.innerHTML + "
" + } : { + innerHTML: "" + }; + + /* Whole Post */ + highlightPost = capcode === 'admin_highlight' ? ' highlightPost' : ''; + message = { + innerHTML: "
" + comment.innerHTML + "
" + }; + wholePost = isOP ? { + innerHTML: "
" + fileBlock.innerHTML + postInfo.innerHTML + message.innerHTML + "
" + } : { + innerHTML: "
>>
" + postInfo.innerHTML + fileBlock.innerHTML + message.innerHTML + "
" + }; 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 || '') + "
") + '
' + id: "pc" + postID }); + $.extend(container, wholePost); _ref = $$('.quotelink', container); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; href = quote.getAttribute('href'); - if (href[0] !== '#') { - continue; + if ((href[0] === '#') && !(Build.sameThread(boardID, threadID))) { + quote.href = (Build.path(boardID, threadID)) + href; + } else if ((match = href.match(/^\/([^\/]+)\/thread\/(\d+)/)) && (Build.sameThread(match[1], match[2]))) { + quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; } - quote.href = Build.path(boardID, threadID, href.slice(2)); } return container; }, @@ -6049,13 +6151,14 @@ postCount = data.replies + 1; fileCount = data.images + !!data.ext; pageCount = Math.floor(Index.liveThreadData.keys.indexOf("" + thread.ID) / Index.threadsNumPerPage) + 1; - subject = thread.OP.info.subject ? "
" + thread.OP.nodes.subject.innerHTML + "
" : ''; + subject = thread.OP.info.subject ? { + innerHTML: "
" + E(thread.OP.nodes.subject.innerHTML) + "
" + } : ''; comment = thread.OP.nodes.comment.innerHTML.replace(/(
\s*){2,}/g, '
'); root = $.el('div', { - className: 'catalog-thread', - innerHTML: { - innerHTML: "\r
\r" + postCount + " / " + fileCount + " / " + pageCount + "\r\r
\r" + subject + "\r
" + comment + "
\r" - } + className: 'catalog-thread' + }, { + innerHTML: "\r
\r" + postCount + " / " + fileCount + " / " + pageCount + "\r\r
\r" + subject + "\r
" + comment + "
\r" }); root.dataset.fullID = thread.fullID; if (thread.isPinned) { @@ -6125,7 +6228,7 @@ threadExcerpt: function(thread) { var OP, excerpt, _ref; OP = thread.OP; - excerpt = ((_ref = OP.info.subject) != null ? _ref.trim() : void 0) || OP.info.comment.replace(/\n+/g, ' // ') || OP.getNameBlock(); + excerpt = ((_ref = OP.info.subject) != null ? _ref.trim() : void 0) || OP.info.comment.replace(/\n+/g, ' // ') || OP.info.nameBlock; if (excerpt.length > 70) { excerpt = "" + excerpt.slice(0, 67) + "..."; } @@ -7221,7 +7324,7 @@ }, node: function() { var a, data, label; - if (!this.isReply && g.VIEW !== 'index' || this.isClone) { + if (!this.isReply || this.isClone || this.isFetchedQuote) { return; } if (data = PostHiding.db.get({ @@ -7229,12 +7332,12 @@ threadID: this.thread.ID, postID: this.ID })) { - if (data.thisPost === false) { + if (data.thisPost) { + this.hide('Manually hidden', data.makeStub, data.hideRecursively); + } else { label = "Recursively hidden for quoting No." + this; Recursive.apply('hide', this, label, data.makeStub, true); Recursive.add('hide', this, label, data.makeStub, true); - } else { - this.hide('Manually hidden', data.makeStub, data.hideRecursively); } } if (!Conf['Post Hiding']) { @@ -7316,19 +7419,13 @@ } }; thisPost = { - el: $.el('label', { - innerHTML: ' This post' - }) + el: UI.checkbox('thisPost', ' This post', true) }; replies = { - el: $.el('label', { - innerHTML: " Hide replies" - }) + el: UI.checkbox('replies', ' Hide replies', Conf['Recursive Hiding']) }; makeStub = { - el: $.el('label', { - innerHTML: " Make stub" - }) + el: UI.checkbox('makeStub', ' Make stub', Conf['Stubs']) }; Menu.menu.addEntry({ el: $.el('div', { @@ -7358,18 +7455,14 @@ } }; thisPost = { - el: $.el('label', { - innerHTML: ' This post' - }), + el: UI.checkbox('thisPost', ' This post', false), open: function(post) { this.el.firstChild.checked = post.isHidden; return true; } }; replies = { - el: $.el('label', { - innerHTML: ' Unhide replies' - }), + el: UI.checkbox('replies', ' Show replies', false), open: function(post) { var data; data = PostHiding.db.get({ @@ -7479,6 +7572,10 @@ Recursive = { recursives: {}, init: function() { + var _ref; + if ((_ref = g.VIEW) !== 'index' && _ref !== 'thread') { + return; + } return Post.callbacks.push({ name: 'Recursive', cb: this.node @@ -7486,7 +7583,7 @@ }, node: function() { var i, obj, quote, recursive, _i, _j, _len, _len1, _ref, _ref1; - if (this.isClone) { + if (this.isClone || this.isFetchedQuote) { return; } _ref = this.quotes; @@ -16967,10 +17064,9 @@ $.event('CloseMenu'); Settings.dialog = dialog = $.el('div', { id: 'appchanx-settings', - "class": 'dialog', - innerHTML: { - innerHTML: "\r
\r
\r" - } + "class": 'dialog' + }, { + innerHTML: "\r
\r
\r" }); Settings.overlay = overlay = $.el('div', { id: 'overlay' @@ -17162,9 +17258,9 @@ }, filter: function(section) { var select; - section.innerHTML = { + $.extend(section, { innerHTML: "\r
" - }; + }); select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); @@ -17186,15 +17282,15 @@ $.add(div, ta); return; } - return div.innerHTML = { + return $.extend(div, { innerHTML: "
Filter is disabled.
\r

\rUse regular expressions, one per line.
\rLines starting with a # will be ignored.
\rFor example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
\rMD5 filtering uses exact string matching, not regular expressions.\r

\r\r" - }; + }); }, sauce: function(section) { var ta; - section.innerHTML = { + $.extend(section, { innerHTML: "
Sauce is disabled.
\r
Lines starting with a # will be ignored.
\r
You can specify a display text by appending ;text:[text] to the URL.
\r\r\r" - }; + }); ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { return ta.value = item['sauces'].replace(/\$\d/g, function(c) { @@ -17216,9 +17312,9 @@ }, 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 = { + $.extend(section, { innerHTML: "
\rArchiver\r
404 Redirect is disabled.
\r
\r\r\r\r\r\r\r\r
Thread redirectionPost fetchingFile redirection
\rDisabled selections indicate that only one archive is available for that board and redirection type.\r
\r
\rCustom Board Navigation\r
\rNew lines will be converted into spaces.

\r
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 (@).
\r
Board link: g
\r
Title link: g-title
\r
Board link (Replace with title when on that board): g-replace
\r
Full text link: g-full
\r
Custom text link: g-text:\"Install Gentoo\"
\r
External link: external-text:\"Google\",\"http://www.google.com\"
\r
Index mode: g-mode:\"type\" where type is paged, all threads or catalog
\r
Index sort: g-sort:\"type\" where type is bump order, last reply, creation date, reply count or file countCombinations are possible: g-text:\"VIP Catalog\"-mode:\"catalog\"-sort:\"creation date\"
\r
Full board list toggle: toggle-all
\r
\r
\r[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h-mode:\"catalog\"-sort:\"file count\"] [t-text:\"Piracy\"]
\rwill give you
\r[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\rif you are on /g/.\r
\r
\r
\rTime Formatting is disabled.\r
:
\r
Supported format specifiers:
\r
Day: %a, %A, %d, %e
\r
Month: %m, %b, %B
\r
Year: %y, %Y
\r
Hour: %k, %H, %l, %I, %p, %P
\r
Minute: %M
\r
Second: %S
\r
\r
\rQuote Backlinks formatting is disabled.\r
:
\r
\r
\rFile Info Formatting is disabled.\r
:
\r
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
\r
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
\r
Spoiler indicator: %p
\r
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
\r
Resolution: %r (Displays 'PDF' for PDF files)
\r
\r
\rQuick Reply Personas\r\r

\rOne item per line.
\rItems will be added in the relevant input's auto-completion list.
\rPassword items will always be used, since there is no password input.
\rLines starting with a # will be ignored.\r

\r\r
\r
\rUnread Favicon is disabled.\r\r\r
\r
\rThread Updater is disabled.\r
\rInterval: \r
\r
\r
\r Custom CSS\r
\r\r\r
\r
\r" - }; + }); items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']; @@ -17413,9 +17509,9 @@ }, keybinds: function(section) { var arr, input, inputs, items, key, tbody, tr, _ref; - section.innerHTML = { + $.extend(section, { innerHTML: "
Keybinds are disabled.
\r
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
\r
Press Backspace to disable a keybind.
\r\r\r
ActionsKeybinds
" - }; + }); tbody = $('tbody', section); items = {}; inputs = {}; diff --git a/builds/crx/script.js b/builds/crx/script.js index 52f8afc28..23eb4321d 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -3375,6 +3375,7 @@ root: root, post: post, info: info, + nameBlock: $('.nameBlock', info), comment: $('.postMessage', post), links: [], quotelinks: [], @@ -3386,6 +3387,7 @@ this.thread.isClosed = !!$('.closedIcon', info); } this.info = {}; + this.info.nameBlock = Conf['Anonymize'] ? 'Anonymous' : this.nodes.nameBlock.textContent.trim(); if (subject = $('.subject', info)) { this.nodes.subject = subject; this.info.subject = subject.textContent; @@ -4851,16 +4853,14 @@ }); this.pagelist = $.el('div', { className: 'pagelist', - hidden: true, - innerHTML: { - innerHTML: "
\r\r<\r\r
\r
\r
\r\r>\r\r
" - } + hidden: true + }, { + innerHTML: "
\r\r<\r\r
\r
\r
\r\r>\r\r
" }); this.navLinks = $.el('div', { - className: 'navLinks', - innerHTML: { - innerHTML: "\r\r\\uf05c\r \r\r\r\r\r\r\r\r" - } + className: 'navLinks' + }, { + innerHTML: "\r\r\\uf05c\r \r\r\r\r\r\r\r\r" }); this.timeEl = $('time#index-last-refresh', this.navLinks); this.searchInput = $('#index-search', this.navLinks); @@ -5865,9 +5865,23 @@ staticPath: '//s.4cdn.org/image/', gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', spoilerRange: {}, - shortFilename: function(filename, isReply) { + unescape: function(text) { + if (text == null) { + return text; + } + return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>' + }[c]; + }); + }, + shortFilename: function(filename) { var ext, threshold; - threshold = isReply ? 30 : 40; + threshold = 30; ext = filename.match(/\.[^.]+$/)[0]; if (filename.length - ext.length > threshold) { return "" + filename.slice(0, threshold - 5) + "(...)" + ext; @@ -5876,12 +5890,22 @@ } }, thumbRotate: (function() { - var n; - n = 0; + var t; + t = 0; return function() { - return n = (n + 1) % 2; + return t = (t ? 0 : 1); }; })(), + sameThread: function(boardID, threadID) { + return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; + }, + postURL: function(boardID, threadID, postID) { + if (Build.sameThread(boardID, threadID)) { + return "#p" + postID; + } else { + return Build.path(boardID, threadID, postID); + } + }, path: function(boardID, threadID, postID, fragment) { var path; path = "/" + boardID + "/thread/" + threadID; @@ -5899,22 +5923,30 @@ 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, + comment: { + innerHTML: data.com || '' + }, isSticky: !!data.sticky, - isClosed: !!data.closed + isClosed: !!data.closed, + isArchived: !!data.archived }; - if (data.ext || data.filedeleted) { + if (data.filedeleted) { o.file = { + isDeleted: true + }; + } else if (data.ext) { + o.file = { + name: (Build.unescape(data.filename)) + data.ext, name: data.filename + data.ext, timestamp: "" + data.tim + data.ext, url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, @@ -5926,107 +5958,177 @@ theight: data.tn_h, twidth: data.tn_w, isSpoiler: !!data.spoiler, - isDeleted: !!data.filedeleted + isDeleted: false, + tag: data.tag }; } return Build.post(o); }, - post: function(o, isArchived) { + post: function(o) { /* 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 boardID, capcode, capcodeClass, capcodeIcon, capcodeStart, comment, container, date, dateUTC, desktop2, email, emailField, emailProcessed, file, fileBlock, fileCont, fileDims, fileLink, fileSize, fileText, fileThumb, flag, flagCode, flagName, gifIcon, highlightPost, href, icons, isOP, match, message, name, nameBlock, nameClass, postID, postInfo, postLink, quote, quoteLink, replyLink, shortFilename, spoilerRange, staticPath, subject, subjectField, threadID, tripcode, tripcodeField, type, typeLC, uniqueID, userID, wholePost, _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, comment = o.comment, file = o.file; + name || (name = ''); + subject || (subject = ''); isOP = postID === threadID; staticPath = Build.staticPath, gifIcon = Build.gifIcon; - tripcode = tripcode ? " " + tripcode + "" : ''; - if (email) { - emailStart = ''; - emailEnd = ''; - } else { - emailStart = ''; - emailEnd = ''; - } + + /* Name Block */ switch (capcode) { case 'admin': case 'admin_highlight': - capcodeClass = " capcodeAdmin"; - capcodeStart = " ## Admin"; - capcodeIcon = (" "; + capcodeClass = ' capcodeAdmin'; + capcodeStart = { + innerHTML: " ## Admin" + }; + capcodeIcon = { + innerHTML: " \"Admin" + }; break; case 'mod': - capcodeClass = " capcodeMod"; - capcodeStart = " ## Mod"; - capcodeIcon = (" "; - break; - case 'developer': - capcodeClass = " capcodeDeveloper"; - capcodeStart = " ## Developer"; - capcodeIcon = (" "; + capcodeClass = ' capcodeMod'; + capcodeStart = { + innerHTML: " ## Mod" + }; + capcodeIcon = { + innerHTML: " \"Mod" + }; + capcodeClass = ' capcodeDeveloper'; + capcodeStart = { + innerHTML: " ## Developer" + }; + capcodeIcon = { + innerHTML: " \"Developer" + }; break; default: capcodeClass = ''; - capcodeStart = ''; - capcodeIcon = ''; + capcodeStart = { + innerHTML: "" + }; + capcodeIcon = isOP && boardID === 'f' ? { + innerHTML: "" + } : { + innerHTML: " " + }; } - userID = !capcode && uniqueID ? (" (ID: ") + ("" + uniqueID + ") ") : ''; - flag = !flagCode ? '' : boardID === 'pol' ? " " : " "; - if (file != null ? file.isDeleted : void 0) { - fileHTML = isOP ? "
" + ("") + "
" : "
" + ("") + "
"; - } else if (file) { - fileSize = $.bytesToString(file.size); - fileThumb = file.turl; - if (file.isSpoiler) { - fileSize = "Spoiler Image, " + fileSize; - if (!isArchived) { - fileThumb = "" + staticPath + "spoiler"; - if (spoilerRange = Build.spoilerRange[boardID]) { - fileThumb += ("-" + boardID) + Math.floor(1 + spoilerRange * Math.random()); - } - fileThumb += '.png'; - file.twidth = file.theight = 100; + nameClass = capcode ? ' capcode' : ''; + tripcodeField = tripcode ? { + innerHTML: " " + E(tripcode) + "" + } : { + innerHTML: "" + }; + emailField = { + innerHTML: "" + E(name) + "" + tripcodeField.innerHTML + capcodeStart.innerHTML + }; + if (email) { + emailProcessed = encodeURIComponent(email).replace(/%40/g, '@'); + emailField = { + innerHTML: "" + emailField.innerHTML + "" + }; + } + userID = !capcode && uniqueID ? { + innerHTML: " (ID: " + E(uniqueID) + ")" + } : { + innerHTML: "" + }; + flag = !flagCode ? { + innerHTML: "" + } : false ? { + innerHTML: " \""" + } : { + innerHTML: " " + }; + nameBlock = { + innerHTML: "" + emailField.innerHTML + capcodeIcon.innerHTML + userID.innerHTML + flag.innerHTML + " " + }; + + /* Post Info */ + subjectField = isOP || boardID === 'f' ? { + innerHTML: "" + E(subject) + " " + } : { + innerHTML: "" + }; + desktop2 = isOP && boardID === 'f' ? '' : ' desktop'; + postLink = Build.postURL(boardID, threadID, postID); + quoteLink = Build.sameThread(boardID, threadID) ? "javascript:quote('" + (+postID) + "');" : "/" + boardID + "/thread/" + threadID + "#q" + postID; + icons = (function() { + var _i, _len, _ref, _results; + _ref = ['Sticky', 'Closed', 'Archived']; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + if (!(o["is" + type] && !(type === 'Closed' && o.isArchived))) { + continue; } + typeLC = type.toLowerCase(); + _results.push({ + innerHTML: " \""" + }); } - 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.isSpoiler ? 'Spoiler Image' : shortFilename) + "") + (" (" + fileSize + ", " + fileDims + ")
"); - fileHTML = "
" + fileInfo + imgSrc + "
"; - } else { - fileHTML = ''; - } - sticky = isSticky ? " " : ''; - closed = isClosed ? " " : ''; - if (isOP && g.VIEW === 'index') { - pageNum = Math.floor(Index.liveThreadData.keys.indexOf("" + postID) / Index.threadsNumPerPage) + 1; - pageIcon = " Page " + pageNum + ""; - replyLink = "   [Reply]"; - } else { - pageIcon = ''; - replyLink = ''; - } + return _results; + })(); + replyLink = isOP && g.VIEW === 'index' ? { + innerHTML: "   [Reply]" + } : { + innerHTML: "" + }; + postInfo = { + innerHTML: "
" + subjectField.innerHTML + nameBlock.innerHTML + "" + E(date) + " No." + E(postID) + "" + icons.map(function(x) { + return x.innerHTML; + }).join('') + replyLink.innerHTML + "
" + }; + + /* File Info */ + fileCont = (file != null ? file.isDeleted : void 0) ? { + innerHTML: "\"File" + } : file && boardID === 'f' ? { + innerHTML: "
File: " + E(file.name) + "-(" + E($.bytesToString(file.size)) + ", " + E(file.width) + "x" + E(file.height) + ", " + E(file.tag) + ")
" + } : file ? (file.isSpoiler ? (shortFilename = 'Spoiler Image', (spoilerRange = Build.spoilerRange[boardID]) ? fileThumb = "//s.4cdn.org/image/spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png" : fileThumb = '//s.4cdn.org/image/spoiler.png', file.twidth = file.theight = 100) : (shortFilename = Build.shortFilename(file.name, !isOP), fileThumb = file.turl), fileSize = $.bytesToString(file.size), fileDims = file.url.slice(-4) === '.pdf' ? 'PDF' : "" + file.width + "x" + file.height, fileLink = file.isSpoiler || file.name === shortFilename ? { + innerHTML: "" + E(shortFilename) + "" + } : { + innerHTML: "" + E(shortFilename) + "" + }, fileText = file.isSpoiler ? { + innerHTML: "
File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
" + } : { + innerHTML: "
File: " + fileLink.innerHTML + " (" + E(fileSize) + ", " + E(fileDims) + ")
" + }, { + innerHTML: fileText.innerHTML + "\""" + }) : void 0; + fileBlock = file ? { + innerHTML: "
" + fileCont.innerHTML + "
" + } : { + innerHTML: "" + }; + + /* Whole Post */ + highlightPost = capcode === 'admin_highlight' ? ' highlightPost' : ''; + message = { + innerHTML: "
" + comment.innerHTML + "
" + }; + wholePost = isOP ? { + innerHTML: "
" + fileBlock.innerHTML + postInfo.innerHTML + message.innerHTML + "
" + } : { + innerHTML: "
>>
" + postInfo.innerHTML + fileBlock.innerHTML + message.innerHTML + "
" + }; 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 || '') + "
") + '
' + id: "pc" + postID }); + $.extend(container, wholePost); _ref = $$('.quotelink', container); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; href = quote.getAttribute('href'); - if (href[0] !== '#') { - continue; + if ((href[0] === '#') && !(Build.sameThread(boardID, threadID))) { + quote.href = (Build.path(boardID, threadID)) + href; + } else if ((match = href.match(/^\/([^\/]+)\/thread\/(\d+)/)) && (Build.sameThread(match[1], match[2]))) { + quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; } - quote.href = Build.path(boardID, threadID, href.slice(2)); } return container; }, @@ -6078,13 +6180,14 @@ postCount = data.replies + 1; fileCount = data.images + !!data.ext; pageCount = Math.floor(Index.liveThreadData.keys.indexOf("" + thread.ID) / Index.threadsNumPerPage) + 1; - subject = thread.OP.info.subject ? "
" + thread.OP.nodes.subject.innerHTML + "
" : ''; + subject = thread.OP.info.subject ? { + innerHTML: "
" + E(thread.OP.nodes.subject.innerHTML) + "
" + } : ''; comment = thread.OP.nodes.comment.innerHTML.replace(/(
\s*){2,}/g, '
'); root = $.el('div', { - className: 'catalog-thread', - innerHTML: { - innerHTML: "\r
\r" + postCount + " / " + fileCount + " / " + pageCount + "\r\r
\r" + subject + "\r
" + comment + "
\r" - } + className: 'catalog-thread' + }, { + innerHTML: "\r
\r" + postCount + " / " + fileCount + " / " + pageCount + "\r\r
\r" + subject + "\r
" + comment + "
\r" }); root.dataset.fullID = thread.fullID; if (thread.isPinned) { @@ -6154,7 +6257,7 @@ threadExcerpt: function(thread) { var OP, excerpt, _ref; OP = thread.OP; - excerpt = ((_ref = OP.info.subject) != null ? _ref.trim() : void 0) || OP.info.comment.replace(/\n+/g, ' // ') || OP.getNameBlock(); + excerpt = ((_ref = OP.info.subject) != null ? _ref.trim() : void 0) || OP.info.comment.replace(/\n+/g, ' // ') || OP.info.nameBlock; if (excerpt.length > 70) { excerpt = "" + excerpt.slice(0, 67) + "..."; } @@ -7266,7 +7369,7 @@ }, node: function() { var a, data, label; - if (!this.isReply && g.VIEW !== 'index' || this.isClone) { + if (!this.isReply || this.isClone || this.isFetchedQuote) { return; } if (data = PostHiding.db.get({ @@ -7274,12 +7377,12 @@ threadID: this.thread.ID, postID: this.ID })) { - if (data.thisPost === false) { + if (data.thisPost) { + this.hide('Manually hidden', data.makeStub, data.hideRecursively); + } else { label = "Recursively hidden for quoting No." + this; Recursive.apply('hide', this, label, data.makeStub, true); Recursive.add('hide', this, label, data.makeStub, true); - } else { - this.hide('Manually hidden', data.makeStub, data.hideRecursively); } } if (!Conf['Post Hiding']) { @@ -7361,19 +7464,13 @@ } }; thisPost = { - el: $.el('label', { - innerHTML: ' This post' - }) + el: UI.checkbox('thisPost', ' This post', true) }; replies = { - el: $.el('label', { - innerHTML: " Hide replies" - }) + el: UI.checkbox('replies', ' Hide replies', Conf['Recursive Hiding']) }; makeStub = { - el: $.el('label', { - innerHTML: " Make stub" - }) + el: UI.checkbox('makeStub', ' Make stub', Conf['Stubs']) }; Menu.menu.addEntry({ el: $.el('div', { @@ -7403,18 +7500,14 @@ } }; thisPost = { - el: $.el('label', { - innerHTML: ' This post' - }), + el: UI.checkbox('thisPost', ' This post', false), open: function(post) { this.el.firstChild.checked = post.isHidden; return true; } }; replies = { - el: $.el('label', { - innerHTML: ' Unhide replies' - }), + el: UI.checkbox('replies', ' Show replies', false), open: function(post) { var data; data = PostHiding.db.get({ @@ -7524,6 +7617,10 @@ Recursive = { recursives: {}, init: function() { + var _ref; + if ((_ref = g.VIEW) !== 'index' && _ref !== 'thread') { + return; + } return Post.callbacks.push({ name: 'Recursive', cb: this.node @@ -7531,7 +7628,7 @@ }, node: function() { var i, obj, quote, recursive, _i, _j, _len, _len1, _ref, _ref1; - if (this.isClone) { + if (this.isClone || this.isFetchedQuote) { return; } _ref = this.quotes; @@ -16990,10 +17087,9 @@ $.event('CloseMenu'); Settings.dialog = dialog = $.el('div', { id: 'appchanx-settings', - "class": 'dialog', - innerHTML: { - innerHTML: "\r
\r
\r" - } + "class": 'dialog' + }, { + innerHTML: "\r
\r
\r" }); Settings.overlay = overlay = $.el('div', { id: 'overlay' @@ -17183,9 +17279,9 @@ }, filter: function(section) { var select; - section.innerHTML = { + $.extend(section, { innerHTML: "\r
" - }; + }); select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); @@ -17207,15 +17303,15 @@ $.add(div, ta); return; } - return div.innerHTML = { + return $.extend(div, { innerHTML: "
Filter is disabled.
\r

\rUse regular expressions, one per line.
\rLines starting with a # will be ignored.
\rFor example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
\rMD5 filtering uses exact string matching, not regular expressions.\r

\r\r" - }; + }); }, sauce: function(section) { var ta; - section.innerHTML = { + $.extend(section, { innerHTML: "
Sauce is disabled.
\r
Lines starting with a # will be ignored.
\r
You can specify a display text by appending ;text:[text] to the URL.
\r\r\r" - }; + }); ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { return ta.value = item['sauces'].replace(/\$\d/g, function(c) { @@ -17237,9 +17333,9 @@ }, 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 = { + $.extend(section, { innerHTML: "
\rArchiver\r
404 Redirect is disabled.
\r
\r\r\r\r\r\r\r\r
Thread redirectionPost fetchingFile redirection
\rDisabled selections indicate that only one archive is available for that board and redirection type.\r
\r
\rCustom Board Navigation\r
\rNew lines will be converted into spaces.

\r
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 (@).
\r
Board link: g
\r
Title link: g-title
\r
Board link (Replace with title when on that board): g-replace
\r
Full text link: g-full
\r
Custom text link: g-text:\"Install Gentoo\"
\r
External link: external-text:\"Google\",\"http://www.google.com\"
\r
Index mode: g-mode:\"type\" where type is paged, all threads or catalog
\r
Index sort: g-sort:\"type\" where type is bump order, last reply, creation date, reply count or file countCombinations are possible: g-text:\"VIP Catalog\"-mode:\"catalog\"-sort:\"creation date\"
\r
Full board list toggle: toggle-all
\r
\r
\r[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h-mode:\"catalog\"-sort:\"file count\"] [t-text:\"Piracy\"]
\rwill give you
\r[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\rif you are on /g/.\r
\r
\r
\rTime Formatting is disabled.\r
:
\r
Supported format specifiers:
\r
Day: %a, %A, %d, %e
\r
Month: %m, %b, %B
\r
Year: %y, %Y
\r
Hour: %k, %H, %l, %I, %p, %P
\r
Minute: %M
\r
Second: %S
\r
\r
\rQuote Backlinks formatting is disabled.\r
:
\r
\r
\rFile Info Formatting is disabled.\r
:
\r
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
\r
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
\r
Spoiler indicator: %p
\r
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
\r
Resolution: %r (Displays 'PDF' for PDF files)
\r
\r
\rQuick Reply Personas\r\r

\rOne item per line.
\rItems will be added in the relevant input's auto-completion list.
\rPassword items will always be used, since there is no password input.
\rLines starting with a # will be ignored.\r

\r\r
\r
\rUnread Favicon is disabled.\r\r\r
\r
\rThread Updater is disabled.\r
\rInterval: \r
\r
\r
\r Custom CSS\r
\r\r\r
\r
\r" - }; + }); items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']; @@ -17434,9 +17530,9 @@ }, keybinds: function(section) { var arr, input, inputs, items, key, tbody, tr, _ref; - section.innerHTML = { + $.extend(section, { innerHTML: "
Keybinds are disabled.
\r
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
\r
Press Backspace to disable a keybind.
\r\r\r
ActionsKeybinds
" - }; + }); tbody = $('tbody', section); items = {}; inputs = {}; diff --git a/src/Filtering/PostHiding.coffee b/src/Filtering/PostHiding.coffee index 1037ac87a..9a69c12ea 100644 --- a/src/Filtering/PostHiding.coffee +++ b/src/Filtering/PostHiding.coffee @@ -15,15 +15,15 @@ PostHiding = cb: @node node: -> - return if !@isReply and g.VIEW isnt 'index' or @isClone + return if !@isReply or @isClone or @isFetchedQuote if data = PostHiding.db.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID} - if data.thisPost is false + if data.thisPost + @hide 'Manually hidden', data.makeStub, data.hideRecursively + else label = "Recursively hidden for quoting No.#{@}" Recursive.apply 'hide', @, label, data.makeStub, true Recursive.add 'hide', @, label, data.makeStub, true - else - @hide 'Manually hidden', data.makeStub, data.hideRecursively return unless Conf['Post Hiding'] if @isReply @@ -83,12 +83,9 @@ PostHiding = @cb = -> PostHiding.menu.hide post $.on @el, 'click', @cb true - thisPost = - el: $.el 'label', innerHTML: ' This post' - replies = - el: $.el 'label', innerHTML: " Hide replies" - makeStub = - el: $.el 'label', innerHTML: " Make stub" + thisPost = el: UI.checkbox 'thisPost', ' This post', true + replies = el: UI.checkbox 'replies', ' Hide replies', Conf['Recursive Hiding'] + makeStub = el: UI.checkbox 'makeStub', ' Make stub', Conf['Stubs'] Menu.menu.addEntry el: $.el 'div', @@ -107,12 +104,12 @@ PostHiding = $.on @el, 'click', @cb true thisPost = - el: $.el 'label', innerHTML: ' This post' + el: UI.checkbox 'thisPost', ' This post', false open: (post) -> @el.firstChild.checked = post.isHidden true replies = - el: $.el 'label', innerHTML: ' Unhide replies' + el: UI.checkbox 'replies', ' Show replies', false open: (post) -> data = PostHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID} @el.firstChild.checked = if 'hideRecursively' of data then data.hideRecursively else Conf['Recursive Hiding'] diff --git a/src/Filtering/Recursive.coffee b/src/Filtering/Recursive.coffee index ac01949df..aa3f55a4c 100755 --- a/src/Filtering/Recursive.coffee +++ b/src/Filtering/Recursive.coffee @@ -1,12 +1,13 @@ Recursive = recursives: {} init: -> + return unless g.VIEW in ['index', 'thread'] Post.callbacks.push name: 'Recursive' cb: @node node: -> - return if @isClone + return if @isClone or @isFetchedQuote for quote in @quotes when obj = Recursive.recursives[quote] for recursive, i in obj.recursives @[recursive] obj.args[i]... diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 714ecbc39..2db22c451 100755 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -2,48 +2,63 @@ Build = staticPath: '//s.4cdn.org/image/' gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif' spoilerRange: {} - shortFilename: (filename, isReply) -> - # FILENAME SHORTENING SCIENCE: - # OPs have a +10 characters threshold. - # The file extension is not taken into account. - threshold = if isReply then 30 else 40 + unescape: (text) -> + return text unless text? + text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt);/g, (c) -> + {'&': '&', ''': "'", '"': '"', '<': '<', '>': '>'}[c] + shortFilename: (filename) -> + threshold = 30 ext = filename.match(/\.[^.]+$/)[0] if filename.length - ext.length > threshold "#{filename[...threshold - 5]}(...)#{ext}" else filename thumbRotate: do -> - n = 0 - -> n = (n + 1) % 2 + t = 0 + -> t = (if t then 0 else 1) + sameThread: (boardID, threadID) -> + g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID + postURL: (boardID, threadID, postID) -> + if Build.sameThread boardID, threadID + "#p#{postID}" + else + Build.path boardID, threadID, postID path: (boardID, threadID, postID, fragment) -> path = "/#{boardID}/thread/#{threadID}" - path += "/#{g.SLUG}" if g.SLUG? and threadID is g.THREADID - path += "##{fragment or 'p'}#{postID}" if postID + if g.SLUG? and threadID is g.THREADID + path += "/#{g.SLUG}" + if postID + path += "##{fragment or 'p'}#{postID}" path postFromObject: (data, boardID) -> o = # id - postID: data.no - threadID: data.resto or data.no - boardID: boardID + postID: data.no + threadID: data.resto or data.no + boardID: boardID # info - name: data.name - capcode: data.capcode - tripcode: data.trip - uniqueID: data.id - email: if data.email then encodeURI data.email.replace /"/g, '"' else '' - subject: data.sub - flagCode: data.country - flagName: data.country_name - date: data.now - dateUTC: data.time - comment: data.com + name: Build.unescape data.name + capcode: data.capcode + tripcode: data.trip + uniqueID: data.id + email: Build.unescape data.email + subject: Build.unescape data.sub + flagCode: data.country + flagName: Build.unescape data.country_name + date: data.now + dateUTC: data.time + comment: {innerHTML: data.com or ''} # thread status - isSticky: !!data.sticky - isClosed: !!data.closed + isSticky: !!data.sticky + isClosed: !!data.closed + isArchived: !!data.archived # file - if data.ext or data.filedeleted + if data.filedeleted o.file = + isDeleted: true + else if data.ext + o.file = + name: (Build.unescape data.filename) + data.ext name: data.filename + data.ext timestamp: "#{data.tim}#{data.ext}" url: if boardID is 'f' @@ -58,9 +73,10 @@ Build = theight: data.tn_h twidth: data.tn_w isSpoiler: !!data.spoiler - isDeleted: !!data.filedeleted + isDeleted: false + tag: data.tag Build.post o - post: (o, isArchived) -> + post: (o) -> ### This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS). @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE @@ -68,174 +84,186 @@ Build = { postID, threadID, boardID name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC - isSticky, isClosed comment file } = o + name or= '' + subject or= '' isOP = postID is threadID {staticPath, gifIcon} = Build - tripcode = if tripcode - " #{tripcode}" - else - '' - - if email - emailStart = '' - emailEnd = '' - else - emailStart = '' - emailEnd = '' + ### Name Block ### switch capcode when 'admin', 'admin_highlight' - capcodeClass = " capcodeAdmin" - capcodeStart = " ## Admin" - capcodeIcon = " " + capcodeClass = ' capcodeAdmin' + capcodeStart = <%= html(' ## Admin') %> + capcodeIcon = <%= html(' Admin Icon') %> when 'mod' - capcodeClass = " capcodeMod" - capcodeStart = " ## Mod" - capcodeIcon = " " - when 'developer' - capcodeClass = " capcodeDeveloper" - capcodeStart = " ## Developer" - capcodeIcon = " " + capcodeClass = ' capcodeMod' + capcodeStart = <%= html(' ## Mod') %> + capcodeIcon = <%= html(' Mod Icon') %> + capcodeClass = ' capcodeDeveloper' + capcodeStart = <%= html(' ## Developer') %> + capcodeIcon = <%= html(' Developer Icon') %> else capcodeClass = '' - capcodeStart = '' - capcodeIcon = '' + capcodeStart = <%= html('') %> + capcodeIcon = if isOP and boardID is 'f' then <%= html('') %> else <%= html(' ') %> - userID = - if !capcode and uniqueID - " (ID: " + - "#{uniqueID}) " - else - '' + nameClass = if capcode then ' capcode' else '' + + tripcodeField = if tripcode + <%= html(' ${tripcode}') %> + else + <%= html('') %> + + emailField = <%= html('${name}&{tripcodeField}&{capcodeStart}') %> + if email + emailProcessed = encodeURIComponent(email).replace /%40/g, '@' + emailField = <%= html('&{emailField}') %> + + userID = if !capcode and uniqueID + <%= html(' (ID: ${uniqueID})') %> + else + <%= html('') %> flag = unless flagCode - '' - else if boardID is 'pol' - " " + <%= html('') %> + else if false + <%= html(' ${flagCode}') %> else - " " + <%= html(' ') %> - if file?.isDeleted - fileHTML = if isOP - "
" + - "" + - "
" - else - "
" + - "" + - "
" + nameBlock = <%= html( + '' + + '&{emailField}&{capcodeIcon}&{userID}&{flag}' + + ' ' + ) %> + + ### Post Info ### + + subjectField = if isOP or boardID is 'f' + <%= html('${subject} ') %> + else + <%= html('') %> + + desktop2 = if isOP and boardID is 'f' then '' else ' desktop' + + postLink = Build.postURL boardID, threadID, postID + quoteLink = if Build.sameThread boardID, threadID + "javascript:quote('#{+postID}');" + else + "/#{boardID}/thread/#{threadID}#q#{postID}" + + icons = for type in ['Sticky', 'Closed', 'Archived'] when o["is#{type}"] and !(type is 'Closed' and o.isArchived) + typeLC = type.toLowerCase() + <%= html(' ${type}') %> + + replyLink = if isOP and g.VIEW is 'index' + <%= html('   [Reply]') %> + else + <%= html('') %> + + postInfo = <%= html( + '
' + + ' ' + + '&{subjectField}' + + '&{nameBlock}' + + '${date} ' + + '' + + 'No.' + + '${postID}' + + '@{icons}&{replyLink}' + + '' + + '
' + ) %> + + ### File Info ### + + fileCont = if file?.isDeleted + <%= html( + '' + + 'File deleted.' + + '' + ) %> + else if file and boardID is 'f' + <%= html( + '
' + + 'File: ${file.name}' + + '-(${$.bytesToString(file.size)}, ${file.width}x${file.height}, ${file.tag})' + + '
' + ) %> else if file - fileSize = $.bytesToString file.size - fileThumb = file.turl if file.isSpoiler - fileSize = "Spoiler Image, #{fileSize}" - unless isArchived - fileThumb = "#{staticPath}spoiler" - if spoilerRange = Build.spoilerRange[boardID] - # Randomize the spoiler image. - fileThumb += "-#{boardID}" + Math.floor 1 + spoilerRange * Math.random() - fileThumb += '.png' - file.twidth = file.theight = 100 - - imgSrc = if boardID is 'f' - '' + shortFilename = 'Spoiler Image' + if spoilerRange = Build.spoilerRange[boardID] + # Randomize the spoiler image. + fileThumb = "//s.4cdn.org/image/spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png" + else + fileThumb = '//s.4cdn.org/image/spoiler.png' + file.twidth = file.theight = 100 else - "" + - "#{fileSize}" + - "" + shortFilename = Build.shortFilename file.name, !isOP + fileThumb = file.turl - # html -> text, translate WebKit's %22s into "s - a = $.el 'a', innerHTML: file.name - filename = a.textContent.replace /%22/g, '"' - # shorten filename, get html - a.textContent = Build.shortFilename filename - shortFilename = a.innerHTML - # get html - a.textContent = filename - filename = a.innerHTML.replace /'/g, ''' + fileSize = $.bytesToString file.size + fileDims = if file.url[-4..] is '.pdf' then 'PDF' else "#{file.width}x#{file.height}" - fileDims = if file.name[-3..] is 'pdf' then 'PDF' else "#{file.width}x#{file.height}" - fileInfo = "
File: " + - "#{if file.isSpoiler then 'Spoiler Image' else shortFilename}" + - " (#{fileSize}, #{fileDims})
" + fileLink = if file.isSpoiler or file.name is shortFilename + <%= html('${shortFilename}') %> + else + <%= html('${shortFilename}') %> - fileHTML = "
#{fileInfo}#{imgSrc}
" + fileText = if file.isSpoiler + <%= html('
File: &{fileLink} (${fileSize}, ${fileDims})
') %> + else + <%= html('
File: &{fileLink} (${fileSize}, ${fileDims})
') %> + + <%= html( + '&{fileText}' + + '' + + '${fileSize}' + + '' + ) %> + + fileBlock = if file + <%= html('
&{fileCont}
') %> else - fileHTML = '' + <%= html('') %> - sticky = if isSticky - " " - else - '' - closed = if isClosed - " " - else - '' + ### Whole Post ### - if isOP and g.VIEW is 'index' - pageNum = Index.liveThreadData.keys.indexOf("#{postID}") // Index.threadsNumPerPage + 1 - pageIcon = " Page #{pageNum}" - replyLink = "   [Reply]" + highlightPost = if capcode is 'admin_highlight' then ' highlightPost' else '' + + message = <%= html('
&{comment}
') %> + + wholePost = if isOP + <%= html( + '
' + + '&{fileBlock}&{postInfo}&{message}' + + '
' + ) %> else - pageIcon = '' - replyLink = '' + <%= html( + '
>>
' + + '
' + + '&{postInfo}&{fileBlock}&{message}' + + '
' + ) %> container = $.el 'div', - id: "pc#{postID}" className: "postContainer #{if isOP then 'op' else 'reply'}Container" - innerHTML: \ - (if isOP then '' else "
>>
") + - "
" + + id: "pc#{postID}" + $.extend container, wholePost - (if isOP then fileHTML else '') + - - "
" + - " " + - "#{subject or ''} " + - "" + - emailStart + - "#{name or ''}" + tripcode + - capcodeStart + emailEnd + capcodeIcon + userID + flag + - ' ' + - "#{date} " + - "" + - "No." + - "#{postID}" + - pageIcon + sticky + closed + replyLink + - '' + - '
' + - - (if isOP then '' else fileHTML) + - - "
#{comment or ''}
" + - - '
' - - # Fix quote pathnames in index or cross-{board,thread} posts + # Fix pathnames for quote in $$ '.quotelink', container href = quote.getAttribute 'href' - continue unless href[0] is '#' - quote.href = Build.path boardID, threadID, href[2..] + if (href[0] is '#') and !(Build.sameThread boardID, threadID) + quote.href = (Build.path boardID, threadID) + href + else if (match = href.match /^\/([^\/]+)\/thread\/(\d+)/) and (Build.sameThread match[1], match[2]) + quote.href = href.match(/(#[^#]*)?$/)[0] or '#' container @@ -284,14 +312,14 @@ Build = pageCount = Index.liveThreadData.keys.indexOf("#{thread.ID}") // Index.threadsNumPerPage + 1 subject = if thread.OP.info.subject - "
#{thread.OP.nodes.subject.innerHTML}
" + <%= html("
${thread.OP.nodes.subject.innerHTML}
") %> else '' comment = thread.OP.nodes.comment.innerHTML.replace /(
\s*){2,}/g, '
' root = $.el 'div', className: 'catalog-thread' - innerHTML: <%= importHTML('Features/Thread-catalog-view') %> + <%= importHTML('Features/Thread-catalog-view') %> root.dataset.fullID = thread.fullID $.addClass root, 'pinned' if thread.isPinned diff --git a/src/General/Get.coffee b/src/General/Get.coffee index ffe5f2bb5..d93f77280 100755 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -3,7 +3,7 @@ Get = {OP} = thread excerpt = OP.info.subject?.trim() or OP.info.comment.replace(/\n+/g, ' // ') or - OP.getNameBlock() + OP.info.nameBlock if excerpt.length > 70 excerpt = "#{excerpt[...67]}..." "/#{thread.board}/ - #{excerpt}" diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 6253ac504..9a255aa00 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -85,11 +85,11 @@ Index = @pagelist = $.el 'div', className: 'pagelist' hidden: true - innerHTML: <%= importHTML('Features/Index-pagelist') %> + <%= importHTML('Features/Index-pagelist') %> @navLinks = $.el 'div', className: 'navLinks' - innerHTML: <%= importHTML('Features/Index-navlinks') %> + <%= importHTML('Features/Index-navlinks') %> @timeEl = $ 'time#index-last-refresh', @navLinks @searchInput = $ '#index-search', @navLinks diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 308174ed7..c4f19550e 100755 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -53,7 +53,7 @@ Settings = Settings.dialog = dialog = $.el 'div', id: 'appchanx-settings' class: 'dialog' - innerHTML: <%= importHTML('Settings/Settings') %> + <%= importHTML('Settings/Settings') %> Settings.overlay = overlay = $.el 'div', id: 'overlay' @@ -189,7 +189,7 @@ Settings = $.clear -> window.location.reload() if confirm 'Reset successful. Reload now?' filter: (section) -> - section.innerHTML = <%= importHTML('Settings/Filter-select') %> + $.extend section, <%= importHTML('Settings/Filter-select') %> select = $ 'select', section $.on select, 'change', Settings.selectFilter Settings.selectFilter.call select @@ -207,10 +207,10 @@ Settings = $.on ta, 'change', $.cb.value $.add div, ta return - div.innerHTML = <%= importHTML('Settings/Filter-guide') %> + $.extend div, <%= importHTML('Settings/Filter-guide') %> sauce: (section) -> - section.innerHTML = <%= importHTML('Settings/Sauce') %> + $.extend section, <%= importHTML('Settings/Sauce') %> ta = $ 'textarea', section $.get 'sauces', Conf['sauces'], (item) -> # XXX remove .replace func after 31-7-2013 (v1 transitioning) @@ -229,7 +229,7 @@ Settings = $.on ta, 'change', $.cb.value advanced: (section) -> - section.innerHTML = <%= importHTML('Settings/Advanced') %> + $.extend section, <%= importHTML('Settings/Advanced') %> items = {} inputs = {} for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'] @@ -388,7 +388,7 @@ Settings = CustomCSS.update() keybinds: (section) -> - section.innerHTML = <%= importHTML('Settings/Keybinds') %> + $.extend section, <%= importHTML('Settings/Keybinds') %> tbody = $ 'tbody', section items = {} diff --git a/src/General/lib/post.class b/src/General/lib/post.class index 5cc887c19..957a01ccf 100755 --- a/src/General/lib/post.class +++ b/src/General/lib/post.class @@ -14,6 +14,7 @@ class Post root: root post: post info: info + nameBlock: $ '.nameBlock', info comment: $ '.postMessage', post links: [] quotelinks: [] @@ -25,6 +26,10 @@ class Post @thread.isClosed = !!$ '.closedIcon', info @info = {} + @info.nameBlock = if Conf['Anonymize'] + 'Anonymous' + else + @nodes.nameBlock.textContent.trim() if subject = $ '.subject', info @nodes.subject = subject @info.subject = subject.textContent