Finish Quote Resurrecting. Add fetching posts from archives. Add post building.
This commit is contained in:
parent
59c9015c75
commit
dccd4bf9ba
315
4chan_x.user.js
315
4chan_x.user.js
@ -8,6 +8,7 @@
|
||||
// @match *://boards.4chan.org/*
|
||||
// @match *://images.4chan.org/*
|
||||
// @match *://sys.4chan.org/*
|
||||
// @match *://*.foolz.us/api/*
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
@ -73,7 +74,7 @@
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var $, $$, Board, Clone, Conf, Config, Get, Main, Post, QuoteBacklink, QuoteInline, QuotePreview, Quotify, Redirect, Thread, Time, UI, d, g,
|
||||
var $, $$, Board, Build, Clone, Conf, Config, Get, Main, Post, QuoteBacklink, QuoteInline, QuotePreview, Quotify, Redirect, Thread, Time, UI, d, g,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
@ -529,15 +530,6 @@
|
||||
p.setAttribute('onclick', 'return window');
|
||||
return p.onclick();
|
||||
})(),
|
||||
shortenFilename: function(filename, isOP) {
|
||||
var threshold;
|
||||
threshold = isOP ? 40 : 30;
|
||||
if (filename.length - 4 > threshold) {
|
||||
return "" + filename.slice(0, threshold - 5) + "(...)." + (filename.match(/\w+$/));
|
||||
} else {
|
||||
return filename;
|
||||
}
|
||||
},
|
||||
bytesToString: function(size) {
|
||||
var unit;
|
||||
unit = 0;
|
||||
@ -647,10 +639,13 @@
|
||||
return this.ID;
|
||||
};
|
||||
|
||||
function Post(root, thread, board) {
|
||||
var alt, anchor, bq, capcode, data, date, email, file, flag, i, info, name, node, nodes, post, quotelink, quotes, subject, text, thumb, tripcode, uniqueID, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2;
|
||||
function Post(root, thread, board, that) {
|
||||
var alt, anchor, bq, capcode, data, date, email, file, flag, i, info, name, node, nodes, post, quotelink, quotes, size, subject, text, thumb, tripcode, uniqueID, unit, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2;
|
||||
this.thread = thread;
|
||||
this.board = board;
|
||||
if (that == null) {
|
||||
that = {};
|
||||
}
|
||||
this.ID = +root.id.slice(2);
|
||||
post = $('.post', root);
|
||||
info = $('.postInfo', post);
|
||||
@ -729,16 +724,24 @@
|
||||
thumb: thumb,
|
||||
URL: anchor.href,
|
||||
MD5: thumb.dataset.md5,
|
||||
size: alt.match(/\d+(\.\d+)?\s\w+$/)[0],
|
||||
isSpoiler: $.hasClass(anchor, 'imgspoiler')
|
||||
};
|
||||
this.file.thumbURL = "" + location.protocol + "//thumbs.4chan.org/" + board + "/thumb/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg";
|
||||
size = +alt.match(/\d+(\.\d+)?/)[0];
|
||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf(alt.match(/\w+$/)[0]);
|
||||
while (unit--) {
|
||||
size *= 1024;
|
||||
}
|
||||
this.file.size = size;
|
||||
this.file.thumbURL = that.isArchived ? thumb.src : "" + location.protocol + "//thumbs.4chan.org/" + board + "/thumb/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg";
|
||||
this.file.name = $('span[title]', this.file.info).title;
|
||||
if (this.file.isImage = /(jpg|png|gif|svg)$/i.test(this.file.name)) {
|
||||
if (this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name)) {
|
||||
this.file.dimensions = this.file.text.textContent.match(/\d+x\d+/)[0];
|
||||
}
|
||||
}
|
||||
this.isReply = $.hasClass(post, 'reply');
|
||||
if (that.isArchived) {
|
||||
this.isDead = true;
|
||||
}
|
||||
this.clones = [];
|
||||
g.posts["" + board + "." + this] = thread.posts[this] = board.posts[this] = this;
|
||||
}
|
||||
@ -837,6 +840,9 @@
|
||||
this.file.text = $('.fileText', file);
|
||||
this.file.thumb = $('img[data-md5]', file);
|
||||
}
|
||||
if (origin.isDead) {
|
||||
this.isDead = true;
|
||||
}
|
||||
this.isClone = true;
|
||||
index = origin.clones.push(this) - 1;
|
||||
root.setAttribute('data-clone', index);
|
||||
@ -1087,7 +1093,7 @@
|
||||
postID = postID.match(/\d+/)[0];
|
||||
}
|
||||
path = threadID ? "" + board + "/thread/" + threadID : "" + board + "/post/" + postID;
|
||||
switch (board) {
|
||||
switch ("" + board) {
|
||||
case 'a':
|
||||
case 'co':
|
||||
case 'jp':
|
||||
@ -1164,6 +1170,134 @@
|
||||
}
|
||||
};
|
||||
|
||||
Build = {
|
||||
shortFilename: function(filename, isReply) {
|
||||
var threshold;
|
||||
threshold = isReply ? 30 : 40;
|
||||
if (filename.length - 4 > threshold) {
|
||||
return "" + filename.slice(0, threshold - 5) + "(...)." + filename.slice(-3);
|
||||
} else {
|
||||
return filename;
|
||||
}
|
||||
},
|
||||
post: function(o) {
|
||||
var board, bq, capcode, comment, container, date, dateUTC, email, file, fl, flag, flagTitle, html, isOP, name, pi, post, postID, subject, threadID, tripcode, uniqueID;
|
||||
postID = o.postID, threadID = o.threadID, board = o.board, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flag = o.flag, flagTitle = o.flagTitle, date = o.date, dateUTC = o.dateUTC, comment = o.comment, file = o.file;
|
||||
isOP = postID === threadID;
|
||||
html = [];
|
||||
html.push("<input type=checkbox name=" + postID + " value=delete> ");
|
||||
html.push("<span class=subject>" + subject + "</span> ");
|
||||
html.push("<span class='nameBlock");
|
||||
html.push((function() {
|
||||
switch (capcode) {
|
||||
case 'M':
|
||||
return ' capcodeMod';
|
||||
case 'A':
|
||||
return ' capcodeAdmin';
|
||||
case 'D':
|
||||
return ' capcodeDeveloper';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
})());
|
||||
html.push("'>");
|
||||
if (email) {
|
||||
html.push("<a href=mailto:" + email + " class=useremail>");
|
||||
}
|
||||
html.push("<span class=name>" + name + "</span>");
|
||||
if (tripcode) {
|
||||
html.push(" <span class=postertrip>" + tripcode + "</span>");
|
||||
}
|
||||
if (uniqueID) {
|
||||
html.push(" <span class='posteruid id_" + uniqueID + "'>(ID: <span class=hand title='Highlight posts by this ID'>" + uniqueID + "</span>)</span>");
|
||||
}
|
||||
switch (capcode) {
|
||||
case 'M':
|
||||
html.push(' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>');
|
||||
html.push(' <img src=//static.4chan.org/image/modicon.gif alt="This user is a 4chan Moderator." title="This user is a 4chan Moderator." class=identityIcon>');
|
||||
break;
|
||||
case 'A':
|
||||
html.push(' <strong class="capcode hand id_admin" title="Highlight posts by the Administrator">## Admin</strong>');
|
||||
html.push(' <img src=//static.4chan.org/image/adminicon.gif alt="This user is the 4chan Administrator." title="This user is the 4chan Administrator." class=identityIcon>');
|
||||
break;
|
||||
case 'D':
|
||||
html.push(' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>');
|
||||
html.push(' <img src=//static.4chan.org/image/developericon.gif alt="This user is a 4chan Developer." title="This user is a 4chan Developer." class=identityIcon>');
|
||||
}
|
||||
if (email) {
|
||||
html.push('</a>');
|
||||
}
|
||||
if (flag) {
|
||||
html.push(" <img src=//static.4chan.org/image/country/" + (flag.toLowerCase()) + ".gif alt=" + flag + " title='" + flagTitle + "' class=countryFlag>");
|
||||
}
|
||||
html.push('</span> ');
|
||||
html.push("<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span> ");
|
||||
html.push('<span class="postNum desktop">');
|
||||
html.push("<a href=/" + board + "/res/" + threadID + "#p" + postID + " title='Highlight this post'>No.</a>");
|
||||
html.push("<a href=/" + board + "/res/" + threadID + "#q" + postID + " title='Quote this post'>" + postID + "</a>");
|
||||
html.push('</span>');
|
||||
pi = $.el('div', {
|
||||
id: "pi" + postID,
|
||||
className: 'postInfo desktop',
|
||||
innerHTML: html.join('')
|
||||
});
|
||||
bq = $.el('blockquote', {
|
||||
id: "m" + postID,
|
||||
className: 'postMessage',
|
||||
innerHTML: comment
|
||||
});
|
||||
if (file.name) {
|
||||
html = [];
|
||||
html.push('<div class=fileInfo>');
|
||||
html.push("<span id=fT" + postID + " class=fileText" + (file.isSpoiler ? " title='file.name'" : '') + ">File: ");
|
||||
html.push("<a href=" + file.url + " target=_blank>" + file.origin + "</a>");
|
||||
html.push('-(');
|
||||
if (file.isSpoiler) {
|
||||
html.push('Spoiler Image, ');
|
||||
}
|
||||
html.push("" + ($.bytesToString(file.size)) + ", ");
|
||||
html.push(/\.pdf$/i.test(file.name) ? "PDF" : "" + file.width + "x" + file.height);
|
||||
if (!file.isSpoiler) {
|
||||
html.push(", <span title='" + file.name + "'>" + (Build.shortFilename(file.name)) + "</span>");
|
||||
}
|
||||
html.push(")</span></div>");
|
||||
html.push("<a class='fileThumb" + (file.isSpoiler ? ' imgspoiler' : '') + "' href=" + file.url + " target=_blank>");
|
||||
html.push("<img src=" + file.turl + " alt='" + (file.isSpoiler ? 'Spoiler Image, ' : '') + ($.bytesToString(file.size)) + "' data-md5='" + file.MD5 + "' style='height:" + file.theight + "px;width:" + file.twidth + "px'>");
|
||||
html.push('</a>');
|
||||
fl = $.el('div', {
|
||||
id: "f" + postID,
|
||||
className: 'file',
|
||||
innerHTML: html.join('')
|
||||
});
|
||||
}
|
||||
post = $.el('div', {
|
||||
id: "p" + postID,
|
||||
className: "post " + (isOP ? 'op' : 'reply')
|
||||
});
|
||||
if (fl && isOP) {
|
||||
$.add(post, fl);
|
||||
}
|
||||
$.add(post, pi);
|
||||
if (fl && !isOP) {
|
||||
$.add(post, fl);
|
||||
}
|
||||
$.add(post, bq);
|
||||
container = $.el('div', {
|
||||
id: "pc" + postID,
|
||||
className: "postContainer " + (isOP ? 'op' : 'reply') + "Container"
|
||||
});
|
||||
if (!isOP) {
|
||||
$.add(container, $.el('div', {
|
||||
id: "sa" + postID,
|
||||
className: 'sideArrows',
|
||||
textContent: '>>'
|
||||
}));
|
||||
}
|
||||
$.add(container, post);
|
||||
return container;
|
||||
}
|
||||
};
|
||||
|
||||
Get = {
|
||||
postFromRoot: function(root) {
|
||||
var board, index, link, post, postID;
|
||||
@ -1185,6 +1319,10 @@
|
||||
board = path[1];
|
||||
threadID = path[3];
|
||||
postID = link.hash.slice(2);
|
||||
} else {
|
||||
board = link.dataset.board;
|
||||
threadID = '';
|
||||
postID = link.dataset.postid;
|
||||
}
|
||||
return {
|
||||
board: board,
|
||||
@ -1193,7 +1331,7 @@
|
||||
};
|
||||
},
|
||||
postClone: function(board, threadID, postID, root) {
|
||||
var clone, origin;
|
||||
var clone, origin, url;
|
||||
if (origin = g.posts["" + board + "." + postID]) {
|
||||
clone = origin.addClone();
|
||||
Main.callbackNodes(Post, [clone]);
|
||||
@ -1203,7 +1341,11 @@
|
||||
root.textContent = "Loading post No." + postID + "...";
|
||||
if (threadID) {
|
||||
return $.cache("/" + board + "/res/" + threadID, function() {
|
||||
return Get.parsePost(this, board, threadID, postID, root);
|
||||
return Get.fetchedPost(this, board, threadID, postID, root);
|
||||
});
|
||||
} else if (url = Redirect.post(board, postID)) {
|
||||
return $.cache(url, function() {
|
||||
return Get.archivedPost(this, board, postID, root);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -1219,18 +1361,31 @@
|
||||
}
|
||||
return root;
|
||||
},
|
||||
parsePost: function(req, board, threadID, postID, root) {
|
||||
var clone, doc, href, inBoard, inThread, link, pc, post, quote, status, _i, _len, _ref;
|
||||
fetchedPost: function(req, board, threadID, postID, root) {
|
||||
var clone, doc, href, inBoard, inThread, link, pc, post, quote, status, url, _i, _len, _ref;
|
||||
status = req.status;
|
||||
if (status !== 200) {
|
||||
$.addClass(root, 'warning');
|
||||
root.textContent = status === 404 ? "Thread No." + threadID + " has not been found." : "Error " + req.status + ": " + req.statusText + ".";
|
||||
if (url = Redirect.post(board, postID)) {
|
||||
$.cache(url, function() {
|
||||
return Get.archivedPost(this, board, postID, root);
|
||||
});
|
||||
} else {
|
||||
$.addClass(root, 'warning');
|
||||
root.textContent = status === 404 ? "Thread No." + threadID + " has not been found." : "Error " + req.status + ": " + req.statusText + ".";
|
||||
}
|
||||
return;
|
||||
}
|
||||
doc = d.implementation.createHTMLDocument('');
|
||||
doc.documentElement.innerHTML = req.response;
|
||||
if (!(pc = doc.getElementById("pc" + postID))) {
|
||||
root.textContent = "Post No." + postID + " has not been found.";
|
||||
if (url = Redirect.post(board, postID)) {
|
||||
$.cache(url, function() {
|
||||
return Get.archivedPost(this, board, postID, root);
|
||||
});
|
||||
} else {
|
||||
$.addClass(root, 'warning');
|
||||
root.textContent = "Post No." + postID + " has not been found.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
pc = d.importNode(pc, true);
|
||||
@ -1256,6 +1411,86 @@
|
||||
clone = post.addClone();
|
||||
Main.callbackNodes(Post, [clone]);
|
||||
return $.replace(root.firstChild, Get.cleanRoot(clone));
|
||||
},
|
||||
archivedPost: function(req, board, postID, root) {
|
||||
var bq, clone, comment, data, post, postContainer, thread, threadID;
|
||||
data = JSON.parse(req.response);
|
||||
if (data.error) {
|
||||
$.addClass(root, 'warning');
|
||||
root.textContent = data.error;
|
||||
return;
|
||||
}
|
||||
bq = $.el('blockquote', {
|
||||
textContent: data.comment
|
||||
});
|
||||
bq.innerHTML = bq.innerHTML.replace(/\n|\[\/?b\]|\[\/?spoiler\]|\[\/?code\]|\[\/?moot\]|\[\/?banned\]/g, function(text) {
|
||||
switch (text) {
|
||||
case '\n':
|
||||
return '<br>';
|
||||
case '[b]':
|
||||
return '<b>';
|
||||
case '[/b]':
|
||||
return '</b>';
|
||||
case '[spoiler]':
|
||||
return '<span class=spoiler>';
|
||||
case '[/spoiler]':
|
||||
return '</span>';
|
||||
case '[code]':
|
||||
return '<pre class=prettyprint>';
|
||||
case '[/code]':
|
||||
return '</pre>';
|
||||
case '[moot]':
|
||||
return '<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">';
|
||||
case '[/moot]':
|
||||
return '</div>';
|
||||
case '[banned]':
|
||||
return '<b style="color: red;">';
|
||||
case '[/banned]':
|
||||
return '</b>';
|
||||
}
|
||||
});
|
||||
comment = bq.innerHTML.replace(/(^|>)(>[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3');
|
||||
threadID = data.thread_num;
|
||||
postContainer = Build.post({
|
||||
postID: postID,
|
||||
threadID: threadID,
|
||||
board: board,
|
||||
name: data.name,
|
||||
capcode: data.capcode,
|
||||
tripcode: data.trip,
|
||||
uniqueID: data.poster_hash,
|
||||
email: data.email,
|
||||
subject: data.title,
|
||||
flag: data.poster_country,
|
||||
date: data.fourchan_date,
|
||||
dateUTC: data.timestamp,
|
||||
comment: comment,
|
||||
file: {
|
||||
name: data.media_filename,
|
||||
origin: data.media_orig,
|
||||
url: data.media_link || data.remote_media_link,
|
||||
height: data.media_h,
|
||||
width: data.media_w,
|
||||
isSpoiler: data.spoiler === '1',
|
||||
MD5: data.media_hash,
|
||||
size: data.media_size,
|
||||
turl: data.thumb_link || ("//thumbs.4chan.org/" + board + "/thumb/" + data.preview_orig),
|
||||
theight: data.preview_h,
|
||||
twidth: data.preview_w
|
||||
}
|
||||
});
|
||||
board = g.boards[board] || new Board(board);
|
||||
thread = g.threads["" + board + "." + threadID] || new Thread(threadID, board);
|
||||
post = new Post(postContainer, thread, board, {
|
||||
isArchived: true
|
||||
});
|
||||
Main.callbackNodes(Post, [post]);
|
||||
if (!root.parentNode) {
|
||||
return;
|
||||
}
|
||||
clone = post.addClone();
|
||||
Main.callbackNodes(Post, [clone]);
|
||||
return $.replace(root.firstChild, Get.cleanRoot(clone));
|
||||
}
|
||||
};
|
||||
|
||||
@ -1267,7 +1502,7 @@
|
||||
});
|
||||
},
|
||||
node: function() {
|
||||
var ID, a, board, data, i, index, m, node, nodes, quote, quoteID, quotes, snapshot, text, _i, _j, _len, _ref;
|
||||
var ID, a, board, data, i, index, m, node, nodes, post, quote, quoteID, quotes, snapshot, text, _i, _j, _len, _ref;
|
||||
if (this.isClone) {
|
||||
return;
|
||||
}
|
||||
@ -1288,13 +1523,37 @@
|
||||
ID = quote.match(/\d+$/)[0];
|
||||
board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
|
||||
quoteID = "" + board + "." + ID;
|
||||
if (post = g.posts[quoteID]) {
|
||||
if (post.isDead) {
|
||||
a = $.el('a', {
|
||||
href: Redirect.thread(board, 0, ID),
|
||||
className: 'quotelink deadlink',
|
||||
textContent: "" + quote + "\u00A0(Dead)",
|
||||
target: '_blank'
|
||||
});
|
||||
} else {
|
||||
a = $.el('a', {
|
||||
href: "/" + board + "/" + post.thread + "/res/#p" + ID,
|
||||
className: 'quotelink',
|
||||
textContent: quote
|
||||
});
|
||||
}
|
||||
} else {
|
||||
a = $.el('a', {
|
||||
href: Redirect.thread(board, 0, ID),
|
||||
className: 'deadlink',
|
||||
target: '_blank',
|
||||
textContent: this.isDead ? quote : "" + quote + "\u00A0(Dead)"
|
||||
});
|
||||
if (Redirect.post(board, ID)) {
|
||||
$.addClass(a, 'quotelink');
|
||||
a.setAttribute('data-board', board);
|
||||
a.setAttribute('data-postid', ID);
|
||||
}
|
||||
}
|
||||
if (this.quotes.indexOf(quoteID) === -1) {
|
||||
this.quotes.push(quoteID);
|
||||
}
|
||||
a = $.el('a', {
|
||||
textContent: quote,
|
||||
className: 'quotelink deadlink'
|
||||
});
|
||||
this.nodes.quotelinks.push(a);
|
||||
nodes.push(a);
|
||||
data = data.slice(index + quote.length);
|
||||
|
||||
1
Cakefile
1
Cakefile
@ -19,6 +19,7 @@ HEADER = """
|
||||
// @match *://boards.4chan.org/*
|
||||
// @match *://images.4chan.org/*
|
||||
// @match *://sys.4chan.org/*
|
||||
// @match *://*.foolz.us/api/*
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
|
||||
341
script.coffee
341
script.coffee
@ -407,15 +407,6 @@ $.extend $,
|
||||
p.setAttribute 'onclick', 'return window'
|
||||
p.onclick()
|
||||
)()
|
||||
shortenFilename: (filename, isOP) ->
|
||||
# FILENAME SHORTENING SCIENCE:
|
||||
# OPs have a +10 characters threshold.
|
||||
# The file extension is not taken into account.
|
||||
threshold = if isOP then 40 else 30
|
||||
if filename.length - 4 > threshold
|
||||
"#{filename[...threshold - 5]}(...).#{filename.match(/\w+$/)}"
|
||||
else
|
||||
filename
|
||||
bytesToString: (size) ->
|
||||
unit = 0 # Bytes
|
||||
while size >= 1024
|
||||
@ -502,7 +493,7 @@ class Post
|
||||
callbacks: []
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (root, @thread, @board) ->
|
||||
constructor: (root, @thread, @board, that={}) ->
|
||||
@ID = +root.id[2..]
|
||||
|
||||
post = $ '.post', root
|
||||
@ -583,14 +574,23 @@ class Post
|
||||
thumb: thumb
|
||||
URL: anchor.href
|
||||
MD5: thumb.dataset.md5
|
||||
size: alt.match(/\d+(\.\d+)?\s\w+$/)[0]
|
||||
isSpoiler: $.hasClass anchor, 'imgspoiler'
|
||||
@file.thumbURL = "#{location.protocol}//thumbs.4chan.org/#{board}/thumb/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
|
||||
size = +alt.match(/\d+(\.\d+)?/)[0]
|
||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf alt.match(/\w+$/)[0]
|
||||
while unit--
|
||||
size *= 1024
|
||||
@file.size = size
|
||||
@file.thumbURL =
|
||||
if that.isArchived
|
||||
thumb.src
|
||||
else
|
||||
"#{location.protocol}//thumbs.4chan.org/#{board}/thumb/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
|
||||
@file.name = $('span[title]', @file.info).title
|
||||
if @file.isImage = /(jpg|png|gif|svg)$/i.test @file.name # I want to believe.
|
||||
if @file.isImage = /(jpg|png|gif)$/i.test @file.name
|
||||
@file.dimensions = @file.text.textContent.match(/\d+x\d+/)[0]
|
||||
|
||||
@isReply = $.hasClass post, 'reply'
|
||||
@isDead = true if that.isArchived
|
||||
@clones = []
|
||||
g.posts["#{board}.#{@}"] = thread.posts[@] = board.posts[@] = @
|
||||
|
||||
@ -663,6 +663,7 @@ class Clone extends Post
|
||||
@file.text = $ '.fileText', file
|
||||
@file.thumb = $ 'img[data-md5]', file
|
||||
|
||||
@isDead = true if origin.isDead
|
||||
@isClone = true
|
||||
index = origin.clones.push(@) - 1
|
||||
root.setAttribute 'data-clone', index
|
||||
@ -956,7 +957,7 @@ Redirect =
|
||||
"#{board}/thread/#{threadID}"
|
||||
else
|
||||
"#{board}/post/#{postID}"
|
||||
switch board
|
||||
switch "#{board}"
|
||||
when 'a', 'co', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'wsg', 'dev', 'foolz'
|
||||
url = "//archive.foolz.us/#{path}/"
|
||||
if threadID and postID
|
||||
@ -990,6 +991,125 @@ Redirect =
|
||||
url = "//boards.4chan.org/#{board}/"
|
||||
url or ''
|
||||
|
||||
Build =
|
||||
shortFilename: (filename, isReply) ->
|
||||
# FILENAME SHORTENING SCIENCE:
|
||||
# OPs have a +10 characters threshold.
|
||||
# The file extension is not taken into account.
|
||||
threshold = if isReply then 30 else 40
|
||||
if filename.length - 4 > threshold
|
||||
"#{filename[...threshold - 5]}(...).#{filename[-3..]}"
|
||||
else
|
||||
filename
|
||||
post: (o) ->
|
||||
{
|
||||
postID, threadID, board
|
||||
name, capcode, tripcode, uniqueID, email, subject, flag, flagTitle, date, dateUTC, comment
|
||||
file
|
||||
} = o
|
||||
isOP = postID is threadID
|
||||
|
||||
# post info
|
||||
html = []
|
||||
# input
|
||||
html.push "<input type=checkbox name=#{postID} value=delete> "
|
||||
# subject
|
||||
html.push "<span class=subject>#{subject}</span> "
|
||||
# name block
|
||||
html.push "<span class='nameBlock"
|
||||
html.push switch capcode
|
||||
when 'M' then ' capcodeMod'
|
||||
when 'A' then ' capcodeAdmin'
|
||||
when 'D' then ' capcodeDeveloper'
|
||||
else ''
|
||||
html.push "'>"
|
||||
# mail start
|
||||
html.push "<a href=mailto:#{email} class=useremail>" if email
|
||||
# name
|
||||
html.push "<span class=name>#{name}</span>"
|
||||
# tripcode
|
||||
html.push " <span class=postertrip>#{tripcode}</span>" if tripcode
|
||||
# user id
|
||||
html.push " <span class='posteruid id_#{uniqueID}'>(ID: <span class=hand title='Highlight posts by this ID'>#{uniqueID}</span>)</span>" if uniqueID
|
||||
# capcode
|
||||
switch capcode
|
||||
when 'M'
|
||||
html.push ' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>'
|
||||
html.push ' <img src=//static.4chan.org/image/modicon.gif alt="This user is a 4chan Moderator." title="This user is a 4chan Moderator." class=identityIcon>'
|
||||
when 'A'
|
||||
html.push ' <strong class="capcode hand id_admin" title="Highlight posts by the Administrator">## Admin</strong>'
|
||||
html.push ' <img src=//static.4chan.org/image/adminicon.gif alt="This user is the 4chan Administrator." title="This user is the 4chan Administrator." class=identityIcon>'
|
||||
when 'D'
|
||||
html.push ' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>'
|
||||
html.push ' <img src=//static.4chan.org/image/developericon.gif alt="This user is a 4chan Developer." title="This user is a 4chan Developer." class=identityIcon>'
|
||||
# mail end
|
||||
html.push '</a>' if email
|
||||
# flag
|
||||
# XXX what about troll flags in /pol/?
|
||||
html.push " <img src=//static.4chan.org/image/country/#{flag.toLowerCase()}.gif alt=#{flag} title='#{flagTitle}' class=countryFlag>" if flag
|
||||
# end name block
|
||||
html.push '</span> '
|
||||
# date
|
||||
html.push "<span class=dateTime data-utc=#{dateUTC}>#{date}</span> "
|
||||
# post num
|
||||
html.push '<span class="postNum desktop">'
|
||||
html.push "<a href=/#{board}/res/#{threadID}#p#{postID} title='Highlight this post'>No.</a>"
|
||||
html.push "<a href=/#{board}/res/#{threadID}#q#{postID} title='Quote this post'>#{postID}</a>"
|
||||
# XXX closed/sticky?
|
||||
html.push '</span>'
|
||||
pi = $.el 'div',
|
||||
id: "pi#{postID}"
|
||||
className: 'postInfo desktop'
|
||||
innerHTML: html.join ''
|
||||
|
||||
bq = $.el 'blockquote',
|
||||
id: "m#{postID}"
|
||||
className: 'postMessage'
|
||||
innerHTML: comment
|
||||
|
||||
# file
|
||||
if file.name # XXX need to fix support of Flash
|
||||
html = []
|
||||
html.push '<div class=fileInfo>'
|
||||
html.push "<span id=fT#{postID} class=fileText#{if file.isSpoiler then " title='file.name'" else ''}>File: "
|
||||
html.push "<a href=#{file.url} target=_blank>#{file.origin}</a>"
|
||||
html.push '-('
|
||||
html.push 'Spoiler Image, ' if file.isSpoiler
|
||||
html.push "#{$.bytesToString file.size}, "
|
||||
html.push if /\.pdf$/i.test file.name
|
||||
"PDF"
|
||||
else
|
||||
"#{file.width}x#{file.height}"
|
||||
html.push ", <span title='#{file.name}'>#{Build.shortFilename file.name}</span>" unless file.isSpoiler
|
||||
html.push ")</span></div>"
|
||||
html.push "<a class='fileThumb#{if file.isSpoiler then ' imgspoiler' else ''}' href=#{file.url} target=_blank>"
|
||||
html.push "<img src=#{file.turl} alt='#{if file.isSpoiler then 'Spoiler Image, ' else ''}#{$.bytesToString file.size}' data-md5='#{file.MD5}' style='height:#{file.theight}px;width:#{file.twidth}px'>"
|
||||
html.push '</a>'
|
||||
fl = $.el 'div',
|
||||
id: "f#{postID}"
|
||||
className: 'file'
|
||||
innerHTML: html.join ''
|
||||
|
||||
post = $.el 'div',
|
||||
id: "p#{postID}"
|
||||
className: "post #{if isOP then 'op' else 'reply'}"
|
||||
$.add post, fl if fl and isOP
|
||||
$.add post, pi
|
||||
$.add post, fl if fl and !isOP
|
||||
$.add post, bq
|
||||
|
||||
container = $.el 'div',
|
||||
id: "pc#{postID}"
|
||||
className: "postContainer #{if isOP then 'op' else 'reply'}Container"
|
||||
unless isOP
|
||||
$.add container, $.el 'div',
|
||||
id: "sa#{postID}"
|
||||
className: 'sideArrows'
|
||||
textContent: '>>'
|
||||
$.add container, post
|
||||
|
||||
container
|
||||
|
||||
Get =
|
||||
postFromRoot: (root) ->
|
||||
link = $ 'a[title="Highlight this post"]', root
|
||||
@ -1004,11 +1124,10 @@ Get =
|
||||
board = path[1]
|
||||
threadID = path[3]
|
||||
postID = link.hash[2..]
|
||||
# XXX
|
||||
# else # quote resurrection
|
||||
# board = ???
|
||||
# threadID = ???
|
||||
# postID = ???
|
||||
else # resurrected quote
|
||||
board = link.dataset.board
|
||||
threadID = ''
|
||||
postID = link.dataset.postid
|
||||
return {
|
||||
board: board
|
||||
threadID: threadID
|
||||
@ -1024,30 +1143,29 @@ Get =
|
||||
root.textContent = "Loading post No.#{postID}..."
|
||||
if threadID
|
||||
$.cache "/#{board}/res/#{threadID}", ->
|
||||
Get.parsePost @, board, threadID, postID, root
|
||||
# else if url = Redirect.post board, postID
|
||||
# $.cache url, ->
|
||||
# Get.parseArchivedPost @, board, postID, root
|
||||
Get.fetchedPost @, board, threadID, postID, root
|
||||
else if url = Redirect.post board, postID
|
||||
$.cache url, ->
|
||||
Get.archivedPost @, board, postID, root
|
||||
cleanRoot: (clone) ->
|
||||
{root, post} = clone.nodes
|
||||
for child in Array::slice.call root.childNodes
|
||||
$.rm child unless child is post
|
||||
root
|
||||
parsePost: (req, board, threadID, postID, root) ->
|
||||
fetchedPost: (req, board, threadID, postID, root) ->
|
||||
{status} = req
|
||||
if status isnt 200
|
||||
# The thread can die by the time we check a quote.
|
||||
# XXX
|
||||
# if url = Redirect.post board, postID
|
||||
# $.cache url, ->
|
||||
# Get.parseArchivedPost @, board, postID, root
|
||||
# else
|
||||
$.addClass root, 'warning'
|
||||
root.textContent =
|
||||
if status is 404
|
||||
"Thread No.#{threadID} has not been found."
|
||||
else
|
||||
"Error #{req.status}: #{req.statusText}."
|
||||
if url = Redirect.post board, postID
|
||||
$.cache url, ->
|
||||
Get.archivedPost @, board, postID, root
|
||||
else
|
||||
$.addClass root, 'warning'
|
||||
root.textContent =
|
||||
if status is 404
|
||||
"Thread No.#{threadID} has not been found."
|
||||
else
|
||||
"Error #{req.status}: #{req.statusText}."
|
||||
return
|
||||
|
||||
doc = d.implementation.createHTMLDocument ''
|
||||
@ -1055,12 +1173,12 @@ Get =
|
||||
|
||||
unless pc = doc.getElementById "pc#{postID}"
|
||||
# The post can be deleted by the time we check a quote.
|
||||
# XXX
|
||||
# if url = Redirect.post board, postID
|
||||
# $.cache url, ->
|
||||
# Get.parseArchivedPost @, board, postID, root
|
||||
# else
|
||||
root.textContent = "Post No.#{postID} has not been found."
|
||||
if url = Redirect.post board, postID
|
||||
$.cache url, ->
|
||||
Get.archivedPost @, board, postID, root
|
||||
else
|
||||
$.addClass root, 'warning'
|
||||
root.textContent = "Post No.#{postID} has not been found."
|
||||
return
|
||||
pc = d.importNode pc, true
|
||||
|
||||
@ -1079,6 +1197,96 @@ Get =
|
||||
post = new Post pc, inThread, inBoard
|
||||
Main.callbackNodes Post, [post]
|
||||
|
||||
# Stop here if the container has been removed while loading.
|
||||
return unless root.parentNode
|
||||
clone = post.addClone()
|
||||
Main.callbackNodes Post, [clone]
|
||||
$.replace root.firstChild, Get.cleanRoot clone
|
||||
archivedPost: (req, board, postID, root) ->
|
||||
data = JSON.parse req.response
|
||||
if data.error
|
||||
$.addClass root, 'warning'
|
||||
root.textContent = data.error
|
||||
return
|
||||
|
||||
# convert comment to html
|
||||
bq = $.el 'blockquote', textContent: data.comment # set this first to convert text to HTML entities
|
||||
# https://github.com/eksopl/fuuka/blob/master/Board/Yotsuba.pm#L413-452
|
||||
# https://github.com/eksopl/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138
|
||||
bq.innerHTML = bq.innerHTML.replace ///
|
||||
\n
|
||||
| \[/?b\]
|
||||
| \[/?spoiler\]
|
||||
| \[/?code\]
|
||||
| \[/?moot\]
|
||||
| \[/?banned\]
|
||||
///g, (text) ->
|
||||
switch text
|
||||
when '\n'
|
||||
'<br>'
|
||||
when '[b]'
|
||||
'<b>'
|
||||
when '[/b]'
|
||||
'</b>'
|
||||
when '[spoiler]'
|
||||
'<span class=spoiler>'
|
||||
when '[/spoiler]'
|
||||
'</span>'
|
||||
when '[code]'
|
||||
'<pre class=prettyprint>'
|
||||
when '[/code]'
|
||||
'</pre>'
|
||||
when '[moot]'
|
||||
'<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">'
|
||||
when '[/moot]'
|
||||
'</div>'
|
||||
when '[banned]'
|
||||
'<b style="color: red;">'
|
||||
when '[/banned]'
|
||||
'</b>'
|
||||
# greentext
|
||||
comment = bq.innerHTML.replace /(^|>)(>[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3'
|
||||
|
||||
threadID = data.thread_num
|
||||
postContainer = Build.post
|
||||
# id
|
||||
postID: postID
|
||||
threadID: threadID
|
||||
board: board
|
||||
# info
|
||||
name: data.name
|
||||
capcode: data.capcode
|
||||
tripcode: data.trip
|
||||
uniqueID: data.poster_hash
|
||||
email: data.email
|
||||
subject: data.title
|
||||
flag: data.poster_country
|
||||
# XXX flagTitle: data.???
|
||||
date: data.fourchan_date
|
||||
dateUTC: data.timestamp
|
||||
comment: comment
|
||||
# file
|
||||
file:
|
||||
name: data.media_filename
|
||||
origin: data.media_orig
|
||||
url: data.media_link or data.remote_media_link
|
||||
height: data.media_h
|
||||
width: data.media_w
|
||||
isSpoiler: data.spoiler is '1'
|
||||
MD5: data.media_hash
|
||||
size: data.media_size
|
||||
turl: data.thumb_link or "//thumbs.4chan.org/#{board}/thumb/#{data.preview_orig}"
|
||||
theight: data.preview_h
|
||||
twidth: data.preview_w
|
||||
|
||||
board = g.boards[board] or
|
||||
new Board board
|
||||
thread = g.threads["#{board}.#{threadID}"] or
|
||||
new Thread threadID, board
|
||||
post = new Post postContainer, thread, board,
|
||||
isArchived: true
|
||||
Main.callbackNodes Post, [post]
|
||||
|
||||
# Stop here if the container has been removed while loading.
|
||||
return unless root.parentNode
|
||||
clone = post.addClone()
|
||||
@ -1120,28 +1328,37 @@ Quotify =
|
||||
@board.ID
|
||||
|
||||
quoteID = "#{board}.#{ID}"
|
||||
|
||||
# \u00A0 is nbsp
|
||||
if post = g.posts[quoteID]
|
||||
if post.isDead
|
||||
a = $.el 'a',
|
||||
href: Redirect.thread board, 0, ID
|
||||
className: 'quotelink deadlink'
|
||||
textContent: "#{quote}\u00A0(Dead)"
|
||||
target: '_blank'
|
||||
else
|
||||
# Don't (Dead) when quotifying in an archived post,
|
||||
# and we know the post still exists.
|
||||
a = $.el 'a',
|
||||
href: "/#{board}/#{post.thread}/res/#p#{ID}"
|
||||
className: 'quotelink'
|
||||
textContent: quote
|
||||
else
|
||||
a = $.el 'a',
|
||||
href: Redirect.thread board, 0, ID
|
||||
className: 'deadlink'
|
||||
target: '_blank'
|
||||
# Don't (Dead) when quotifying in an archived post,
|
||||
# and we don't know anything about the post.
|
||||
textContent: if @isDead then quote else "#{quote}\u00A0(Dead)"
|
||||
if Redirect.post board, ID
|
||||
$.addClass a, 'quotelink'
|
||||
a.setAttribute 'data-board', board
|
||||
a.setAttribute 'data-postid', ID
|
||||
|
||||
if @quotes.indexOf(quoteID) is -1
|
||||
@quotes.push quoteID
|
||||
|
||||
a = $.el 'a',
|
||||
# \u00A0 is nbsp
|
||||
# textContent: "#{quote}\u00A0(Dead)"
|
||||
textContent: quote
|
||||
# XXX
|
||||
className: 'quotelink deadlink'
|
||||
|
||||
# if board is g.BOARD and $.id "p#{ID}"
|
||||
# a.href = "#p#{ID}"
|
||||
# a.className = 'quotelink'
|
||||
# else
|
||||
# a.href = Redirect.thread board, 0, ID
|
||||
# a.className = 'deadlink'
|
||||
# a.target = '_blank'
|
||||
# if Redirect.post board, ID
|
||||
# $.addClass a, 'quotelink'
|
||||
# a.setAttribute 'data-board', board
|
||||
# a.setAttribute 'data-id', ID
|
||||
|
||||
@nodes.quotelinks.push a
|
||||
nodes.push a
|
||||
data = data[index + quote.length..]
|
||||
@ -1242,7 +1459,7 @@ QuoteInline =
|
||||
$.x 'ancestor-or-self::*[parent::blockquote][1]', inline
|
||||
root = $.x "following-sibling::div[@id='i#{postID}'][1]", root
|
||||
continue unless el = root.firstElementChild
|
||||
post = g.posts["#{board}.#{postID}"]
|
||||
post = g.posts["#{board}.#{postID}"]
|
||||
post.rmClone el.dataset.clone
|
||||
|
||||
if Conf['Forward Hiding'] and
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user