diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2e7c876..f96cf85b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ **zixaphir**: - Make new archive selection not depend on a JSON file - Remove some code that sends user errors back to us (we didn't have a working link anyway) +- Add board selection to archiver options +- Fix bug where image hover would close when hitting Enter while typing +- Add `Quoted Title` option which adds (!) text to title when user is quoted ### v2.0.3 *2013-05-10* diff --git a/LICENSE b/LICENSE index 698fb155d..c91e0d1ca 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* appchan x - Version 2.0.3 - 2013-05-14 +* appchan x - Version 2.0.3 - 2013-05-15 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE diff --git a/builds/appchan-x.js b/builds/appchan-x.js index 5a20bbf3d..a5008af56 100644 --- a/builds/appchan-x.js +++ b/builds/appchan-x.js @@ -20,7 +20,7 @@ // ==/UserScript== /* -* appchan x - Version 2.0.3 - 2013-05-14 +* appchan x - Version 2.0.3 - 2013-05-15 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -202,7 +202,8 @@ 'Remember QR Size': [false, 'Remember the size of the quick reply\'s comment field.'], 'Cooldown': [true, 'Indicate the remaining time before posting again.'], 'Cooldown Prediction': [true, 'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'], - 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'] + 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'], + 'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], @@ -214,7 +215,7 @@ 'Quote Highlighting': [true, 'Highlight the previewed post.'], 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], - 'Quoted Title': ['Change the page title to reflect you\'ve been quoted.', false], + 'Quoted Title': [false, 'Change the page title to reflect you\'ve been quoted.'], 'Highlight Posts Quoting You': [false, 'Highlights any posts that contain a quote to your post.'], 'Highlight Own Posts': [false, 'Highlights own posts if Mark Quotes of You is enabled.'], 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], @@ -2579,7 +2580,7 @@ "Timestamps": "rgb(100,100,100)", "Warnings": "rgb(215,0,0)", "Shadow Color": "rgba(0,0,0,.1)", - "Custom CSS": ".thread {\npadding: 2px;\nbox-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3) inset, rgba(70,70,70,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2) inset, rgba(70,70,70,.3) 1px 1px;\n}\n#qr .selectrice {\nbox-shadow: none;\n}\n#header-bar {\npadding: 1px 3px;\n}\n.dialog {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2);\n}\n.thread > .replyContainer\n.threadContainer > .replyContainer {\npadding-left: 20px;\n}\n.threadContainer {\nborder: none;\nbox-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px;\npadding: 2px 0;\nmargin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\ntransition:background .2s,box-shadow .2s;\n}" + "Custom CSS": ".thread {\npadding: 2px;\nbox-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3) inset, rgba(70,70,70,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2) inset, rgba(70,70,70,.3) 1px 1px;\n}\n#qr .selectrice {\nbox-shadow: none;\n}\n#header-bar {\npadding: 1px 3px;\n}\n.dialog {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2);\n}\n.threadContainer {\nborder: none;\nbox-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px;\npadding: 2px 0;\nmargin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\ntransition:background .2s,box-shadow .2s;\n}" }, "Frost": { "Author": "Zixaphir", @@ -2630,7 +2631,7 @@ "Timestamps": "rgb(100,100,100)", "Warnings": "rgb(215,0,0)", "Shadow Color": "rgba(0,0,0,.1)", - "Custom CSS": ".thread {\n padding: 2px;\n box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.8) 1px 1px;\n}\n#qr .selectrice {\n box-shadow: none;\n}\n#header-bar {\n padding: 1px 3px;\n}\n.dialog {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n.thread > .replyContainer\n.threadContainer > .replyContainer {\n padding-left: 20px;\n}\n.threadContainer {\n border: none;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n padding: 2px 0;\n margin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\n transition: background .2s,box-shadow .2s;\n}" + "Custom CSS": ".thread {\n padding: 2px;\n box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.8) 1px 1px;\n}\n#qr .selectrice {\n box-shadow: none;\n}\n#header-bar {\n padding: 1px 3px;\n}\n.dialog {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n.threadContainer {\n border: none;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n padding: 2px 0;\n margin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\n transition: background .2s,box-shadow .2s;\n}" } }; @@ -3621,12 +3622,18 @@ }; DataBoard.prototype.clean = function() { - var boardID, now; + var boardID, now, val, _ref; - for (boardID in this.data.boards) { - this.deleteIfEmpty({ - boardID: boardID - }); + _ref = this.data.boards; + for (boardID in _ref) { + val = _ref[boardID]; + if (!val) { + delete this.data.boards[boardID]; + } else { + this.deleteIfEmpty({ + boardID: boardID + }); + } } now = Date.now(); if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { @@ -4925,6 +4932,10 @@ } for (key in Config.filter) { this.filters[key] = []; + if (Conf[key] === void 0) { + $["delete"](key); + continue; + } _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { filter = _ref[_i]; @@ -6975,6 +6986,9 @@ QR.cleanNotifications(); d.activeElement.blur(); $.rmClass(QR.nodes.el, 'dump'); + if (!Conf['Captcha Warning Notifications']) { + $.rmClass(QR.captcha.nodes.input, 'error'); + } if (Conf['QR Shortcut']) { $.toggleClass($('.qr-shortcut'), 'disabled'); } @@ -7020,6 +7034,16 @@ } if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) { QR.captcha.nodes.input.focus(); + if (Conf['Captcha Warning Notifications']) { + QR.notifications.push(new Notification('warning', el)); + } else { + $.addClass(QR.captcha.nodes.input, 'error'); + $.on(QR.captcha.nodes.input, 'keydown', function() { + return $.rmClass(QR.captcha.nodes.input, 'error'); + }); + } + } else { + QR.notifications.push(new Notification('warning', el)); } if (d.hidden) { alert(el.textContent); @@ -8121,13 +8145,13 @@ err = 'No valid captcha.'; } } + QR.cleanNotifications(); if (err) { QR.cooldown.auto = false; QR.status(); QR.error(err); return; } - QR.cleanNotifications(); QR.cooldown.auto = QR.posts.length > 1; if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { QR.hide(); @@ -9847,13 +9871,12 @@ } } Unread.addPosts(posts); - if (!Conf['Scroll to Last Read Post']) { - return; + if (Conf['Scroll to Last Read Post']) { + return Unread.scroll(); } - return Unread.scroll(); }, scroll: function() { - var hash, onload, post, posts, prevID, root; + var hash, post, posts, prevID, root; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; @@ -9870,17 +9893,11 @@ break; } } - onload = function() { - return root.scrollIntoView(false); - }; - } else { - posts = Object.keys(Unread.thread.posts); - post = Unread.thread.posts[posts[posts.length - 1]]; - onload = function() { - return Header.scrollToPost(post.nodes.root); - }; + root.scrollIntoView(false); + return; } - return $.on(window, 'load', onload); + posts = Object.keys(Unread.thread.posts); + return Header.scrollToPost(Unread.thread.posts[posts[posts.length - 1]].nodes.root); }, sync: function() { var lastReadPost; @@ -13120,7 +13137,7 @@ Sauce = { init: function() { - var link, links, _i, _len, _ref; + var err, link, links, _i, _len, _ref; if (g.VIEW === 'catalog' || !Conf['Sauce']) { return; @@ -13132,7 +13149,12 @@ if (link[0] === '#') { continue; } - links.push(this.createSauceLink(link.trim())); + try { + links.push(this.createSauceLink(link.trim())); + } catch (_error) { + err = _error; + continue; + } } if (!links.length) { return; @@ -13546,6 +13568,7 @@ }; } $.get(Conf, function(Conf) { + delete Conf['archives']; data.Conf = Conf; return Settings["export"](now, data); }); @@ -14500,6 +14523,9 @@ Conf = items; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); + if (g.BOARD.ID === 'z') { + return; + } g.VIEW = (function() { switch (pathname[2]) { case 'res': @@ -14698,6 +14724,12 @@ } return; } + try { + localStorage.getItem('4chan-settings'); + } catch (_error) { + err = _error; + new Notification('warning', 'Cookies need to be enabled on 4chan for appchan x to properly function.', 30); + } $.event('4chanXInitFinished'); return Main.checkUpdate(); }, diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index fa7581c01..53052d935 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -20,7 +20,7 @@ // ==/UserScript== /* -* appchan x - Version 2.0.3 - 2013-05-14 +* appchan x - Version 2.0.3 - 2013-05-15 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -203,7 +203,8 @@ 'Remember QR Size': [false, 'Remember the size of the quick reply\'s comment field.'], 'Cooldown': [true, 'Indicate the remaining time before posting again.'], 'Cooldown Prediction': [true, 'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'], - 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'] + 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'], + 'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], @@ -215,7 +216,7 @@ 'Quote Highlighting': [true, 'Highlight the previewed post.'], 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], - 'Quoted Title': ['Change the page title to reflect you\'ve been quoted.', false], + 'Quoted Title': [false, 'Change the page title to reflect you\'ve been quoted.'], 'Highlight Posts Quoting You': [false, 'Highlights any posts that contain a quote to your post.'], 'Highlight Own Posts': [false, 'Highlights own posts if Mark Quotes of You is enabled.'], 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], @@ -2576,7 +2577,7 @@ "Timestamps": "rgb(100,100,100)", "Warnings": "rgb(215,0,0)", "Shadow Color": "rgba(0,0,0,.1)", - "Custom CSS": ".thread {\npadding: 2px;\nbox-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3) inset, rgba(70,70,70,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2) inset, rgba(70,70,70,.3) 1px 1px;\n}\n#qr .selectrice {\nbox-shadow: none;\n}\n#header-bar {\npadding: 1px 3px;\n}\n.dialog {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2);\n}\n.thread > .replyContainer\n.threadContainer > .replyContainer {\npadding-left: 20px;\n}\n.threadContainer {\nborder: none;\nbox-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px;\npadding: 2px 0;\nmargin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\ntransition:background .2s,box-shadow .2s;\n}" + "Custom CSS": ".thread {\npadding: 2px;\nbox-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3) inset, rgba(70,70,70,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2) inset, rgba(70,70,70,.3) 1px 1px;\n}\n#qr .selectrice {\nbox-shadow: none;\n}\n#header-bar {\npadding: 1px 3px;\n}\n.dialog {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2);\n}\n.threadContainer {\nborder: none;\nbox-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px;\npadding: 2px 0;\nmargin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\ntransition:background .2s,box-shadow .2s;\n}" }, "Frost": { "Author": "Zixaphir", @@ -2627,7 +2628,7 @@ "Timestamps": "rgb(100,100,100)", "Warnings": "rgb(215,0,0)", "Shadow Color": "rgba(0,0,0,.1)", - "Custom CSS": ".thread {\n padding: 2px;\n box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.8) 1px 1px;\n}\n#qr .selectrice {\n box-shadow: none;\n}\n#header-bar {\n padding: 1px 3px;\n}\n.dialog {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n.thread > .replyContainer\n.threadContainer > .replyContainer {\n padding-left: 20px;\n}\n.threadContainer {\n border: none;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n padding: 2px 0;\n margin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\n transition: background .2s,box-shadow .2s;\n}" + "Custom CSS": ".thread {\n padding: 2px;\n box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.8) 1px 1px;\n}\n#qr .selectrice {\n box-shadow: none;\n}\n#header-bar {\n padding: 1px 3px;\n}\n.dialog {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n.threadContainer {\n border: none;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n padding: 2px 0;\n margin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\n transition: background .2s,box-shadow .2s;\n}" } }; @@ -3617,12 +3618,18 @@ }; DataBoard.prototype.clean = function() { - var boardID, now; + var boardID, now, val, _ref; - for (boardID in this.data.boards) { - this.deleteIfEmpty({ - boardID: boardID - }); + _ref = this.data.boards; + for (boardID in _ref) { + val = _ref[boardID]; + if (!val) { + delete this.data.boards[boardID]; + } else { + this.deleteIfEmpty({ + boardID: boardID + }); + } } now = Date.now(); if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { @@ -4921,6 +4928,10 @@ } for (key in Config.filter) { this.filters[key] = []; + if (Conf[key] === void 0) { + $["delete"](key); + continue; + } _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { filter = _ref[_i]; @@ -6959,6 +6970,9 @@ QR.cleanNotifications(); d.activeElement.blur(); $.rmClass(QR.nodes.el, 'dump'); + if (!Conf['Captcha Warning Notifications']) { + $.rmClass(QR.captcha.nodes.input, 'error'); + } if (Conf['QR Shortcut']) { $.toggleClass($('.qr-shortcut'), 'disabled'); } @@ -7004,6 +7018,16 @@ } if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) { QR.captcha.nodes.input.focus(); + if (Conf['Captcha Warning Notifications']) { + QR.notifications.push(new Notification('warning', el)); + } else { + $.addClass(QR.captcha.nodes.input, 'error'); + $.on(QR.captcha.nodes.input, 'keydown', function() { + return $.rmClass(QR.captcha.nodes.input, 'error'); + }); + } + } else { + QR.notifications.push(new Notification('warning', el)); } if (d.hidden) { alert(el.textContent); @@ -8131,13 +8155,13 @@ err = 'No valid captcha.'; } } + QR.cleanNotifications(); if (err) { QR.cooldown.auto = false; QR.status(); QR.error(err); return; } - QR.cleanNotifications(); QR.cooldown.auto = QR.posts.length > 1; if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { QR.hide(); @@ -9856,13 +9880,12 @@ } } Unread.addPosts(posts); - if (!Conf['Scroll to Last Read Post']) { - return; + if (Conf['Scroll to Last Read Post']) { + return Unread.scroll(); } - return Unread.scroll(); }, scroll: function() { - var hash, onload, post, posts, prevID, root; + var hash, post, posts, prevID, root; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; @@ -9879,17 +9902,11 @@ break; } } - onload = function() { - return root.scrollIntoView(false); - }; - } else { - posts = Object.keys(Unread.thread.posts); - post = Unread.thread.posts[posts[posts.length - 1]]; - onload = function() { - return Header.scrollToPost(post.nodes.root); - }; + root.scrollIntoView(false); + return; } - return $.on(window, 'load', onload); + posts = Object.keys(Unread.thread.posts); + return Header.scrollToPost(Unread.thread.posts[posts[posts.length - 1]].nodes.root); }, sync: function() { var lastReadPost; @@ -13129,7 +13146,7 @@ Sauce = { init: function() { - var link, links, _i, _len, _ref; + var err, link, links, _i, _len, _ref; if (g.VIEW === 'catalog' || !Conf['Sauce']) { return; @@ -13141,7 +13158,12 @@ if (link[0] === '#') { continue; } - links.push(this.createSauceLink(link.trim())); + try { + links.push(this.createSauceLink(link.trim())); + } catch (_error) { + err = _error; + continue; + } } if (!links.length) { return; @@ -13555,6 +13577,7 @@ }; } $.get(Conf, function(Conf) { + delete Conf['archives']; data.Conf = Conf; return Settings["export"](now, data); }); @@ -14507,6 +14530,9 @@ Conf = items; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); + if (g.BOARD.ID === 'z') { + return; + } g.VIEW = (function() { switch (pathname[2]) { case 'res': @@ -14705,6 +14731,12 @@ } return; } + try { + localStorage.getItem('4chan-settings'); + } catch (_error) { + err = _error; + new Notification('warning', 'Cookies need to be enabled on 4chan for appchan x to properly function.', 30); + } $.event('4chanXInitFinished'); return Main.checkUpdate(); }, diff --git a/builds/crx/script.js b/builds/crx/script.js index 0ecc770bc..bfca6b5b6 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript /* -* appchan x - Version 2.0.3 - 2013-05-14 +* appchan x - Version 2.0.3 - 2013-05-15 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -183,7 +183,8 @@ 'Remember QR Size': [false, 'Remember the size of the quick reply\'s comment field.'], 'Cooldown': [true, 'Indicate the remaining time before posting again.'], 'Cooldown Prediction': [true, 'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'], - 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'] + 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'], + 'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], @@ -195,7 +196,7 @@ 'Quote Highlighting': [true, 'Highlight the previewed post.'], 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'], 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'], - 'Quoted Title': ['Change the page title to reflect you\'ve been quoted.', false], + 'Quoted Title': [false, 'Change the page title to reflect you\'ve been quoted.'], 'Highlight Posts Quoting You': [false, 'Highlights any posts that contain a quote to your post.'], 'Highlight Own Posts': [false, 'Highlights own posts if Mark Quotes of You is enabled.'], 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'], @@ -2556,7 +2557,7 @@ "Timestamps": "rgb(100,100,100)", "Warnings": "rgb(215,0,0)", "Shadow Color": "rgba(0,0,0,.1)", - "Custom CSS": ".thread {\npadding: 2px;\nbox-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3) inset, rgba(70,70,70,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2) inset, rgba(70,70,70,.3) 1px 1px;\n}\n#qr .selectrice {\nbox-shadow: none;\n}\n#header-bar {\npadding: 1px 3px;\n}\n.dialog {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2);\n}\n.thread > .replyContainer\n.threadContainer > .replyContainer {\npadding-left: 20px;\n}\n.threadContainer {\nborder: none;\nbox-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px;\npadding: 2px 0;\nmargin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\ntransition:background .2s,box-shadow .2s;\n}" + "Custom CSS": ".thread {\npadding: 2px;\nbox-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3) inset, rgba(70,70,70,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2) inset, rgba(70,70,70,.3) 1px 1px;\n}\n#qr .selectrice {\nbox-shadow: none;\n}\n#header-bar {\npadding: 1px 3px;\n}\n.dialog {\nbox-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2);\n}\n.threadContainer {\nborder: none;\nbox-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px;\npadding: 2px 0;\nmargin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\nbox-shadow:inset rgba(0,0,0,.2) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\ntransition:background .2s,box-shadow .2s;\n}" }, "Frost": { "Author": "Zixaphir", @@ -2607,7 +2608,7 @@ "Timestamps": "rgb(100,100,100)", "Warnings": "rgb(215,0,0)", "Shadow Color": "rgba(0,0,0,.1)", - "Custom CSS": ".thread {\n padding: 2px;\n box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.8) 1px 1px;\n}\n#qr .selectrice {\n box-shadow: none;\n}\n#header-bar {\n padding: 1px 3px;\n}\n.dialog {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n.thread > .replyContainer\n.threadContainer > .replyContainer {\n padding-left: 20px;\n}\n.threadContainer {\n border: none;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n padding: 2px 0;\n margin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\n transition: background .2s,box-shadow .2s;\n}" + "Custom CSS": ".thread {\n padding: 2px;\n box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n#header-bar,\ninput,\ntextarea,\n.field,\n.inline .op,\n.pagelist,\n.prettyprint,\n.post.reply,\n.rice,\n.selectrice {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.8) 1px 1px;\n}\n#qr .selectrice {\n box-shadow: none;\n}\n#header-bar {\n padding: 1px 3px;\n}\n.dialog {\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n}\n.threadContainer {\n border: none;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px;\n padding: 2px 0;\n margin-left: 20px;\n}\ninput[type=password]:hover,\ninput[type=text]:not([disabled]):hover,\ninput#fs_search:hover,\ninput.field:hover,\n.webkit select:hover,\ntextarea:hover,\n#appchanx-settings input:not([type=checkbox]):hover {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\ninput[type=password]:focus,\ninput[type=text]:focus,\ninput#fs_search:focus,\ninput.field:focus,\n.webkit select:focus,\ntextarea:focus,\n#appchanx-settings input:focus {\n box-shadow: inset rgba(0,0,0,.1) 0 1px 2px;\n}\nbutton,\ninput,\ntextarea,\n.rice {\n transition: background .2s,box-shadow .2s;\n}" } }; @@ -3618,12 +3619,18 @@ }; DataBoard.prototype.clean = function() { - var boardID, now; + var boardID, now, val, _ref; - for (boardID in this.data.boards) { - this.deleteIfEmpty({ - boardID: boardID - }); + _ref = this.data.boards; + for (boardID in _ref) { + val = _ref[boardID]; + if (!val) { + delete this.data.boards[boardID]; + } else { + this.deleteIfEmpty({ + boardID: boardID + }); + } } now = Date.now(); if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { @@ -4922,6 +4929,10 @@ } for (key in Config.filter) { this.filters[key] = []; + if (Conf[key] === void 0) { + $["delete"](key); + continue; + } _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { filter = _ref[_i]; @@ -6961,6 +6972,9 @@ QR.cleanNotifications(); d.activeElement.blur(); $.rmClass(QR.nodes.el, 'dump'); + if (!Conf['Captcha Warning Notifications']) { + $.rmClass(QR.captcha.nodes.input, 'error'); + } if (Conf['QR Shortcut']) { $.toggleClass($('.qr-shortcut'), 'disabled'); } @@ -7006,6 +7020,16 @@ } if (QR.captcha.isEnabled && /captcha|verification/i.test(el.textContent)) { QR.captcha.nodes.input.focus(); + if (Conf['Captcha Warning Notifications']) { + QR.notifications.push(new Notification('warning', el)); + } else { + $.addClass(QR.captcha.nodes.input, 'error'); + $.on(QR.captcha.nodes.input, 'keydown', function() { + return $.rmClass(QR.captcha.nodes.input, 'error'); + }); + } + } else { + QR.notifications.push(new Notification('warning', el)); } if (d.hidden) { alert(el.textContent); @@ -8108,13 +8132,13 @@ err = 'No valid captcha.'; } } + QR.cleanNotifications(); if (err) { QR.cooldown.auto = false; QR.status(); QR.error(err); return; } - QR.cleanNotifications(); QR.cooldown.auto = QR.posts.length > 1; if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { QR.hide(); @@ -9833,13 +9857,12 @@ } } Unread.addPosts(posts); - if (!Conf['Scroll to Last Read Post']) { - return; + if (Conf['Scroll to Last Read Post']) { + return Unread.scroll(); } - return Unread.scroll(); }, scroll: function() { - var hash, onload, post, posts, prevID, root; + var hash, post, posts, prevID, root; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; @@ -9856,17 +9879,11 @@ break; } } - onload = function() { - return root.scrollIntoView(false); - }; - } else { - posts = Object.keys(Unread.thread.posts); - post = Unread.thread.posts[posts[posts.length - 1]]; - onload = function() { - return Header.scrollToPost(post.nodes.root); - }; + root.scrollIntoView(false); + return; } - return $.on(window, 'load', onload); + posts = Object.keys(Unread.thread.posts); + return Header.scrollToPost(Unread.thread.posts[posts[posts.length - 1]].nodes.root); }, sync: function() { var lastReadPost; @@ -13111,7 +13128,7 @@ Sauce = { init: function() { - var link, links, _i, _len, _ref; + var err, link, links, _i, _len, _ref; if (g.VIEW === 'catalog' || !Conf['Sauce']) { return; @@ -13123,7 +13140,12 @@ if (link[0] === '#') { continue; } - links.push(this.createSauceLink(link.trim())); + try { + links.push(this.createSauceLink(link.trim())); + } catch (_error) { + err = _error; + continue; + } } if (!links.length) { return; @@ -13537,6 +13559,7 @@ }; } $.get(Conf, function(Conf) { + delete Conf['archives']; data.Conf = Conf; return Settings["export"](now, data); }); @@ -14491,6 +14514,9 @@ Conf = items; pathname = location.pathname.split('/'); g.BOARD = new Board(pathname[1]); + if (g.BOARD.ID === 'z') { + return; + } g.VIEW = (function() { switch (pathname[2]) { case 'res': @@ -14689,6 +14715,12 @@ } return; } + try { + localStorage.getItem('4chan-settings'); + } catch (_error) { + err = _error; + new Notification('warning', 'Cookies need to be enabled on 4chan for appchan x to properly function.', 30); + } $.event('4chanXInitFinished'); return Main.checkUpdate(); }, diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee index ca2ae7c29..382ef4e52 100644 --- a/src/Filtering/Filter.coffee +++ b/src/Filtering/Filter.coffee @@ -8,6 +8,11 @@ Filter = for key of Config.filter @filters[key] = [] + if Conf[key] is undefined + # XXX hopefully tmp fix for the rare people getting this mysterious error: + # "Filter" initialization crashed. TypeError: Cannot call method 'split' of undefined + $.delete key + continue for filter in Conf[key].split '\n' continue if filter[0] is '#' @@ -272,4 +277,4 @@ Filter = ta = $ 'textarea', section tl = ta.textLength ta.setSelectionRange tl, tl - ta.focus() \ No newline at end of file + ta.focus() diff --git a/src/General/Config.coffee b/src/General/Config.coffee index dac35ebfd..f39e9730b 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -289,6 +289,10 @@ Config = true 'Show notifications on successful post creation or file uploading.' ] + 'Captcha Warning Notifications': [ + true + 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.' + ] 'Quote Links': 'Quote Backlinks': [ @@ -328,8 +332,8 @@ Config = 'Add \'(You)\' to quotes linking to your posts.' ] 'Quoted Title': [ - 'Change the page title to reflect you\'ve been quoted.' false + 'Change the page title to reflect you\'ve been quoted.' ] 'Highlight Posts Quoting You': [ false diff --git a/src/General/Globals.coffee b/src/General/Globals.coffee index 58b957631..dbe01f9f8 100644 --- a/src/General/Globals.coffee +++ b/src/General/Globals.coffee @@ -2884,10 +2884,6 @@ textarea, .dialog { box-shadow: 0px 0px 4px rgba(0, 0, 0, .3) inset, 1px 1px 5px rgba(0,0,0,0.2); } -.thread > .replyContainer -.threadContainer > .replyContainer { - padding-left: 20px; -} .threadContainer { border: none; box-shadow: 0px 0px 3px rgba(0, 0, 0, .3) inset, rgba(70,70,70,.3) 1px 1px; @@ -2994,10 +2990,6 @@ textarea, .dialog { box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px; } -.thread > .replyContainer -.threadContainer > .replyContainer { - padding-left: 20px; -} .threadContainer { border: none; box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1) inset, rgba(255,255,255,.5) 1px 1px; diff --git a/src/General/Main.coffee b/src/General/Main.coffee index a26b046a7..c49c04c85 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -32,6 +32,7 @@ Main = pathname = location.pathname.split '/' g.BOARD = new Board pathname[1] + return if g.BOARD.ID is 'z' g.VIEW = switch pathname[2] when 'res' @@ -207,6 +208,10 @@ Main = return + try + localStorage.getItem '4chan-settings' + catch err + new Notification 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30 $.event '4chanXInitFinished' Main.checkUpdate() diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index ca69b3c00..032f65953 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -176,6 +176,8 @@ Settings = Conf[db] = boards: {} # Make sure to export the most recent data. $.get Conf, (Conf) -> + # XXX don't export archives. + delete Conf['archives'] data.Conf = Conf Settings.export now, data return diff --git a/src/General/css/tomorrow.css b/src/General/css/tomorrow.css index dfe391e33..37b74e775 100644 --- a/src/General/css/tomorrow.css +++ b/src/General/css/tomorrow.css @@ -35,12 +35,6 @@ background-color: #282A2E; border-color: #111; } -:root.tomorrow #qr select { - color: #C5C8C6; -} -:root.tomorrow #qr option { - color: #000; -} :root.tomorrow .qr-preview { background-color: rgba(255, 255, 255, .15); } diff --git a/src/General/lib/databoard.class b/src/General/lib/databoard.class index 056018ba0..efa033681 100644 --- a/src/General/lib/databoard.class +++ b/src/General/lib/databoard.class @@ -59,8 +59,13 @@ class DataBoard val or defaultValue clean: -> - for boardID of @data.boards - @deleteIfEmpty {boardID} + for boardID, val of @data.boards + # XXX tmp fix for users that had the `null` + # value for a board with the Unread features: + unless val + delete @data.boards[boardID] + else + @deleteIfEmpty {boardID} now = Date.now() if (@data.lastChecked or 0) < now - 2 * $.HOUR diff --git a/src/Miscellaneous/Sauce.coffee b/src/Miscellaneous/Sauce.coffee index fe57249d9..916c178f9 100644 --- a/src/Miscellaneous/Sauce.coffee +++ b/src/Miscellaneous/Sauce.coffee @@ -5,7 +5,11 @@ Sauce = links = [] for link in Conf['sauces'].split '\n' continue if link[0] is '#' - links.push @createSauceLink link.trim() + try + links.push @createSauceLink link.trim() + catch err + # Don't add random text plz. + continue return unless links.length @links = links @link = $.el 'a', target: '_blank' @@ -39,4 +43,4 @@ Sauce = for link in Sauce.links # \u00A0 is nbsp nodes.push $.tn('\u00A0'), link @, Sauce.link.cloneNode true - $.add @file.info, nodes \ No newline at end of file + $.add @file.info, nodes diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 286656056..9dae14212 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -30,8 +30,7 @@ Unread = for ID, post of Unread.thread.posts posts.push post if post.isReply Unread.addPosts posts - return unless Conf['Scroll to Last Read Post'] - Unread.scroll() + Unread.scroll() if Conf['Scroll to Last Read Post'] scroll: -> # Let the header's onload callback handle it. @@ -44,15 +43,11 @@ Unread = break if prevID is post.ID prevID = post.ID break unless post.isHidden - onload = -> root.scrollIntoView false - else - # Scroll to the last read post. - posts = Object.keys Unread.thread.posts - post = Unread.thread.posts[posts[posts.length - 1]] - onload = -> Header.scrollToPost post.nodes.root - # Prevent the browser to scroll back to - # the previous scroll location on page load. - $.on window, 'load', onload + root.scrollIntoView false + return + # Scroll to the last read post. + posts = Object.keys Unread.thread.posts + Header.scrollToPost Unread.thread.posts[posts[posts.length - 1]].nodes.root sync: -> lastReadPost = Unread.db.get diff --git a/src/Posting/QuickReply.coffee b/src/Posting/QuickReply.coffee index a8b959ab2..17a4468f8 100644 --- a/src/Posting/QuickReply.coffee +++ b/src/Posting/QuickReply.coffee @@ -87,6 +87,8 @@ QR = QR.cleanNotifications() d.activeElement.blur() $.rmClass QR.nodes.el, 'dump' + unless Conf['Captcha Warning Notifications'] + $.rmClass QR.captcha.nodes.input, 'error' if Conf['QR Shortcut'] $.toggleClass $('.qr-shortcut'), 'disabled' for i in QR.posts @@ -125,6 +127,14 @@ QR = if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent # Focus the captcha input on captcha error. QR.captcha.nodes.input.focus() + if Conf['Captcha Warning Notifications'] + QR.notifications.push new Notification 'warning', el + else + $.addClass QR.captcha.nodes.input, 'error' + $.on QR.captcha.nodes.input, 'keydown', -> + $.rmClass QR.captcha.nodes.input, 'error' + else + QR.notifications.push new Notification 'warning', el alert el.textContent if d.hidden QR.notifications.push new Notification 'warning', el @@ -1046,13 +1056,13 @@ QR = {challenge, response} = QR.captcha.getOne() err = 'No valid captcha.' unless response + QR.cleanNotifications() if err # stop auto-posting QR.cooldown.auto = false QR.status() QR.error err return - QR.cleanNotifications() # Enable auto-posting if we have stuff to post, disable it otherwise. QR.cooldown.auto = QR.posts.length > 1