Merge branch 'v3' of git://github.com/MayhemYDG/4chan-x into v3

Conflicts:
	CHANGELOG.md
	LICENSE
	css/style.css
	html/Monitoring/ThreadWatcher.html
	json/archives.json
	package.json
	src/General/Config.coffee
	src/General/Header.coffee
	src/General/Main.coffee
	src/General/img/favicons/empty.gif
	src/Linkification/Linkify.coffee
	src/Miscellaneous/ExpandComment.coffee
	src/Monitoring/Favicon.coffee
	src/Monitoring/ThreadWatcher.coffee
	src/Monitoring/Unread.coffee
	src/Posting/QuickReply.coffee
This commit is contained in:
Zixaphir 2013-08-22 12:10:33 -07:00
commit ccc0335b79
13 changed files with 308 additions and 301 deletions

View File

@ -1,3 +1,8 @@
**MayhemYDG**:
- New desktop notification:
- The QR will now warn you when you are running low on cached captchas while auto-posting.
### v1.2.35
*2013-08-20*

View File

@ -30,6 +30,7 @@ module.exports = (grunt) ->
'src/Linkification/**/*'
'src/Posting/**/*'
'src/Images/**/*'
'src/Linkification/**/*'
'src/Menu/**/*'
'src/Monitoring/**/*'
'src/Archive/**/*'

View File

