Finish Quote Resurrecting. Add fetching posts from archives. Add post building.

This commit is contained in:
Nicolas Stepien 2012-09-05 03:23:17 +02:00
parent 59c9015c75
commit dccd4bf9ba
3 changed files with 567 additions and 90 deletions

View File

@ -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(/(^|>)(&gt;[^<$]+)(<|$)/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);

View File

@ -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

View File

@ -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 /(^|>)(&gt;[^<$]+)(<|$)/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