Merge branch 'menu'
This commit is contained in:
commit
a63dad61b6
538
4chan_x.user.js
538
4chan_x.user.js
@ -77,7 +77,7 @@
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var $, $$, Anonymize, AutoGif, Conf, Config, DeleteButton, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, Get, ImageExpand, ImageHover, Keybinds, Main, Nav, Options, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, ReplyHiding, ReportButton, RevealSpoilers, Sauce, StrikethroughQuotes, ThreadHiding, ThreadStats, Time, TitlePost, UI, Unread, Updater, Watcher, d, g, _base;
|
||||
var $, $$, Anonymize, ArchiveLink, AutoGif, Conf, Config, DeleteLink, DownloadLink, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, Get, ImageExpand, ImageHover, Keybinds, Main, Menu, Nav, Options, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, ReplyHiding, ReportLink, RevealSpoilers, Sauce, StrikethroughQuotes, ThreadHiding, ThreadStats, Time, TitlePost, UI, Unread, Updater, Watcher, d, g, _base;
|
||||
|
||||
Config = {
|
||||
main: {
|
||||
@ -86,8 +86,6 @@
|
||||
'Keybinds': [true, 'Binds actions to keys'],
|
||||
'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'],
|
||||
'File Info Formatting': [true, 'Reformats the file information'],
|
||||
'Report Button': [true, 'Add report buttons'],
|
||||
'Delete Button': [false, 'Add delete buttons'],
|
||||
'Comment Expansion': [true, 'Expand too long comments'],
|
||||
'Thread Expansion': [true, 'View all replies'],
|
||||
'Index Navigation': [true, 'Navigate to previous / next thread'],
|
||||
@ -110,6 +108,13 @@
|
||||
'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail'],
|
||||
'Expand From Current': [false, 'Expand images from current position to thread end.']
|
||||
},
|
||||
Menu: {
|
||||
'Menu': [true, 'Add a drop-down menu in posts.'],
|
||||
'Report Link': [true, 'Add a report link to the menu.'],
|
||||
'Delete Link': [true, 'Add a delete link to the menu.'],
|
||||
'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'],
|
||||
'Archive Link': [true, 'Add an archive link to the menu.']
|
||||
},
|
||||
Monitoring: {
|
||||
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'],
|
||||
'Unread Count': [true, 'Show unread post count in tab title'],
|
||||
@ -419,7 +424,7 @@
|
||||
},
|
||||
nodes: function(nodes) {
|
||||
var frag, node, _i, _len;
|
||||
if (nodes instanceof Node) {
|
||||
if (!(nodes instanceof Array)) {
|
||||
return nodes;
|
||||
}
|
||||
frag = d.createDocumentFragment();
|
||||
@ -712,8 +717,12 @@
|
||||
filename: function(post) {
|
||||
var file, fileInfo;
|
||||
fileInfo = post.fileInfo;
|
||||
if (fileInfo && (file = $('.fileText > span', fileInfo))) {
|
||||
return file.title;
|
||||
if (fileInfo) {
|
||||
if (file = $('.fileText > span', fileInfo)) {
|
||||
return file.title;
|
||||
} else {
|
||||
return fileInfo.firstElementChild.dataset.filename;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
@ -740,6 +749,74 @@
|
||||
return img.dataset.md5;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
menuInit: function() {
|
||||
var div, entry, type, _i, _len, _ref;
|
||||
div = $.el('div', {
|
||||
textContent: 'Filter'
|
||||
});
|
||||
entry = {
|
||||
el: div,
|
||||
open: function() {
|
||||
return true;
|
||||
},
|
||||
children: []
|
||||
};
|
||||
_ref = [['Name', 'name'], ['Unique ID', 'uniqueid'], ['Tripcode', 'tripcode'], ['Admin/Mod', 'mod'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Country', 'country'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'md5']];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
type = _ref[_i];
|
||||
entry.children.push(Filter.createSubEntry(type[0], type[1]));
|
||||
}
|
||||
return Menu.addEntry(entry);
|
||||
},
|
||||
createSubEntry: function(text, type) {
|
||||
var el, onclick, open;
|
||||
el = $.el('a', {
|
||||
href: 'javascript:;',
|
||||
textContent: text
|
||||
});
|
||||
onclick = null;
|
||||
open = function(post) {
|
||||
var value;
|
||||
value = Filter[type](post);
|
||||
if (value === false) {
|
||||
return false;
|
||||
}
|
||||
$.off(el, 'click', onclick);
|
||||
onclick = function() {
|
||||
var re, save, select, ta, tl;
|
||||
re = type === 'md5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) {
|
||||
if (c === '\n') {
|
||||
return '\\n';
|
||||
} else if (c === '\\') {
|
||||
return '\\\\';
|
||||
} else {
|
||||
return "\\" + c;
|
||||
}
|
||||
});
|
||||
re = type === 'md5' ? "/" + value + "/" : "/^" + re + "$/";
|
||||
if (/\bop\b/.test(post["class"])) {
|
||||
re += ';op:yes';
|
||||
}
|
||||
save = (save = $.get(type, '')) ? "" + save + "\n" + re : re;
|
||||
$.set(type, save);
|
||||
Options.dialog();
|
||||
select = $('select[name=filter]', $.id('options'));
|
||||
select.value = type;
|
||||
$.event(select, new Event('change'));
|
||||
$.id('filter_tab').checked = true;
|
||||
ta = select.nextElementSibling;
|
||||
tl = ta.textLength;
|
||||
ta.setSelectionRange(tl, tl);
|
||||
return ta.focus();
|
||||
};
|
||||
$.on(el, 'click', onclick);
|
||||
return true;
|
||||
};
|
||||
return {
|
||||
el: el,
|
||||
open: open
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -910,7 +987,7 @@
|
||||
quote.href = "res/" + href;
|
||||
}
|
||||
id = reply.id.slice(2);
|
||||
link = $('.postInfo > .postNum > a[title="Highlight this post"]', reply);
|
||||
link = $('.postNum > a[title="Highlight this post"]', reply);
|
||||
link.href = "res/" + threadID + "#p" + id;
|
||||
link.nextSibling.href = "res/" + threadID + "#q" + id;
|
||||
nodes.push(reply);
|
||||
@ -951,7 +1028,7 @@
|
||||
}
|
||||
},
|
||||
cb: function() {
|
||||
return ThreadHiding.toggle(this.parentNode);
|
||||
return ThreadHiding.toggle($.x('ancestor::div[parent::div[@class="board"]]', this));
|
||||
},
|
||||
toggle: function(thread) {
|
||||
var hiddenThreads, id;
|
||||
@ -967,7 +1044,7 @@
|
||||
return $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
|
||||
},
|
||||
hide: function(thread, show_stub) {
|
||||
var a, num, opInfo, span, text;
|
||||
var a, menuButton, num, opInfo, span, stub, text;
|
||||
if (show_stub == null) {
|
||||
show_stub = Conf['Show Stubs'];
|
||||
}
|
||||
@ -986,19 +1063,24 @@
|
||||
num += $$('.opContainer ~ .replyContainer', thread).length;
|
||||
text = num === 1 ? '1 reply' : "" + num + " replies";
|
||||
opInfo = $('.op > .postInfo > .nameBlock', thread).textContent;
|
||||
a = $.el('a', {
|
||||
stub = $.el('div', {
|
||||
className: 'hide_thread_button hidden_thread',
|
||||
innerHTML: '<span>[ + ]</span>',
|
||||
href: 'javascript:;'
|
||||
innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>'
|
||||
});
|
||||
$.add(a, $.tn(" " + opInfo + " (" + text + ")"));
|
||||
a = stub.firstChild;
|
||||
$.on(a, 'click', ThreadHiding.cb);
|
||||
return $.prepend(thread, a);
|
||||
$.add(a, $.tn("" + opInfo + " (" + text + ")"));
|
||||
if (Conf['Menu']) {
|
||||
menuButton = Menu.a.cloneNode(true);
|
||||
$.on(menuButton, 'click', Menu.toggle);
|
||||
$.add(stub, [$.tn(' '), menuButton]);
|
||||
}
|
||||
return $.prepend(thread, stub);
|
||||
},
|
||||
show: function(thread) {
|
||||
var a;
|
||||
if (a = $('.hidden_thread', thread)) {
|
||||
$.rm(a);
|
||||
var stub;
|
||||
if (stub = $('.hidden_thread', thread)) {
|
||||
$.rm(stub);
|
||||
}
|
||||
thread.hidden = false;
|
||||
return thread.nextElementSibling.hidden = false;
|
||||
@ -1046,7 +1128,7 @@
|
||||
return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
|
||||
},
|
||||
hide: function(root, show_stub) {
|
||||
var a, el, side, stub;
|
||||
var a, el, menuButton, side, stub;
|
||||
if (show_stub == null) {
|
||||
show_stub = Conf['Show Stubs'];
|
||||
}
|
||||
@ -1065,8 +1147,13 @@
|
||||
innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>'
|
||||
});
|
||||
a = stub.firstChild;
|
||||
$.add(a, $.tn($('.nameBlock', el).textContent));
|
||||
$.on(a, 'click', ReplyHiding.toggle);
|
||||
$.add(a, $.tn($('.nameBlock', el).textContent));
|
||||
if (Conf['Menu']) {
|
||||
menuButton = Menu.a.cloneNode(true);
|
||||
$.on(menuButton, 'click', Menu.toggle);
|
||||
$.add(stub, [$.tn(' '), menuButton]);
|
||||
}
|
||||
return $.prepend(root, stub);
|
||||
},
|
||||
show: function(root) {
|
||||
@ -1079,6 +1166,186 @@
|
||||
}
|
||||
};
|
||||
|
||||
Menu = {
|
||||
entries: [],
|
||||
init: function() {
|
||||
this.a = $.el('a', {
|
||||
className: 'menu_button',
|
||||
href: 'javascript:;',
|
||||
innerHTML: '[<span></span>]'
|
||||
});
|
||||
this.el = $.el('div', {
|
||||
className: 'reply dialog',
|
||||
id: 'menu',
|
||||
tabIndex: 0
|
||||
});
|
||||
$.on(this.el, 'click', function(e) {
|
||||
return e.stopPropagation();
|
||||
});
|
||||
$.on(this.el, 'keydown', this.keybinds);
|
||||
$.on(d, 'AddMenuEntry', function(e) {
|
||||
return Menu.addEntry(e.detail);
|
||||
});
|
||||
return Main.callbacks.push(this.node);
|
||||
},
|
||||
node: function(post) {
|
||||
var a;
|
||||
if (post.isInlined && !post.isCrosspost) {
|
||||
a = $('.menu_button', post.el);
|
||||
} else {
|
||||
a = Menu.a.cloneNode(true);
|
||||
$.add($('.postInfo', post.el), a);
|
||||
}
|
||||
return $.on(a, 'click', Menu.toggle);
|
||||
},
|
||||
toggle: function(e) {
|
||||
var lastOpener, post;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (Menu.el.parentNode) {
|
||||
lastOpener = Menu.lastOpener;
|
||||
Menu.close();
|
||||
if (lastOpener === this) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Menu.lastOpener = this;
|
||||
post = /\bhidden_thread\b/.test(this.parentNode.className) ? $.x('ancestor::div[parent::div[@class="board"]]/child::div[contains(@class,"opContainer")]', this) : $.x('ancestor::div[contains(@class,"postContainer")][1]', this);
|
||||
return Menu.open(this, Main.preParse(post));
|
||||
},
|
||||
open: function(button, post) {
|
||||
var bLeft, bRect, bTop, el, entry, funk, mRect, _i, _len, _ref;
|
||||
el = Menu.el;
|
||||
el.setAttribute('data-id', post.ID);
|
||||
el.setAttribute('data-rootid', post.root.id);
|
||||
funk = function(entry, parent) {
|
||||
var child, children, subMenu, _i, _len;
|
||||
children = entry.children;
|
||||
if (!entry.open(post)) {
|
||||
return;
|
||||
}
|
||||
$.add(parent, entry.el);
|
||||
if (!children) {
|
||||
return;
|
||||
}
|
||||
if (subMenu = $('.subMenu', entry.el)) {
|
||||
$.rm(subMenu);
|
||||
}
|
||||
subMenu = $.el('div', {
|
||||
className: 'reply dialog subMenu'
|
||||
});
|
||||
$.add(entry.el, subMenu);
|
||||
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
||||
child = children[_i];
|
||||
funk(child, subMenu);
|
||||
}
|
||||
};
|
||||
_ref = Menu.entries;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
entry = _ref[_i];
|
||||
funk(entry, el);
|
||||
}
|
||||
Menu.focus($('.entry', Menu.el));
|
||||
$.on(d, 'click', Menu.close);
|
||||
$.add(d.body, el);
|
||||
mRect = el.getBoundingClientRect();
|
||||
bRect = button.getBoundingClientRect();
|
||||
bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top;
|
||||
bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left;
|
||||
el.style.top = bRect.top + bRect.height + mRect.height < d.documentElement.clientHeight ? bTop + bRect.height + 2 + 'px' : bTop - mRect.height - 2 + 'px';
|
||||
el.style.left = bRect.left + mRect.width < d.documentElement.clientWidth ? bLeft + 'px' : bLeft + bRect.width - mRect.width + 'px';
|
||||
return el.focus();
|
||||
},
|
||||
close: function() {
|
||||
var el, focused, _i, _len, _ref;
|
||||
el = Menu.el;
|
||||
$.rm(el);
|
||||
_ref = $$('.focused.entry', el);
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
focused = _ref[_i];
|
||||
$.rmClass(focused, 'focused');
|
||||
}
|
||||
el.innerHTML = null;
|
||||
el.removeAttribute('style');
|
||||
delete Menu.lastOpener;
|
||||
delete Menu.focusedEntry;
|
||||
return $.off(d, 'click', Menu.close);
|
||||
},
|
||||
keybinds: function(e) {
|
||||
var el, next, subMenu;
|
||||
el = Menu.focusedEntry;
|
||||
switch (Keybinds.keyCode(e) || e.keyCode) {
|
||||
case 'Esc':
|
||||
Menu.lastOpener.focus();
|
||||
Menu.close();
|
||||
break;
|
||||
case 13:
|
||||
case 32:
|
||||
el.click();
|
||||
break;
|
||||
case 'Up':
|
||||
if (next = el.previousElementSibling) {
|
||||
Menu.focus(next);
|
||||
}
|
||||
break;
|
||||
case 'Down':
|
||||
if (next = el.nextElementSibling) {
|
||||
Menu.focus(next);
|
||||
}
|
||||
break;
|
||||
case 'Right':
|
||||
if ((subMenu = $('.subMenu', el)) && (next = subMenu.firstElementChild)) {
|
||||
Menu.focus(next);
|
||||
}
|
||||
break;
|
||||
case 'Left':
|
||||
if (next = $.x('parent::*[contains(@class,"subMenu")]/parent::*', el)) {
|
||||
Menu.focus(next);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
return e.stopPropagation();
|
||||
},
|
||||
focus: function(el) {
|
||||
var focused, _i, _len, _ref;
|
||||
if (focused = $.x('parent::*/child::*[contains(@class,"focused")]', el)) {
|
||||
$.rmClass(focused, 'focused');
|
||||
}
|
||||
_ref = $$('.focused', el);
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
focused = _ref[_i];
|
||||
$.rmClass(focused, 'focused');
|
||||
}
|
||||
Menu.focusedEntry = el;
|
||||
return $.addClass(el, 'focused');
|
||||
},
|
||||
addEntry: function(entry) {
|
||||
var funk;
|
||||
funk = function(entry) {
|
||||
var child, children, el, _i, _len;
|
||||
el = entry.el, children = entry.children;
|
||||
$.addClass(el, 'entry');
|
||||
$.on(el, 'focus mouseover', function(e) {
|
||||
e.stopPropagation();
|
||||
return Menu.focus(this);
|
||||
});
|
||||
if (!children) {
|
||||
return;
|
||||
}
|
||||
$.addClass(el, 'hasSubMenu');
|
||||
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
||||
child = children[_i];
|
||||
funk(child);
|
||||
}
|
||||
};
|
||||
funk(entry);
|
||||
return Menu.entries.push(entry);
|
||||
}
|
||||
};
|
||||
|
||||
Keybinds = {
|
||||
init: function() {
|
||||
var node, _i, _len, _ref;
|
||||
@ -1300,7 +1567,7 @@
|
||||
},
|
||||
qr: function(thread, quote) {
|
||||
if (quote) {
|
||||
QR.quote.call($('.postInfo > .postNum > a[title="Quote this post"]', $('.post.highlight', thread) || thread));
|
||||
QR.quote.call($('.postNum > a[title="Quote this post"]', $('.post.highlight', thread) || thread));
|
||||
} else {
|
||||
QR.open();
|
||||
}
|
||||
@ -1455,7 +1722,7 @@
|
||||
return $.on(d, 'dragstart dragend', QR.drag);
|
||||
},
|
||||
node: function(post) {
|
||||
return $.on($('.postInfo > .postNum > a[title="Quote this post"]', post.el), 'click', QR.quote);
|
||||
return $.on($('.postNum > a[title="Quote this post"]', post.el), 'click', QR.quote);
|
||||
},
|
||||
open: function() {
|
||||
if (QR.el) {
|
||||
@ -3078,6 +3345,7 @@
|
||||
fullname: span.title,
|
||||
shortname: span.textContent
|
||||
};
|
||||
node.setAttribute('data-filename', span.title);
|
||||
return node.innerHTML = FileInfo.funk(FileInfo);
|
||||
},
|
||||
setFormats: function() {
|
||||
@ -3211,7 +3479,7 @@
|
||||
}
|
||||
quote.href = "/" + board + "/res/" + href;
|
||||
}
|
||||
link = $('.postInfo > .postNum > a[title="Highlight this post"]', pc);
|
||||
link = $('.postNum > a[title="Highlight this post"]', pc);
|
||||
link.href = "/" + board + "/res/" + threadID + "#p" + postID;
|
||||
link.nextSibling.href = "/" + board + "/res/" + threadID + "#q" + postID;
|
||||
$.replace(root.firstChild, pc);
|
||||
@ -3369,13 +3637,13 @@
|
||||
}));
|
||||
$.after((isOP ? piM : pi), file);
|
||||
}
|
||||
$.replace(root.firstChild, pc);
|
||||
$.replace(root.firstChild, Get.cleanPost(pc));
|
||||
if (cb) {
|
||||
return cb();
|
||||
}
|
||||
},
|
||||
cleanPost: function(root) {
|
||||
var child, el, inline, inlined, now, post, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3;
|
||||
var child, el, els, inline, inlined, now, post, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2;
|
||||
post = $('.post', root);
|
||||
_ref = Array.prototype.slice.call(root.childNodes);
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
@ -3395,9 +3663,10 @@
|
||||
$.rmClass(inlined, 'inlined');
|
||||
}
|
||||
now = Date.now();
|
||||
_ref3 = $$('[id]', root);
|
||||
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
|
||||
el = _ref3[_l];
|
||||
els = $$('[id]', root);
|
||||
els.push(root);
|
||||
for (_l = 0, _len3 = els.length; _l < _len3; _l++) {
|
||||
el = els[_l];
|
||||
el.id = "" + now + "_" + el.id;
|
||||
}
|
||||
$.rmClass(root, 'forwarded');
|
||||
@ -3754,7 +4023,7 @@
|
||||
nodes.push($.tn(text));
|
||||
}
|
||||
id = quote.match(/\d+$/)[0];
|
||||
board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : $('.postInfo > .postNum > a[title="Highlight this post"]', post.el).pathname.split('/')[1];
|
||||
board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : $('.postNum > a[title="Highlight this post"]', post.el).pathname.split('/')[1];
|
||||
nodes.push(a = $.el('a', {
|
||||
textContent: "" + quote + "\u00A0(Dead)"
|
||||
}));
|
||||
@ -3782,46 +4051,44 @@
|
||||
}
|
||||
};
|
||||
|
||||
DeleteButton = {
|
||||
DeleteLink = {
|
||||
init: function() {
|
||||
this.a = $.el('a', {
|
||||
className: 'delete_button',
|
||||
innerHTML: '[ × ]',
|
||||
var a;
|
||||
a = $.el('a', {
|
||||
className: 'delete_link',
|
||||
href: 'javascript:;'
|
||||
});
|
||||
return Main.callbacks.push(this.node);
|
||||
},
|
||||
node: function(post) {
|
||||
var a;
|
||||
if (!(a = $('.delete_button', post.el))) {
|
||||
a = DeleteButton.a.cloneNode(true);
|
||||
$.add($('.postInfo', post.el), a);
|
||||
}
|
||||
return $.on(a, 'click', DeleteButton["delete"]);
|
||||
return Menu.addEntry({
|
||||
el: a,
|
||||
open: function(post) {
|
||||
if (post.isArchived) {
|
||||
return false;
|
||||
}
|
||||
a.textContent = 'Delete this post';
|
||||
$.on(a, 'click', DeleteLink["delete"]);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
},
|
||||
"delete": function() {
|
||||
var board, form, id, m, pwd, self;
|
||||
$.off(this, 'click', DeleteButton["delete"]);
|
||||
this.innerHTML = '[ Deleting... ]';
|
||||
if (m = d.cookie.match(/4chan_pass=([^;]+)/)) {
|
||||
pwd = decodeURIComponent(m[1]);
|
||||
} else {
|
||||
pwd = $.id('delPassword').value;
|
||||
}
|
||||
id = $.x('preceding-sibling::input', this).name;
|
||||
board = $.x('preceding-sibling::span[1]/a', this).pathname.match(/\w+/)[0];
|
||||
$.off(this, 'click', DeleteLink["delete"]);
|
||||
this.textContent = 'Deleting...';
|
||||
pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $.id('delPassword').value;
|
||||
id = this.parentNode.dataset.id;
|
||||
board = $('.postNum > a[title="Highlight this post"]', $.id(this.parentNode.dataset.rootid)).pathname.split('/')[1];
|
||||
self = this;
|
||||
form = {
|
||||
mode: 'usrdel',
|
||||
pwd: pwd
|
||||
};
|
||||
form[id] = 'delete';
|
||||
return $.ajax("https://sys.4chan.org/" + board + "/imgboard.php", {
|
||||
return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + board + "/"), {
|
||||
onload: function() {
|
||||
return DeleteButton.load(self, this.response);
|
||||
return DeleteLink.load(self, this.response);
|
||||
},
|
||||
onerror: function() {
|
||||
return DeleteButton.error(self);
|
||||
return DeleteLink.error(self);
|
||||
}
|
||||
}, {
|
||||
form: $.formData(form)
|
||||
@ -3835,44 +4102,93 @@
|
||||
s = 'Banned!';
|
||||
} else if (msg = doc.getElementById('errmsg')) {
|
||||
s = msg.textContent;
|
||||
$.on(self, 'click', DeleteButton["delete"]);
|
||||
$.on(self, 'click', DeleteLink["delete"]);
|
||||
} else {
|
||||
s = 'Deleted';
|
||||
}
|
||||
return self.innerHTML = "[ " + s + " ]";
|
||||
return self.textContent = s;
|
||||
},
|
||||
error: function(self) {
|
||||
self.innerHTML = '[ Connection error, please retry. ]';
|
||||
return $.on(self, 'click', DeleteButton["delete"]);
|
||||
self.textContent = 'Connection error, please retry.';
|
||||
return $.on(self, 'click', DeleteLink["delete"]);
|
||||
}
|
||||
};
|
||||
|
||||
ReportButton = {
|
||||
ReportLink = {
|
||||
init: function() {
|
||||
this.a = $.el('a', {
|
||||
className: 'report_button',
|
||||
innerHTML: '[ ! ]',
|
||||
href: 'javascript:;'
|
||||
});
|
||||
return Main.callbacks.push(this.node);
|
||||
},
|
||||
node: function(post) {
|
||||
var a;
|
||||
if (!(a = $('.report_button', post.el))) {
|
||||
a = ReportButton.a.cloneNode(true);
|
||||
$.add($('.postInfo', post.el), a);
|
||||
}
|
||||
return $.on(a, 'click', ReportButton.report);
|
||||
a = $.el('a', {
|
||||
className: 'report_link',
|
||||
href: 'javascript:;',
|
||||
textContent: 'Report this post'
|
||||
});
|
||||
$.on(a, 'click', this.report);
|
||||
return Menu.addEntry({
|
||||
el: a,
|
||||
open: function(post) {
|
||||
return post.isArchived === false;
|
||||
}
|
||||
});
|
||||
},
|
||||
report: function() {
|
||||
var id, set, url;
|
||||
url = "//sys.4chan.org/" + g.BOARD + "/imgboard.php?mode=report&no=" + ($.x('preceding-sibling::input', this).name);
|
||||
var a, id, set, url;
|
||||
a = $('.postNum > a[title="Highlight this post"]', $.id(this.parentNode.dataset.rootid));
|
||||
url = "//sys.4chan.org/" + (a.pathname.split('/')[1]) + "/imgboard.php?mode=report&no=" + this.parentNode.dataset.id;
|
||||
id = Date.now();
|
||||
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200";
|
||||
return window.open(url, id, set);
|
||||
}
|
||||
};
|
||||
|
||||
DownloadLink = {
|
||||
init: function() {
|
||||
var a;
|
||||
if ($.el('a').download === void 0) {
|
||||
return;
|
||||
}
|
||||
a = $.el('a', {
|
||||
className: 'download_link',
|
||||
textContent: 'Download file'
|
||||
});
|
||||
return Menu.addEntry({
|
||||
el: a,
|
||||
open: function(post) {
|
||||
var fileText;
|
||||
if (!post.img) {
|
||||
return false;
|
||||
}
|
||||
a.href = post.img.parentNode.href;
|
||||
fileText = post.fileInfo.firstElementChild;
|
||||
a.download = Conf['File Info Formatting'] ? fileText.dataset.filename : $('span', fileText).title;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ArchiveLink = {
|
||||
init: function() {
|
||||
var a;
|
||||
a = $.el('a', {
|
||||
className: 'archive_link',
|
||||
target: '_blank',
|
||||
textContent: 'Archived post'
|
||||
});
|
||||
return Menu.addEntry({
|
||||
el: a,
|
||||
open: function(post) {
|
||||
var href, path;
|
||||
path = $('.postNum > a[title="Highlight this post"]', post.el).pathname.split('/');
|
||||
if ((href = Redirect.thread(path[1], path[3], post.ID)) === ("//boards.4chan.org/" + path[1] + "/")) {
|
||||
return false;
|
||||
}
|
||||
a.href = href;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ThreadStats = {
|
||||
init: function() {
|
||||
var dialog;
|
||||
@ -4531,11 +4847,23 @@
|
||||
if (Conf['Image Hover']) {
|
||||
ImageHover.init();
|
||||
}
|
||||
if (Conf['Report Button']) {
|
||||
ReportButton.init();
|
||||
}
|
||||
if (Conf['Delete Button']) {
|
||||
DeleteButton.init();
|
||||
if (Conf['Menu']) {
|
||||
Menu.init();
|
||||
if (Conf['Report Link']) {
|
||||
ReportLink.init();
|
||||
}
|
||||
if (Conf['Delete Link']) {
|
||||
DeleteLink.init();
|
||||
}
|
||||
if (Conf['Filter']) {
|
||||
Filter.menuInit();
|
||||
}
|
||||
if (Conf['Download Link']) {
|
||||
DownloadLink.init();
|
||||
}
|
||||
if (Conf['Archive Link']) {
|
||||
ArchiveLink.init();
|
||||
}
|
||||
}
|
||||
if (Conf['Resurrect Quotes']) {
|
||||
Quotify.init();
|
||||
@ -4799,6 +5127,60 @@ a[href="javascript:;"] {\
|
||||
display: none !important;\
|
||||
}\
|
||||
\
|
||||
.menu_button {\
|
||||
display: inline-block;\
|
||||
}\
|
||||
.menu_button > span {\
|
||||
border-top: .5em solid;\
|
||||
border-right: .3em solid transparent;\
|
||||
border-left: .3em solid transparent;\
|
||||
display: inline-block;\
|
||||
margin: 2px;\
|
||||
vertical-align: middle;\
|
||||
}\
|
||||
#menu {\
|
||||
position: absolute;\
|
||||
outline: none;\
|
||||
}\
|
||||
.entry {\
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .25);\
|
||||
cursor: pointer;\
|
||||
display: block;\
|
||||
outline: none;\
|
||||
padding: 3px 7px;\
|
||||
position: relative;\
|
||||
text-decoration: none;\
|
||||
white-space: nowrap;\
|
||||
}\
|
||||
.entry:last-child {\
|
||||
border: none;\
|
||||
}\
|
||||
.focused.entry {\
|
||||
background: rgba(255, 255, 255, .33);\
|
||||
}\
|
||||
.entry.hasSubMenu {\
|
||||
padding-right: 1.5em;\
|
||||
}\
|
||||
.hasSubMenu::after {\
|
||||
content: "";\
|
||||
border-left: .5em solid;\
|
||||
border-top: .3em solid transparent;\
|
||||
border-bottom: .3em solid transparent;\
|
||||
display: inline-block;\
|
||||
margin: .3em;\
|
||||
position: absolute;\
|
||||
right: 3px;\
|
||||
}\
|
||||
.hasSubMenu:not(.focused) > .subMenu {\
|
||||
display: none;\
|
||||
}\
|
||||
.subMenu {\
|
||||
position: absolute;\
|
||||
left: 100%;\
|
||||
top: 0;\
|
||||
margin-top: -1px;\
|
||||
}\
|
||||
\
|
||||
h1 {\
|
||||
text-align: center;\
|
||||
}\
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
master
|
||||
- Mayhem
|
||||
New feature: Menu, which
|
||||
- replaces and includes Report Button and Delete Button.
|
||||
- add one-click Filter buttons.
|
||||
- add download links to automatically save the file with its original filename. Chrome-only currently.
|
||||
- add archive links.
|
||||
- can integrate features from external userscripts/extensions, see https://github.com/MayhemYDG/4chan-x/wiki/Menu-API
|
||||
The updater's refresh interval will now increase gradually in inactive threads.
|
||||
The updater's refresh interval is now limited to 5 seconds minimum.
|
||||
|
||||
|
||||
499
script.coffee
499
script.coffee
@ -5,8 +5,6 @@ Config =
|
||||
'Keybinds': [true, 'Binds actions to keys']
|
||||
'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time']
|
||||
'File Info Formatting': [true, 'Reformats the file information']
|
||||
'Report Button': [true, 'Add report buttons']
|
||||
'Delete Button': [false, 'Add delete buttons']
|
||||
'Comment Expansion': [true, 'Expand too long comments']
|
||||
'Thread Expansion': [true, 'View all replies']
|
||||
'Index Navigation': [true, 'Navigate to previous / next thread']
|
||||
@ -26,6 +24,12 @@ Config =
|
||||
'Sauce': [true, 'Add sauce to images']
|
||||
'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail']
|
||||
'Expand From Current': [false, 'Expand images from current position to thread end.']
|
||||
Menu:
|
||||
'Menu': [true, 'Add a drop-down menu in posts.']
|
||||
'Report Link': [true, 'Add a report link to the menu.']
|
||||
'Delete Link': [true, 'Add a delete link to the menu.']
|
||||
'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.']
|
||||
'Archive Link': [true, 'Add an archive link to the menu.']
|
||||
Monitoring:
|
||||
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.']
|
||||
'Unread Count': [true, 'Show unread post count in tab title']
|
||||
@ -325,7 +329,10 @@ $.extend $,
|
||||
tn: (s) ->
|
||||
d.createTextNode s
|
||||
nodes: (nodes) ->
|
||||
if nodes instanceof Node
|
||||
# In (at least) Chrome, elements created inside different
|
||||
# scripts/window contexts inherit from unequal prototypes.
|
||||
# window_ext1.Node !== window_ext2.Node
|
||||
unless nodes instanceof Array
|
||||
return nodes
|
||||
frag = d.createDocumentFragment()
|
||||
for node in nodes
|
||||
@ -562,8 +569,11 @@ Filter =
|
||||
false
|
||||
filename: (post) ->
|
||||
{fileInfo} = post
|
||||
if fileInfo and file = $ '.fileText > span', fileInfo
|
||||
return file.title
|
||||
if fileInfo
|
||||
if file = $ '.fileText > span', fileInfo
|
||||
return file.title
|
||||
else
|
||||
return fileInfo.firstElementChild.dataset.filename
|
||||
false
|
||||
dimensions: (post) ->
|
||||
{fileInfo} = post
|
||||
@ -581,6 +591,99 @@ Filter =
|
||||
return img.dataset.md5
|
||||
false
|
||||
|
||||
menuInit: ->
|
||||
div = $.el 'div',
|
||||
textContent: 'Filter'
|
||||
|
||||
entry =
|
||||
el: div
|
||||
open: -> true
|
||||
children: []
|
||||
|
||||
for type in [
|
||||
['Name', 'name']
|
||||
['Unique ID', 'uniqueid']
|
||||
['Tripcode', 'tripcode']
|
||||
['Admin/Mod', 'mod']
|
||||
['E-mail', 'email']
|
||||
['Subject', 'subject']
|
||||
['Comment', 'comment']
|
||||
['Country', 'country']
|
||||
['Filename', 'filename']
|
||||
['Image dimensions', 'dimensions']
|
||||
['Filesize', 'filesize']
|
||||
['Image MD5', 'md5']
|
||||
]
|
||||
# Add a sub entry for each filter type.
|
||||
entry.children.push Filter.createSubEntry type[0], type[1]
|
||||
|
||||
Menu.addEntry entry
|
||||
|
||||
createSubEntry: (text, type) ->
|
||||
el = $.el 'a',
|
||||
href: 'javascript:;'
|
||||
textContent: text
|
||||
# Define the onclick var outside of open's scope to $.off it properly.
|
||||
onclick = null
|
||||
|
||||
open = (post) ->
|
||||
value = Filter[type] post
|
||||
return false if value is false
|
||||
$.off el, 'click', onclick
|
||||
onclick = ->
|
||||
# Convert value -> regexp, unless type is md5
|
||||
re = if type is 'md5' then value else value.replace ///
|
||||
/
|
||||
| \\
|
||||
| \^
|
||||
| \$
|
||||
| \n
|
||||
| \.
|
||||
| \(
|
||||
| \)
|
||||
| \{
|
||||
| \}
|
||||
| \[
|
||||
| \]
|
||||
| \?
|
||||
| \*
|
||||
| \+
|
||||
| \|
|
||||
///g, (c) ->
|
||||
if c is '\n'
|
||||
'\\n'
|
||||
else if c is '\\'
|
||||
'\\\\'
|
||||
else
|
||||
"\\#{c}"
|
||||
|
||||
re =
|
||||
if type is 'md5'
|
||||
"/#{value}/"
|
||||
else
|
||||
"/^#{re}$/"
|
||||
if /\bop\b/.test post.class
|
||||
re += ';op:yes'
|
||||
|
||||
# Add a new line before the regexp unless the text is empty.
|
||||
save = if save = $.get type, '' then "#{save}\n#{re}" else re
|
||||
$.set type, save
|
||||
|
||||
# Open the options and display & focus the relevant filter textarea.
|
||||
Options.dialog()
|
||||
select = $ 'select[name=filter]', $.id 'options'
|
||||
select.value = type
|
||||
$.event select, new Event 'change'
|
||||
$.id('filter_tab').checked = true
|
||||
ta = select.nextElementSibling
|
||||
tl = ta.textLength
|
||||
ta.setSelectionRange tl, tl
|
||||
ta.focus()
|
||||
$.on el, 'click', onclick
|
||||
true
|
||||
|
||||
return el: el, open: open
|
||||
|
||||
StrikethroughQuotes =
|
||||
init: ->
|
||||
Main.callbacks.push @node
|
||||
@ -696,7 +799,7 @@ ExpandThread =
|
||||
continue if href[0] is '/' # Cross-board quote
|
||||
quote.href = "res/#{href}" # Fix pathnames
|
||||
id = reply.id[2..]
|
||||
link = $ '.postInfo > .postNum > a[title="Highlight this post"]', reply
|
||||
link = $ '.postNum > a[title="Highlight this post"]', reply
|
||||
link.href = "res/#{threadID}#p#{id}"
|
||||
link.nextSibling.href = "res/#{threadID}#q#{id}"
|
||||
nodes.push reply
|
||||
@ -724,7 +827,7 @@ ThreadHiding =
|
||||
return
|
||||
|
||||
cb: ->
|
||||
ThreadHiding.toggle @parentNode
|
||||
ThreadHiding.toggle $.x 'ancestor::div[parent::div[@class="board"]]', @
|
||||
|
||||
toggle: (thread) ->
|
||||
hiddenThreads = $.get "hiddenThreads/#{g.BOARD}/", {}
|
||||
@ -752,17 +855,21 @@ ThreadHiding =
|
||||
text = if num is 1 then '1 reply' else "#{num} replies"
|
||||
opInfo = $('.op > .postInfo > .nameBlock', thread).textContent
|
||||
|
||||
a = $.el 'a',
|
||||
stub = $.el 'div',
|
||||
className: 'hide_thread_button hidden_thread'
|
||||
innerHTML: '<span>[ + ]</span>'
|
||||
href: 'javascript:;'
|
||||
$.add a, $.tn " #{opInfo} (#{text})"
|
||||
$.on a, 'click', ThreadHiding.cb
|
||||
$.prepend thread, a
|
||||
innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>'
|
||||
a = stub.firstChild
|
||||
$.on a, 'click', ThreadHiding.cb
|
||||
$.add a, $.tn "#{opInfo} (#{text})"
|
||||
if Conf['Menu']
|
||||
menuButton = Menu.a.cloneNode true
|
||||
$.on menuButton, 'click', Menu.toggle
|
||||
$.add stub, [$.tn(' '), menuButton]
|
||||
$.prepend thread, stub
|
||||
|
||||
show: (thread) ->
|
||||
if a = $ '.hidden_thread', thread
|
||||
$.rm a
|
||||
if stub = $ '.hidden_thread', thread
|
||||
$.rm stub
|
||||
thread.hidden = false
|
||||
thread.nextElementSibling.hidden = false
|
||||
|
||||
@ -810,8 +917,12 @@ ReplyHiding =
|
||||
className: 'hide_reply_button stub'
|
||||
innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>'
|
||||
a = stub.firstChild
|
||||
$.add a, $.tn $('.nameBlock', el).textContent
|
||||
$.on a, 'click', ReplyHiding.toggle
|
||||
$.add a, $.tn $('.nameBlock', el).textContent
|
||||
if Conf['Menu']
|
||||
menuButton = Menu.a.cloneNode true
|
||||
$.on menuButton, 'click', Menu.toggle
|
||||
$.add stub, [$.tn(' '), menuButton]
|
||||
$.prepend root, stub
|
||||
|
||||
show: (root) ->
|
||||
@ -820,6 +931,155 @@ ReplyHiding =
|
||||
$('.sideArrows', root).hidden = false
|
||||
$('.post', root).hidden = false
|
||||
|
||||
Menu =
|
||||
entries: []
|
||||
init: ->
|
||||
@a = $.el 'a',
|
||||
className: 'menu_button'
|
||||
href: 'javascript:;'
|
||||
innerHTML: '[<span></span>]'
|
||||
@el = $.el 'div',
|
||||
className: 'reply dialog'
|
||||
id: 'menu'
|
||||
tabIndex: 0
|
||||
$.on @el, 'click', (e) -> e.stopPropagation()
|
||||
$.on @el, 'keydown', @keybinds
|
||||
|
||||
# Doc here: https://github.com/MayhemYDG/4chan-x/wiki/Menu-API
|
||||
$.on d, 'AddMenuEntry', (e) -> Menu.addEntry e.detail
|
||||
|
||||
Main.callbacks.push @node
|
||||
node: (post) ->
|
||||
if post.isInlined and !post.isCrosspost
|
||||
a = $ '.menu_button', post.el
|
||||
else
|
||||
a = Menu.a.cloneNode true
|
||||
$.add $('.postInfo', post.el), a
|
||||
$.on a, 'click', Menu.toggle
|
||||
|
||||
toggle: (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if Menu.el.parentNode
|
||||
# Close if it's already opened.
|
||||
# Reopen if we clicked on another button.
|
||||
{lastOpener} = Menu
|
||||
Menu.close()
|
||||
return if lastOpener is @
|
||||
|
||||
Menu.lastOpener = @
|
||||
post =
|
||||
if /\bhidden_thread\b/.test @parentNode.className
|
||||
$.x 'ancestor::div[parent::div[@class="board"]]/child::div[contains(@class,"opContainer")]', @
|
||||
else
|
||||
$.x 'ancestor::div[contains(@class,"postContainer")][1]', @
|
||||
Menu.open @, Main.preParse post
|
||||
open: (button, post) ->
|
||||
{el} = Menu
|
||||
# XXX GM/Scriptish require setAttribute
|
||||
el.setAttribute 'data-id', post.ID
|
||||
el.setAttribute 'data-rootid', post.root.id
|
||||
|
||||
funk = (entry, parent) ->
|
||||
{children} = entry
|
||||
return unless entry.open post
|
||||
$.add parent, entry.el
|
||||
|
||||
return unless children
|
||||
if subMenu = $ '.subMenu', entry.el
|
||||
# Reset sub menu, remove irrelevant entries.
|
||||
$.rm subMenu
|
||||
subMenu = $.el 'div',
|
||||
className: 'reply dialog subMenu'
|
||||
$.add entry.el, subMenu
|
||||
for child in children
|
||||
funk child, subMenu
|
||||
return
|
||||
for entry in Menu.entries
|
||||
funk entry, el
|
||||
|
||||
Menu.focus $ '.entry', Menu.el
|
||||
$.on d, 'click', Menu.close
|
||||
$.add d.body, el
|
||||
|
||||
# Position
|
||||
mRect = el.getBoundingClientRect()
|
||||
bRect = button.getBoundingClientRect()
|
||||
bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top
|
||||
bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left
|
||||
el.style.top =
|
||||
if bRect.top + bRect.height + mRect.height < d.documentElement.clientHeight
|
||||
bTop + bRect.height + 2 + 'px'
|
||||
else
|
||||
bTop - mRect.height - 2 + 'px'
|
||||
el.style.left =
|
||||
if bRect.left + mRect.width < d.documentElement.clientWidth
|
||||
bLeft + 'px'
|
||||
else
|
||||
bLeft + bRect.width - mRect.width + 'px'
|
||||
|
||||
el.focus()
|
||||
close: ->
|
||||
{el} = Menu
|
||||
$.rm el
|
||||
for focused in $$ '.focused.entry', el
|
||||
$.rmClass focused, 'focused'
|
||||
el.innerHTML = null
|
||||
el.removeAttribute 'style'
|
||||
delete Menu.lastOpener
|
||||
delete Menu.focusedEntry
|
||||
$.off d, 'click', Menu.close
|
||||
|
||||
keybinds: (e) ->
|
||||
el = Menu.focusedEntry
|
||||
|
||||
switch Keybinds.keyCode(e) or e.keyCode
|
||||
when 'Esc'
|
||||
Menu.lastOpener.focus()
|
||||
Menu.close()
|
||||
when 13, 32 # 'Enter', 'Space'
|
||||
el.click()
|
||||
when 'Up'
|
||||
if next = el.previousElementSibling
|
||||
Menu.focus next
|
||||
when 'Down'
|
||||
if next = el.nextElementSibling
|
||||
Menu.focus next
|
||||
when 'Right'
|
||||
if (subMenu = $ '.subMenu', el) and next = subMenu.firstElementChild
|
||||
Menu.focus next
|
||||
when 'Left'
|
||||
if next = $.x 'parent::*[contains(@class,"subMenu")]/parent::*', el
|
||||
Menu.focus next
|
||||
else
|
||||
return
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
focus: (el) ->
|
||||
if focused = $.x 'parent::*/child::*[contains(@class,"focused")]', el
|
||||
$.rmClass focused, 'focused'
|
||||
for focused in $$ '.focused', el
|
||||
$.rmClass focused, 'focused'
|
||||
Menu.focusedEntry = el
|
||||
$.addClass el, 'focused'
|
||||
|
||||
addEntry: (entry) ->
|
||||
funk = (entry) ->
|
||||
{el, children} = entry
|
||||
$.addClass el, 'entry'
|
||||
$.on el, 'focus mouseover', (e) ->
|
||||
e.stopPropagation()
|
||||
Menu.focus @
|
||||
return unless children
|
||||
$.addClass el, 'hasSubMenu'
|
||||
for child in children
|
||||
funk child
|
||||
return
|
||||
funk entry
|
||||
Menu.entries.push entry
|
||||
|
||||
Keybinds =
|
||||
init: ->
|
||||
for node in $$ '[accesskey]'
|
||||
@ -951,7 +1211,7 @@ Keybinds =
|
||||
|
||||
qr: (thread, quote) ->
|
||||
if quote
|
||||
QR.quote.call $ '.postInfo > .postNum > a[title="Quote this post"]', $('.post.highlight', thread) or thread
|
||||
QR.quote.call $ '.postNum > a[title="Quote this post"]', $('.post.highlight', thread) or thread
|
||||
else
|
||||
QR.open()
|
||||
$('textarea', QR.el).focus()
|
||||
@ -1072,7 +1332,7 @@ QR =
|
||||
$.on d, 'dragstart dragend', QR.drag
|
||||
|
||||
node: (post) ->
|
||||
$.on $('.postInfo > .postNum > a[title="Quote this post"]', post.el), 'click', QR.quote
|
||||
$.on $('.postNum > a[title="Quote this post"]', post.el), 'click', QR.quote
|
||||
|
||||
open: ->
|
||||
if QR.el
|
||||
@ -1358,9 +1618,9 @@ QR =
|
||||
QR.characterCount.call $ 'textarea', QR.el
|
||||
$('#spoiler', QR.el).checked = @spoiler
|
||||
dragStart: ->
|
||||
$.addClass @, 'drag'
|
||||
$.addClass @, 'drag'
|
||||
dragEnter: ->
|
||||
$.addClass @, 'over'
|
||||
$.addClass @, 'over'
|
||||
dragLeave: ->
|
||||
$.rmClass @, 'over'
|
||||
dragOver: (e) ->
|
||||
@ -2390,6 +2650,8 @@ FileInfo =
|
||||
resolution: span.previousSibling.textContent.match(/\d+x\d+|PDF/)[0]
|
||||
fullname: span.title
|
||||
shortname: span.textContent
|
||||
# XXX GM/Scriptish
|
||||
node.setAttribute 'data-filename', span.title
|
||||
node.innerHTML = FileInfo.funk FileInfo
|
||||
setFormats: ->
|
||||
code = Conf['fileInfo'].replace /%([BKlLMnNprs])/g, (s, c) ->
|
||||
@ -2473,7 +2735,7 @@ Get =
|
||||
href = quote.getAttribute 'href'
|
||||
continue if href[0] is '/' # Cross-board quote, or board link
|
||||
quote.href = "/#{board}/res/#{href}" # Fix pathnames
|
||||
link = $ '.postInfo > .postNum > a[title="Highlight this post"]', pc
|
||||
link = $ '.postNum > a[title="Highlight this post"]', pc
|
||||
link.href = "/#{board}/res/#{threadID}#p#{postID}"
|
||||
link.nextSibling.href = "/#{board}/res/#{threadID}#q#{postID}"
|
||||
|
||||
@ -2640,7 +2902,7 @@ Get =
|
||||
innerHTML: "<img #{thumb_src} alt='#{if data.media_status isnt 'available' then "Error: #{data.media_status}, " else ''}#{if spoiler then 'Spoiler Image, ' else ''}#{filesize}' data-md5=#{data.media_hash} style='height: #{data.preview_h}px; width: #{data.preview_w}px;'>"
|
||||
$.after (if isOP then piM else pi), file
|
||||
|
||||
$.replace root.firstChild, pc
|
||||
$.replace root.firstChild, Get.cleanPost pc
|
||||
cb() if cb
|
||||
cleanPost: (root) ->
|
||||
post = $ '.post', root
|
||||
@ -2655,7 +2917,9 @@ Get =
|
||||
|
||||
# Don't mess with other features
|
||||
now = Date.now()
|
||||
for el in $$ '[id]', root
|
||||
els = $$ '[id]', root
|
||||
els.push root
|
||||
for el in els
|
||||
el.id = "#{now}_#{el.id}"
|
||||
|
||||
$.rmClass root, 'forwarded'
|
||||
@ -2924,7 +3188,7 @@ Quotify =
|
||||
m[1]
|
||||
else
|
||||
# Get the post's board, whether it's inlined or not.
|
||||
$('.postInfo > .postNum > a[title="Highlight this post"]', post.el).pathname.split('/')[1]
|
||||
$('.postNum > a[title="Highlight this post"]', post.el).pathname.split('/')[1]
|
||||
|
||||
nodes.push a = $.el 'a',
|
||||
# \u00A0 is nbsp
|
||||
@ -2957,28 +3221,32 @@ Quotify =
|
||||
$.replace node, nodes
|
||||
return
|
||||
|
||||
DeleteButton =
|
||||
DeleteLink =
|
||||
init: ->
|
||||
@a = $.el 'a',
|
||||
className: 'delete_button'
|
||||
innerHTML: '[ × ]'
|
||||
a = $.el 'a',
|
||||
className: 'delete_link'
|
||||
href: 'javascript:;'
|
||||
Main.callbacks.push @node
|
||||
node: (post) ->
|
||||
unless a = $ '.delete_button', post.el
|
||||
a = DeleteButton.a.cloneNode true
|
||||
$.add $('.postInfo', post.el), a
|
||||
$.on a, 'click', DeleteButton.delete
|
||||
Menu.addEntry
|
||||
el: a
|
||||
open: (post) ->
|
||||
if post.isArchived
|
||||
return false
|
||||
a.textContent = 'Delete this post'
|
||||
$.on a, 'click', DeleteLink.delete
|
||||
true
|
||||
delete: ->
|
||||
$.off @, 'click', DeleteButton.delete
|
||||
@innerHTML = '[ Deleting... ]'
|
||||
$.off @, 'click', DeleteLink.delete
|
||||
@textContent = 'Deleting...'
|
||||
|
||||
if m = d.cookie.match /4chan_pass=([^;]+)/
|
||||
pwd = decodeURIComponent m[1]
|
||||
else
|
||||
pwd = $.id('delPassword').value
|
||||
id = $.x('preceding-sibling::input', @).name
|
||||
board = $.x('preceding-sibling::span[1]/a', @).pathname.match(/\w+/)[0]
|
||||
pwd =
|
||||
if m = d.cookie.match /4chan_pass=([^;]+)/
|
||||
decodeURIComponent m[1]
|
||||
else
|
||||
$.id('delPassword').value
|
||||
|
||||
id = @parentNode.dataset.id
|
||||
board = $('.postNum > a[title="Highlight this post"]',
|
||||
$.id @parentNode.dataset.rootid).pathname.split('/')[1]
|
||||
self = this
|
||||
|
||||
form =
|
||||
@ -2986,13 +3254,12 @@ DeleteButton =
|
||||
pwd: pwd
|
||||
form[id] = 'delete'
|
||||
|
||||
$.ajax "https://sys.4chan.org/#{board}/imgboard.php", {
|
||||
onload: -> DeleteButton.load self, @response
|
||||
onerror: -> DeleteButton.error self
|
||||
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{board}/"), {
|
||||
onload: -> DeleteLink.load self, @response
|
||||
onerror: -> DeleteLink.error self
|
||||
}, {
|
||||
form: $.formData form
|
||||
}
|
||||
|
||||
load: (self, html) ->
|
||||
doc = d.implementation.createHTMLDocument ''
|
||||
doc.documentElement.innerHTML = html
|
||||
@ -3000,32 +3267,68 @@ DeleteButton =
|
||||
s = 'Banned!'
|
||||
else if msg = doc.getElementById 'errmsg' # error!
|
||||
s = msg.textContent
|
||||
$.on self, 'click', DeleteButton.delete
|
||||
$.on self, 'click', DeleteLink.delete
|
||||
else
|
||||
s = 'Deleted'
|
||||
self.innerHTML = "[ #{s} ]"
|
||||
self.textContent = s
|
||||
error: (self) ->
|
||||
self.innerHTML = '[ Connection error, please retry. ]'
|
||||
$.on self, 'click', DeleteButton.delete
|
||||
self.textContent = 'Connection error, please retry.'
|
||||
$.on self, 'click', DeleteLink.delete
|
||||
|
||||
ReportButton =
|
||||
ReportLink =
|
||||
init: ->
|
||||
@a = $.el 'a',
|
||||
className: 'report_button'
|
||||
innerHTML: '[ ! ]'
|
||||
a = $.el 'a',
|
||||
className: 'report_link'
|
||||
href: 'javascript:;'
|
||||
Main.callbacks.push @node
|
||||
node: (post) ->
|
||||
unless a = $ '.report_button', post.el
|
||||
a = ReportButton.a.cloneNode true
|
||||
$.add $('.postInfo', post.el), a
|
||||
$.on a, 'click', ReportButton.report
|
||||
textContent: 'Report this post'
|
||||
$.on a, 'click', @report
|
||||
Menu.addEntry
|
||||
el: a
|
||||
open: (post) ->
|
||||
post.isArchived is false
|
||||
report: ->
|
||||
url = "//sys.4chan.org/#{g.BOARD}/imgboard.php?mode=report&no=#{$.x('preceding-sibling::input', @).name}"
|
||||
a = $ '.postNum > a[title="Highlight this post"]', $.id @parentNode.dataset.rootid
|
||||
url = "//sys.4chan.org/#{a.pathname.split('/')[1]}/imgboard.php?mode=report&no=#{@parentNode.dataset.id}"
|
||||
id = Date.now()
|
||||
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"
|
||||
window.open url, id, set
|
||||
|
||||
DownloadLink =
|
||||
init: ->
|
||||
# Test for download feature support.
|
||||
return if $.el('a').download is undefined
|
||||
a = $.el 'a',
|
||||
className: 'download_link'
|
||||
textContent: 'Download file'
|
||||
Menu.addEntry
|
||||
el: a
|
||||
open: (post) ->
|
||||
unless post.img
|
||||
return false
|
||||
a.href = post.img.parentNode.href
|
||||
fileText = post.fileInfo.firstElementChild
|
||||
a.download =
|
||||
if Conf['File Info Formatting']
|
||||
fileText.dataset.filename
|
||||
else
|
||||
$('span', fileText).title
|
||||
true
|
||||
|
||||
ArchiveLink =
|
||||
init: ->
|
||||
a = $.el 'a',
|
||||
className: 'archive_link'
|
||||
target: '_blank'
|
||||
textContent: 'Archived post'
|
||||
Menu.addEntry
|
||||
el: a
|
||||
open: (post) ->
|
||||
path = $('.postNum > a[title="Highlight this post"]', post.el).pathname.split '/'
|
||||
if (href = Redirect.thread path[1], path[3], post.ID) is "//boards.4chan.org/#{path[1]}/"
|
||||
return false
|
||||
a.href = href
|
||||
true
|
||||
|
||||
ThreadStats =
|
||||
init: ->
|
||||
dialog = UI.dialog 'stats', 'bottom: 0; left: 0;', '<div class=move><span id=postcount>0</span> / <span id=imagecount>0</span></div>'
|
||||
@ -3506,11 +3809,23 @@ Main =
|
||||
if Conf['Image Hover']
|
||||
ImageHover.init()
|
||||
|
||||
if Conf['Report Button']
|
||||
ReportButton.init()
|
||||
if Conf['Menu']
|
||||
Menu.init()
|
||||
|
||||
if Conf['Delete Button']
|
||||
DeleteButton.init()
|
||||
if Conf['Report Link']
|
||||
ReportLink.init()
|
||||
|
||||
if Conf['Delete Link']
|
||||
DeleteLink.init()
|
||||
|
||||
if Conf['Filter']
|
||||
Filter.menuInit()
|
||||
|
||||
if Conf['Download Link']
|
||||
DownloadLink.init()
|
||||
|
||||
if Conf['Archive Link']
|
||||
ArchiveLink.init()
|
||||
|
||||
if Conf['Resurrect Quotes']
|
||||
Quotify.init()
|
||||
@ -3716,6 +4031,60 @@ a[href="javascript:;"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.menu_button {
|
||||
display: inline-block;
|
||||
}
|
||||
.menu_button > span {
|
||||
border-top: .5em solid;
|
||||
border-right: .3em solid transparent;
|
||||
border-left: .3em solid transparent;
|
||||
display: inline-block;
|
||||
margin: 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#menu {
|
||||
position: absolute;
|
||||
outline: none;
|
||||
}
|
||||
.entry {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .25);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
outline: none;
|
||||
padding: 3px 7px;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.entry:last-child {
|
||||
border: none;
|
||||
}
|
||||
.focused.entry {
|
||||
background: rgba(255, 255, 255, .33);
|
||||
}
|
||||
.entry.hasSubMenu {
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
.hasSubMenu::after {
|
||||
content: "";
|
||||
border-left: .5em solid;
|
||||
border-top: .3em solid transparent;
|
||||
border-bottom: .3em solid transparent;
|
||||
display: inline-block;
|
||||
margin: .3em;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
}
|
||||
.hasSubMenu:not(.focused) > .subMenu {
|
||||
display: none;
|
||||
}
|
||||
.subMenu {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user