@ -1,5 +1,5 @@
/*
* 4chan X - Version 1.2.35 - 2013-08-20
* 4chan X - Version 1.2.35 - 2013-08-22
*
* Licensed under the MIT license.
* https://github.com/seaweedchan/4chan-x/blob/master/LICENSE

View File

@ -22,7 +22,7 @@
// ==/UserScript==
/*
* 4chan X - Version 1.2.35 - 2013-08-20
* 4chan X - Version 1.2.35 - 2013-08-22
*
* Licensed under the MIT license.
* https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
@ -945,6 +945,7 @@
Post.prototype.parseComment = function() {
var bq, i, node, nodes, text;
this.nodes.comment.normalize();
bq = this.nodes.comment.cloneNode(true);
nodes = $$('.abbr, .capcodeReplies, .exif, b', bq);
i = 0;
@ -1872,7 +1873,7 @@
return;
}
el = $.el('span', {
innerHTML: "Desktop notification permissions are not granted:<br>\n<button>Authorize</button> or <button>Disable</button>"
innerHTML: "Desktop notification permissions are not granted.\n[<a href='https://github.com/MayhemYDG/4chan-x/wiki/FAQ#desktop-notifications' target=_blank>FAQ</a>]<br>\n<button>Authorize</button> or <button>Disable</button>"
});
_ref = $$('button', el), authorize = _ref[0], disable = _ref[1];
$.on(authorize, 'click', function() {
@ -5505,7 +5506,7 @@
}
e.preventDefault();
QR.open();
QR.fileInput(e.dataTransfer.files);
QR.handleFiles(e.dataTransfer.files);
return $.addClass(QR.nodes.el, 'dump');
},
paste: function(e) {
@ -5515,84 +5516,72 @@
_ref = e.clipboardData.items;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
if (item.kind === 'file') {
blob = item.getAsFile();
blob.name = 'file';
if (blob.type) {
blob.name += '.' + blob.type.split('/')[1];
}
files.push(blob);
if (!(item.kind === 'file')) {
continue;
}
blob = item.getAsFile();
blob.name = 'file';
if (blob.type) {
blob.name += '.' + blob.type.split('/')[1];
}
files.push(blob);
}
if (!files.length) {
return;
}
QR.open();
return QR.fileInput(files);
QR.handleFiles(files);
return $.addClass(QR.nodes.el, 'dump');
},
openFileInput: function(e) {
e.stopPropagation();
if (e.shiftKey && e.type === 'click') {
return QR.selected.rmFile();
}
if (e.ctrlKey && e.type === 'click') {
$.addClass(QR.nodes.filename, 'edit');
QR.nodes.filename.focus();
return $.on(QR.nodes.filename, 'blur', function() {
return $.rmClass(QR.nodes.filename, 'edit');
});
}
if (e.target.nodeName === 'INPUT' || (e.keyCode && ![32, 13].contains(e.keyCode)) || e.ctrlKey) {
return;
}
e.preventDefault();
return QR.nodes.fileInput.click();
},
fileInput: function(files) {
var file, length, max, post, _i, _len;
handleFiles: function(files) {
var file, isSingle, max, _i, _len;
if (this instanceof Element) {
if (this !== QR) {
files = __slice.call(this.files);
QR.nodes.fileInput.value = null;
this.value = null;
}
length = files.length;
if (!length) {
if (!files.length) {
return;
}
max = QR.nodes.fileInput.max;
isSingle = files.length === 1;
QR.cleanNotifications();
if (length === 1) {
file = files[0];
if (/^text/.test(file.type)) {
QR.selected.pasteText(file);
} else if (file.size > max) {
QR.error("File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
} else if (!QR.mimeTypes.contains(file.type)) {
QR.error('Unsupported file type.');
} else {
QR.selected.setFile(file);
}
return;
}
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
if (/^text/.test(file.type)) {
if ((post = QR.posts[QR.posts.length - 1]).com) {
post = new QR.post();
}
post.pasteText(file);
} else if (file.size > max) {
QR.error("" + file.name + ": File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
} else if (!QR.mimeTypes.contains(file.type)) {
QR.error("" + file.name + ": Unsupported file type.");
} else {
if ((post = QR.posts[QR.posts.length - 1]).file) {
post = new QR.post();
}
post.setFile(file);
}
QR.handleFile(file, isSingle, max);
}
return $.addClass(QR.nodes.el, 'dump');
if (!isSingle) {
return $.addClass(QR.nodes.el, 'dump');
}
},
handleFile: function(file, isSingle, max) {
var post;
if (file.size > max) {
QR.error("" + file.name + ": File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
return;
} else if (!QR.mimeTypes.contains(file.type)) {
if (!/^text/.test(file.type)) {
QR.error("" + file.name + ": Unsupported file type.");
return;
}
if (isSingle) {
post = QR.selected;
} else if ((post = QR.posts[QR.posts.length - 1]).com) {
post = new QR.post();
}
post.pasteText(file);
return;
}
if (isSingle) {
post = QR.selected;
} else if ((post = QR.posts[QR.posts.length - 1]).file) {
post = new QR.post();
}
return post.setFile(file);
},
openFileInput: function() {
return QR.nodes.fileInput.click();
},
posts: [],
post: (function() {
@ -5892,7 +5881,8 @@
return reader.readAsText(file);
};
_Class.prototype.dragStart = function() {
_Class.prototype.dragStart = function(e) {
e.dataTransfer.setDragImage(this, e.layerX, e.layerY);
return $.addClass(this, 'drag');
};
@ -6198,7 +6188,7 @@
$.on(nodes.spoiler, 'change', function() {
return QR.selected.nodes.spoiler.click();
});
$.on(nodes.fileInput, 'change', QR.fileInput);
$.on(nodes.fileInput, 'change', QR.handleFiles);
items = ['name', 'email', 'sub', 'com', 'filename'];
i = 0;
while (name = items[i++]) {
@ -6344,7 +6334,7 @@
return QR.status();
},
response: function() {
var URL, ban, board, err, h1, isReply, m, post, postID, req, resDoc, threadID, _, _ref, _ref1;
var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1;
req = QR.req;
delete QR.req;
@ -6408,7 +6398,22 @@
threadID: threadID,
postID: postID
});
QR.cooldown.auto = QR.posts.length > 1 && isReply;
postsCount = QR.posts.length;
QR.cooldown.auto = postsCount > 1 && isReply;
if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) {
notif = new Notification('Quick reply warning', {
body: "You are running low on cached captchas. Cache count: " + captchasCount + ".",
icon: Favicon.logo
});
notif.onclick = function() {
QR.open();
QR.captcha.nodes.input.focus();
return window.focus();
};
setTimeout(function() {
return notif.close();
}, 7 * $.SECOND);
}
if (!(Conf['Persistent QR'] || QR.cooldown.auto)) {
QR.close();
} else {
@ -7367,7 +7372,6 @@
return Favicon.unreadY = Favicon.unreadNSFWY;
}
},
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg=='
};
@ -8168,7 +8172,7 @@
return div;
},
refresh: function() {
var boardID, data, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
var boardID, data, helper, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
nodes = [];
_ref = ThreadWatcher.getAll();
@ -8187,7 +8191,9 @@
boardID: thread.board.ID,
threadID: threadID
});
$[watched ? 'addClass' : 'rmClass'](toggler, 'watched');
helper = watched ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
$[helper[0]](toggler, 'watched');
toggler.title = "" + helper[1] + " Thread";
}
_ref3 = ThreadWatcher.menu.refreshers;
for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) {
@ -8390,7 +8396,7 @@
Unread = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon']) {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) {
return;
}
this.db = new DataBoard('lastReadPosts', this.sync);
@ -8432,18 +8438,18 @@
}
}
Unread.addPosts(posts);
if (Conf['Scroll to Last Read Post']) {
return Unread.scroll();
}
return Unread.scroll();
},
scroll: function() {
var checkPosition, hash, onload, post, posts, root;
if (!Conf['Scroll to Last Read Post']) {
return;
}
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
}
if (Unread.posts.length) {
post = Unread.posts[0];
if (post = Unread.posts[0]) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) {
break;
@ -8467,10 +8473,7 @@
};
}
checkPosition = function(target) {
var height, top, _ref;
_ref = target.getBoundingClientRect(), top = _ref.top, height = _ref.height;
return top + height - doc.clientHeight > 0;
return target.getBoundingClientRect().bottom > doc.clientHeight;
};
return $.on(window, 'load', onload);
},
@ -8488,7 +8491,9 @@
Unread.lastReadPost = lastReadPost;
Unread.readArray(Unread.posts);
Unread.readArray(Unread.postsQuotingYou);
Unread.setLine();
if (Conf['Unread Line']) {
Unread.setLine();
}
return Unread.update();
},
addPosts: function(posts) {
@ -8533,7 +8538,6 @@
}
Unread.postsQuotingYou.push(post);
Unread.openNotification(post);
return;
}
},
openNotification: function(post) {
@ -8553,7 +8557,7 @@
};
return setTimeout(function() {
return notif.close();
}, 5 * $.SECOND);
}, 7 * $.SECOND);
},
onUpdate: function(e) {
if (e.detail[404]) {
@ -8590,7 +8594,7 @@
return arr.splice(0, i);
},
read: $.debounce(50, function(e) {
var ID, bottom, height, i, post, posts;
var ID, height, i, post, posts;
if (d.hidden || !Unread.posts.length) {
return;
@ -8599,8 +8603,7 @@
posts = Unread.posts;
i = 0;
while (post = posts[i]) {
bottom = post.nodes.root.getBoundingClientRect().bottom;
if (bottom < height) {
if (post.nodes.root.getBoundingClientRect().bottom < height) {
ID = post.ID;
if (Conf['Mark Quotes of You']) {
if (post.info.yours) {
@ -8618,10 +8621,8 @@
}
i++;
}
if (!Conf['Quote Threading']) {
if (i) {
posts.splice(0, i);
}
if (i && !Conf['Quote Threading']) {
posts.splice(0, i);
}
if (!ID) {
return;
@ -8646,19 +8647,17 @@
});
}),
setLine: function(force) {
var post, root;
var post;
if (!(d.hidden || force === true)) {
return;
}
if (post = Unread.posts[0]) {
root = post.nodes.root;
if (root !== $('.thread > .replyContainer', root.parentNode)) {
return $.before(root, Unread.hr);
}
} else {
if (!(post = Unread.posts[0])) {
return $.rm(Unread.hr);
}
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) {
return $.before(post.nodes.root, Unread.hr);
}
},
update: function() {
var count;
@ -8670,7 +8669,7 @@
if (!Conf['Unread Favicon']) {
return;
}
Favicon.el.href = g.DEAD ? Unread.postsQuotingYou.length ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou.length ? Favicon.unreadY : Favicon.unread : Favicon["default"];
Favicon.el.href = g.DEAD ? Unread.postsQuotingYou[0] ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou[0] ? Favicon.unreadY : Favicon.unread : Favicon["default"];
return $.add(d.head, Favicon.el);
}
};
@ -11291,6 +11290,7 @@
} catch (_error) {
err = _error;
new Notice('warning', 'Cookies need to be enabled on 4chan for 4chan X to properly function.', 30);
Main.disableReports = true;
}
return $.event('4chanXInitFinished');
},

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
* 4chan X - Version 1.2.35 - 2013-08-20
* 4chan X - Version 1.2.35 - 2013-08-22
*
* Licensed under the MIT license.
* https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
@ -954,6 +954,7 @@
Post.prototype.parseComment = function() {
var bq, i, node, nodes, text;
this.nodes.comment.normalize();
bq = this.nodes.comment.cloneNode(true);
nodes = $$('.abbr, .capcodeReplies, .exif, b', bq);
i = 0;
@ -1886,7 +1887,7 @@
return;
}
el = $.el('span', {
innerHTML: "Desktop notification permissions are not granted:<br>\n<button>Authorize</button> or <button>Disable</button>"
innerHTML: "Desktop notification permissions are not granted.\n[<a href='https://github.com/MayhemYDG/4chan-x/wiki/FAQ#desktop-notifications' target=_blank>FAQ</a>]<br>\n<button>Authorize</button> or <button>Disable</button>"
});
_ref = $$('button', el), authorize = _ref[0], disable = _ref[1];
$.on(authorize, 'click', function() {
@ -5179,7 +5180,7 @@
return setTimeout(function() {
notif.onclose = null;
return notif.close();
}, 5 * $.SECOND);
}, 7 * $.SECOND);
},
notifications: [],
cleanNotifications: function() {
@ -5515,7 +5516,7 @@
}
e.preventDefault();
QR.open();
QR.fileInput(e.dataTransfer.files);
QR.handleFiles(e.dataTransfer.files);
return $.addClass(QR.nodes.el, 'dump');
},
paste: function(e) {
@ -5525,84 +5526,72 @@
_ref = e.clipboardData.items;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
if (item.kind === 'file') {
blob = item.getAsFile();
blob.name = 'file';
if (blob.type) {
blob.name += '.' + blob.type.split('/')[1];
}
files.push(blob);
if (!(item.kind === 'file')) {
continue;
}
blob = item.getAsFile();
blob.name = 'file';
if (blob.type) {
blob.name += '.' + blob.type.split('/')[1];
}
files.push(blob);
}
if (!files.length) {
return;
}
QR.open();
return QR.fileInput(files);
QR.handleFiles(files);
return $.addClass(QR.nodes.el, 'dump');
},
openFileInput: function(e) {
e.stopPropagation();
if (e.shiftKey && e.type === 'click') {
return QR.selected.rmFile();
}
if (e.ctrlKey && e.type === 'click') {
$.addClass(QR.nodes.filename, 'edit');
QR.nodes.filename.focus();
return $.on(QR.nodes.filename, 'blur', function() {
return $.rmClass(QR.nodes.filename, 'edit');
});
}
if (e.target.nodeName === 'INPUT' || (e.keyCode && ![32, 13].contains(e.keyCode)) || e.ctrlKey) {
return;
}
e.preventDefault();
return QR.nodes.fileInput.click();
},
fileInput: function(files) {
var file, length, max, post, _i, _len;
handleFiles: function(files) {
var file, isSingle, max, _i, _len;
if (this instanceof Element) {
if (this !== QR) {
files = __slice.call(this.files);
QR.nodes.fileInput.value = null;
this.value = null;
}
length = files.length;
if (!length) {
if (!files.length) {
return;
}
max = QR.nodes.fileInput.max;
isSingle = files.length === 1;
QR.cleanNotifications();
if (length === 1) {
file = files[0];
if (/^text/.test(file.type)) {
QR.selected.pasteText(file);
} else if (file.size > max) {
QR.error("File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
} else if (!QR.mimeTypes.contains(file.type)) {
QR.error('Unsupported file type.');
} else {
QR.selected.setFile(file);
}
return;
}
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
if (/^text/.test(file.type)) {
if ((post = QR.posts[QR.posts.length - 1]).com) {
post = new QR.post();
}
post.pasteText(file);
} else if (file.size > max) {
QR.error("" + file.name + ": File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
} else if (!QR.mimeTypes.contains(file.type)) {
QR.error("" + file.name + ": Unsupported file type.");
} else {
if ((post = QR.posts[QR.posts.length - 1]).file) {
post = new QR.post();
}
post.setFile(file);
}
QR.handleFile(file, isSingle, max);
}
return $.addClass(QR.nodes.el, 'dump');
if (!isSingle) {
return $.addClass(QR.nodes.el, 'dump');
}
},
handleFile: function(file, isSingle, max) {
var post;
if (file.size > max) {
QR.error("" + file.name + ": File too large (file: " + ($.bytesToString(file.size)) + ", max: " + ($.bytesToString(max)) + ").");
return;
} else if (!QR.mimeTypes.contains(file.type)) {
if (!/^text/.test(file.type)) {
QR.error("" + file.name + ": Unsupported file type.");
return;
}
if (isSingle) {
post = QR.selected;
} else if ((post = QR.posts[QR.posts.length - 1]).com) {
post = new QR.post();
}
post.pasteText(file);
return;
}
if (isSingle) {
post = QR.selected;
} else if ((post = QR.posts[QR.posts.length - 1]).file) {
post = new QR.post();
}
return post.setFile(file);
},
openFileInput: function() {
return QR.nodes.fileInput.click();
},
posts: [],
post: (function() {
@ -5896,7 +5885,8 @@
return reader.readAsText(file);
};
_Class.prototype.dragStart = function() {
_Class.prototype.dragStart = function(e) {
e.dataTransfer.setDragImage(this, e.layerX, e.layerY);
return $.addClass(this, 'drag');
};
@ -6194,7 +6184,7 @@
$.on(nodes.spoiler, 'change', function() {
return QR.selected.nodes.spoiler.click();
});
$.on(nodes.fileInput, 'change', QR.fileInput);
$.on(nodes.fileInput, 'change', QR.handleFiles);
items = ['name', 'email', 'sub', 'com', 'filename'];
i = 0;
while (name = items[i++]) {
@ -6329,7 +6319,7 @@
return QR.status();
},
response: function() {
var URL, ban, board, err, h1, isReply, m, post, postID, req, resDoc, threadID, _, _ref, _ref1;
var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1;
req = QR.req;
delete QR.req;
@ -6393,7 +6383,22 @@
threadID: threadID,
postID: postID
});
QR.cooldown.auto = QR.posts.length > 1 && isReply;
postsCount = QR.posts.length;
QR.cooldown.auto = postsCount > 1 && isReply;
if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) {
notif = new Notification('Quick reply warning', {
body: "You are running low on cached captchas. Cache count: " + captchasCount + ".",
icon: Favicon.logo
});
notif.onclick = function() {
QR.open();
QR.captcha.nodes.input.focus();
return window.focus();
};
setTimeout(function() {
return notif.close();
}, 7 * $.SECOND);
}
if (!(Conf['Persistent QR'] || QR.cooldown.auto)) {
QR.close();
} else {
@ -7352,7 +7357,6 @@
return Favicon.unreadY = Favicon.unreadNSFWY;
}
},
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg=='
};
@ -8153,7 +8157,7 @@
return div;
},
refresh: function() {
var boardID, data, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
var boardID, data, helper, list, nodes, refresher, thread, threadID, toggler, watched, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
nodes = [];
_ref = ThreadWatcher.getAll();
@ -8172,7 +8176,9 @@
boardID: thread.board.ID,
threadID: threadID
});
$[watched ? 'addClass' : 'rmClass'](toggler, 'watched');
helper = watched ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch'];
$[helper[0]](toggler, 'watched');
toggler.title = "" + helper[1] + " Thread";
}
_ref3 = ThreadWatcher.menu.refreshers;
for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) {
@ -8375,7 +8381,7 @@
Unread = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon']) {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) {
return;
}
this.db = new DataBoard('lastReadPosts', this.sync);
@ -8417,18 +8423,18 @@
}
}
Unread.addPosts(posts);
if (Conf['Scroll to Last Read Post']) {
return Unread.scroll();
}
return Unread.scroll();
},
scroll: function() {
var checkPosition, hash, onload, post, posts, root;
if (!Conf['Scroll to Last Read Post']) {
return;
}
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
}
if (Unread.posts.length) {
post = Unread.posts[0];
if (post = Unread.posts[0]) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) {
break;
@ -8452,10 +8458,7 @@
};
}
checkPosition = function(target) {
var height, top, _ref;
_ref = target.getBoundingClientRect(), top = _ref.top, height = _ref.height;
return top + height - doc.clientHeight > 0;
return target.getBoundingClientRect().bottom > doc.clientHeight;
};
return $.on(window, 'load', onload);
},
@ -8473,7 +8476,9 @@
Unread.lastReadPost = lastReadPost;
Unread.readArray(Unread.posts);
Unread.readArray(Unread.postsQuotingYou);
Unread.setLine();
if (Conf['Unread Line']) {
Unread.setLine();
}
return Unread.update();
},
addPosts: function(posts) {
@ -8518,7 +8523,6 @@
}
Unread.postsQuotingYou.push(post);
Unread.openNotification(post);
return;
}
},
openNotification: function(post) {
@ -8538,7 +8542,7 @@
};
return setTimeout(function() {
return notif.close();
}, 5 * $.SECOND);
}, 7 * $.SECOND);
},
onUpdate: function(e) {
if (e.detail[404]) {
@ -8575,7 +8579,7 @@
return arr.splice(0, i);
},
read: $.debounce(50, function(e) {
var ID, bottom, height, i, post, posts;
var ID, height, i, post, posts;
if (d.hidden || !Unread.posts.length) {
return;
@ -8584,8 +8588,7 @@
posts = Unread.posts;
i = 0;
while (post = posts[i]) {
bottom = post.nodes.root.getBoundingClientRect().bottom;
if (bottom < height) {
if (post.nodes.root.getBoundingClientRect().bottom < height) {
ID = post.ID;
if (Conf['Mark Quotes of You']) {
if (post.info.yours) {
@ -8603,10 +8606,8 @@
}
i++;
}
if (!Conf['Quote Threading']) {
if (i) {
posts.splice(0, i);
}
if (i && !Conf['Quote Threading']) {
posts.splice(0, i);
}
if (!ID) {
return;
@ -8631,19 +8632,17 @@
});
}),
setLine: function(force) {
var post, root;
var post;
if (!(d.hidden || force === true)) {
return;
}
if (post = Unread.posts[0]) {
root = post.nodes.root;
if (root !== $('.thread > .replyContainer', root.parentNode)) {
return $.before(root, Unread.hr);
}
} else {
if (!(post = Unread.posts[0])) {
return $.rm(Unread.hr);
}
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) {
return $.before(post.nodes.root, Unread.hr);
}
},
update: function(dontrepeat) {
var count;
@ -8651,17 +8650,18 @@
count = Unread.posts.length;
if (Conf['Unread Count']) {
d.title = "" + (Conf['Quoted Title'] && Unread.postsQuotingYou.length ? '(!) ' : '') + (count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '') + (g.DEAD ? "/" + g.BOARD + "/ - 404" : "" + Unread.title);
if (!dontrepeat) {
setTimeout(function() {
d.title = '';
return Unread.update(true);
}, $.SECOND);
if (dontrepeat) {
return;
}
setTimeout(function() {
d.title = '';
return Unread.update(true);
}, $.SECOND);
}
if (!Conf['Unread Favicon']) {
return;
}
return Favicon.el.href = g.DEAD ? Unread.postsQuotingYou.length ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou.length ? Favicon.unreadY : Favicon.unread : Favicon["default"];
return Favicon.el.href = g.DEAD ? Unread.postsQuotingYou[0] ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou[0] ? Favicon.unreadY : Favicon.unread : Favicon["default"];
}
};
@ -11046,7 +11046,7 @@
}));
Main.logError({
message: 'Chrome Storage API bug',
error: new Error(chrome.runtime.lastError.message || 'no lastError.message')
error: new Error('~')
});
}
return Main.initFeatures();
@ -11288,6 +11288,7 @@
} catch (_error) {
err = _error;
new Notice('warning', 'Cookies need to be enabled on 4chan for 4chan X to properly function.', 30);
Main.disableReports = true;
}
return $.event('4chanXInitFinished');
},

View File

@ -29,7 +29,7 @@ Header =
@linkJustifyToggler = linkJustifyToggler.firstElementChild
@headerToggler = headerToggler.firstElementChild
@footerToggler = footerToggler.firstElementChild
@shortcutToggler = shortcutToggler.firstElementChild
@shortcutToggler = shortcutToggler.firstElementChild
@customNavToggler = customNavToggler.firstElementChild
$.on menuButton, 'click', @menuToggle
@ -362,7 +362,8 @@ Header =
el = $.el 'span',
innerHTML: """
Desktop notification permissions are not granted:<br>
Desktop notification permissions are not granted.
[<a href='https://github.com/MayhemYDG/4chan-x/wiki/FAQ#desktop-notifications' target=_blank>FAQ</a>]<br>
<button>Authorize</button> or <button>Disable</button>
"""
[authorize, disable] = $$ 'button', el

View File

@ -29,7 +29,7 @@ Main =
# Track resolution of this bug.
Main.logError
message: 'Chrome Storage API bug'
error: new Error chrome.runtime.lastError.message or 'no lastError.message'
error: new Error '~'
<% } %>
Main.initFeatures()
@ -237,6 +237,7 @@ Main =
localStorage.getItem '4chan-settings'
catch err
new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30
Main.disableReports = true
$.event '4chanXInitFinished'

View File

@ -62,6 +62,8 @@ class Post
@kill() if that.isArchived
parseComment: ->
# Merge text nodes and remove empty ones.
@nodes.comment.normalize()
# Get the comment's text.
# <br> -> \n
# Remove:

View File

@ -398,4 +398,5 @@ Linkify =
src: "//www.youtube.com/embed/#{a.dataset.uid}#{if a.dataset.option then '#' + a.dataset.option else ''}?wmode=opaque"
title:
api: (uid) -> "https://gdata.youtube.com/feeds/api/videos/#{uid}?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"
text: (data) -> data.entry.title.$t
text: (data) -> data.entry.title.$t

View File

@ -45,6 +45,5 @@ Favicon =
Favicon.unread = Favicon.unreadNSFW
Favicon.unreadY = Favicon.unreadNSFWY
empty: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/empty.gif", {encoding: "base64"}) %>'
dead: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/dead.gif", {encoding: "base64"}) %>'
logo: 'data:image/png;base64,<%= grunt.file.read("src/General/img/icon128.png", {encoding: "base64"}) %>'

View File

@ -177,7 +177,9 @@ ThreadWatcher =
for threadID, thread of g.BOARD.threads
toggler = $ '.watch-thread-link', thread.OP.nodes.post
watched = ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
$[if watched then 'addClass' else 'rmClass'] toggler, 'watched'
helper = if watched then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
$[helper[0]] toggler, 'watched'
toggler.title = "#{helper[1]} Thread"
for refresher in ThreadWatcher.menu.refreshers
refresher()

View File

@ -1,6 +1,6 @@
Unread =
init: ->
return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Favicon']
return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Favicon'] and !Conf['Desktop Notifications']
@db = new DataBoard 'lastReadPosts', @sync
@hr = $.el 'hr',
@ -30,14 +30,14 @@ Unread =
for ID, post of Unread.thread.posts
posts.push post if post.isReply
Unread.addPosts posts
Unread.scroll() if Conf['Scroll to Last Read Post']
Unread.scroll()
scroll: ->
return unless Conf['Scroll to Last Read Post']
# Let the header's onload callback handle it.
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
if Unread.posts.length
if post = Unread.posts[0]
# Scroll to a non-hidden, non-OP post that's before the first unread post.
post = Unread.posts[0]
while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root
break unless (post = Get.postFromRoot root).isHidden
return unless root
@ -48,11 +48,8 @@ Unread =
{root} = Unread.thread.posts[posts[posts.length - 1]].nodes
onload = -> Header.scrollToPost root if checkPosition root
checkPosition = (target) ->
# Don't scroll to the target if
# - it's visible.
# - we've scrolled past it.
{top, height} = target.getBoundingClientRect()
top + height - doc.clientHeight > 0
# Scroll to the target unless we scrolled past it.
target.getBoundingClientRect().bottom > doc.clientHeight
# Prevent the browser to scroll back to
# the previous scroll location on page load.
$.on window, 'load', onload
@ -66,7 +63,7 @@ Unread =
Unread.lastReadPost = lastReadPost
Unread.readArray Unread.posts
Unread.readArray Unread.postsQuotingYou
Unread.setLine()
Unread.setLine() if Conf['Unread Line']
Unread.update()
addPosts: (posts) ->
@ -93,7 +90,8 @@ Unread =
for quotelink in post.nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
Unread.postsQuotingYou.push post
Unread.openNotification post
return
return
openNotification: (post) ->
return unless Header.areNotificationsEnabled
name = if Conf['Anonymize']
@ -108,7 +106,7 @@ Unread =
window.focus()
setTimeout ->
notif.close()
, 5 * $.SECOND
, 7 * $.SECOND
onUpdate: (e) ->
if e.detail[404]
@ -138,8 +136,7 @@ Unread =
i = 0
while post = posts[i]
{bottom} = post.nodes.root.getBoundingClientRect()
if bottom < height # post is completely read
if post.nodes.root.getBoundingClientRect().bottom < height # post is not completely read
{ID} = post
if Conf['Mark Quotes of You']
if post.info.yours
@ -152,9 +149,8 @@ Unread =
break
i++
unless Conf['Quote Threading']
if i
posts.splice 0, i
if i and !Conf['Quote Threading']
posts.splice 0, i
return unless ID
@ -172,12 +168,9 @@ Unread =
setLine: (force) ->
return unless d.hidden or force is true
if post = Unread.posts[0]
{root} = post.nodes
if root isnt $ '.thread > .replyContainer', root.parentNode # not the first reply
$.before root, Unread.hr
else
$.rm Unread.hr
return $.rm Unread.hr unless post = Unread.posts[0]
if $.x 'preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root # not the first reply
$.before post.nodes.root, Unread.hr
update: <% if (type === 'crx') { %>(dontrepeat) <% } %>->
count = Unread.posts.length
@ -189,18 +182,18 @@ Unread =
# crbug.com/124381
# Call it one second later,
# but don't display outdated unread count.
unless dontrepeat
setTimeout ->
d.title = ''
Unread.update true
, $.SECOND
return if dontrepeat
setTimeout ->
d.title = ''
Unread.update true
, $.SECOND
<% } %>
return unless Conf['Unread Favicon']
Favicon.el.href =
if g.DEAD
if Unread.postsQuotingYou.length
if Unread.postsQuotingYou[0]
Favicon.unreadDeadY
else if count
Favicon.unreadDead
@ -208,7 +201,7 @@ Unread =
Favicon.dead
else
if count
if Unread.postsQuotingYou.length
if Unread.postsQuotingYou[0]
Favicon.unreadY
else
Favicon.unread

View File

@ -165,7 +165,7 @@ QR =
setTimeout ->
notif.onclose = null
notif.close()
, 5 * $.SECOND
, 7 * $.SECOND
<% } %>
notifications: []
@ -448,69 +448,56 @@ QR =
return unless e.dataTransfer.files.length
e.preventDefault()
QR.open()
QR.fileInput e.dataTransfer.files
QR.handleFiles e.dataTransfer.files
$.addClass QR.nodes.el, 'dump'
paste: (e) ->
files = []
for item in e.clipboardData.items
if item.kind is 'file'
blob = item.getAsFile()
blob.name = 'file'
blob.name += '.' + blob.type.split('/')[1] if blob.type
files.push blob
for item in e.clipboardData.items when item.kind is 'file'
blob = item.getAsFile()
blob.name = 'file'
blob.name += '.' + blob.type.split('/')[1] if blob.type
files.push blob
return unless files.length
QR.open()
QR.fileInput files
openFileInput: (e) ->
e.stopPropagation()
if e.shiftKey and e.type is 'click'
return QR.selected.rmFile()
if e.ctrlKey and e.type is 'click'
$.addClass QR.nodes.filename, 'edit'
QR.nodes.filename.focus()
return $.on QR.nodes.filename, 'blur', -> $.rmClass QR.nodes.filename, 'edit'
return if e.target.nodeName is 'INPUT' or (e.keyCode and not [32, 13].contains e.keyCode) or e.ctrlKey
e.preventDefault()
QR.nodes.fileInput.click()
fileInput: (files) ->
if @ instanceof Element # file input, revert to "files instanceof Event" after a Pale Moon update
files = [@files...]
QR.nodes.fileInput.value = null # Don't hold the files from being modified on windows
{length} = files
return unless length
max = QR.nodes.fileInput.max
QR.cleanNotifications()
# Set or change current post's file.
if length is 1
file = files[0]
if /^text/.test file.type
QR.selected.pasteText file
else if file.size > max
QR.error "File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString max})."
else unless QR.mimeTypes.contains file.type
QR.error 'Unsupported file type.'
else
QR.selected.setFile file
return
# Create new posts with these files.
for file in files
if /^text/.test file.type
if (post = QR.posts[QR.posts.length - 1]).com
post = new QR.post()
post.pasteText file
else if file.size > max
QR.error "#{file.name}: File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString max})."
else unless QR.mimeTypes.contains file.type
QR.error "#{file.name}: Unsupported file type."
else
if (post = QR.posts[QR.posts.length - 1]).file
post = new QR.post()
post.setFile file
QR.handleFiles files
$.addClass QR.nodes.el, 'dump'
handleFiles: (files) ->
if @ isnt QR # file input
files = [@files...]
@value = null
return unless files.length
max = QR.nodes.fileInput.max
isSingle = files.length is 1
QR.cleanNotifications()
for file in files
QR.handleFile file, isSingle, max
$.addClass QR.nodes.el, 'dump' unless isSingle
handleFile: (file, isSingle, max) ->
if file.size > max
QR.error "#{file.name}: File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString max})."
return
else unless QR.mimeTypes.contains file.type
unless /^text/.test file.type
QR.error "#{file.name}: Unsupported file type."
return
if isSingle
post = QR.selected
else if (post = QR.posts[QR.posts.length - 1]).com
post = new QR.post()
post.pasteText file
return
if isSingle
post = QR.selected
else if (post = QR.posts[QR.posts.length - 1]).file
post = new QR.post()
post.setFile file
openFileInput: ->
QR.nodes.fileInput.click()
posts: []
post: class
@ -748,7 +735,9 @@ QR =
@nodes.span.textContent = @com
reader.readAsText file
dragStart: -> $.addClass @, 'drag'
dragStart: (e) ->
e.dataTransfer.setDragImage @, e.layerX, e.layerY
$.addClass @, 'drag'
dragEnd: -> $.rmClass @, 'drag'
dragEnter: -> $.addClass @, 'over'
dragLeave: -> $.rmClass @, 'over'
@ -987,7 +976,7 @@ QR =
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
$.on nodes.fileInput, 'change', QR.fileInput
$.on nodes.fileInput, 'change', QR.handleFiles
# save selected post's data
items = ['name', 'email', 'sub', 'com', 'filename']
i = 0
@ -1213,7 +1202,19 @@ QR =
}
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.posts.length > 1 and isReply
postsCount = QR.posts.length
QR.cooldown.auto = postsCount > 1 and isReply
if QR.cooldown.auto and QR.captcha.isEnabled and (captchasCount = QR.captcha.captchas.length) < 3 and captchasCount < postsCount
notif = new Notification 'Quick reply warning',
body: "You are running low on cached captchas. Cache count: #{captchasCount}."
icon: Favicon.logo
notif.onclick = ->
QR.open()
QR.captcha.nodes.input.focus()
window.focus()
setTimeout ->
notif.close()
, 7 * $.SECOND
unless Conf['Persistent QR'] or QR.cooldown.auto
QR.close()