Get most of Archived-JSON'd-Posts-Into-4chan-HMTL-Format-Translator done for Quote Inlining/Previewing.

This commit is contained in:
Nicolas Stepien 2012-06-07 20:46:10 +02:00
parent 7c83d43e2d
commit 7845428c68
2 changed files with 449 additions and 51 deletions

View File

@ -2994,7 +2994,7 @@
Get = {
post: function(board, threadID, postID, root, cb) {
var post;
var post, url;
if (board === g.BOARD && (post = $.id("pc" + postID))) {
$.add(root, Get.cleanPost(post.cloneNode(true)));
return;
@ -3004,19 +3004,35 @@
return $.cache("/" + board + "/res/" + threadID, function() {
return Get.parsePost(this, board, threadID, postID, root, cb);
});
} else if (url = Redirect.post(board, postID)) {
return $.cache(url, function() {
return Get.parseArchivedPost(this, board, postID, root, cb);
});
}
},
parsePost: function(req, board, threadID, postID, root, cb) {
var doc, href, link, pc, quote, status, _i, _len, _ref;
var doc, href, link, pc, quote, status, url, _i, _len, _ref;
status = req.status;
if (status !== 200) {
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.parseArchivedPost(this, board, postID, root, cb);
});
} else {
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.parseArchivedPost(this, board, postID, root, cb);
});
} else {
root.textContent = "Post No." + postID + " has not been found.";
}
return;
}
pc = Get.cleanPost(d.importNode(pc, true));
@ -3037,7 +3053,158 @@
return cb();
}
},
parseArchivedPost: function(req, board, postID, root, cb) {},
parseArchivedPost: function(req, board, postID, root, cb) {
var bq, data, date, email, file, filesize, isOP, nameBlock, p, pc, pi, piM, span, time, unit;
data = JSON.parse(req.response);
if (data.error) {
root.textContent = data.error;
return;
}
isOP = postID === data.thread_num;
pc = $.el('div', {
id: "pc",
className: isOP ? 'postContainer opContainer' : 'postContainer replyContainer'
});
p = $.el('div', {
id: "p" + postID,
className: isOP ? 'post op' : 'post reply'
});
$.add(pc, p);
piM = $.el('div', {
id: "pim" + postID,
className: 'postInfoM mobile',
innerHTML: ''
});
pi = $.el('div', {
id: "pi" + postID,
className: 'postInfo desktop',
innerHTML: "<input type=checkbox name=" + postID + " value=delete> <span class=userInfo><span class=subject></span> <span class=nameBlock></span></span> <span class=dateTime data-utc=" + data.timestamp + "></span> <span class='postNum desktop'><a href='/" + board + "/res/" + data.thread_num + "#p" + postID + "' title='Highlight this post'>No.</a><a href='/" + board + "/res/" + data.thread_num + "#q" + postID + "' title='Quote this post'>" + postID + "</a>" + (isOP ? ' &nbsp; ' : '') + "</span> "
});
time = $('.dateTime', pi);
date = new Date(data.timestamp * 1000);
time.textContent = date.toString();
$('.subject', pi).textContent = data.title;
nameBlock = $('.nameBlock', pi);
if (data.email) {
email = $.el('a', {
className: 'useremail',
href: "mailto:" + data.email
});
$.add(nameBlock, email);
nameBlock = email;
}
$.add(nameBlock, $.el('span', {
className: 'name',
textContent: data.name
}));
if (data.trip) {
$.add(nameBlock, [
$.tn(' '), $.el('span', {
className: 'postertrip',
textContent: data.trip
})
]);
}
if (data.capcode === 'A') {
$.add(nameBlock, [
$.tn(' '), $.el('strong', {
className: 'capcode capcodeAdmin',
textContent: '## Admin'
})
]);
nameBlock = $('.nameBlock', pi);
$.addClass(nameBlock, 'capcodeAdmin');
$.add(nameBlock, [
$.tn(' '), $.el('img'), {
src: '//static.4chan.org/image/adminicon.gif',
alt: 'This user is the 4chan Administrator.',
title: 'This user is the 4chan Administrator.',
className: 'identityIcon'
}
]);
} else if (data.capcode === 'M') {
$.add(nameBlock, [
$.tn(' '), $.el('strong', {
className: 'capcode',
textContent: '## Mod'
})
]);
nameBlock = $('.nameBlock', pi);
$.addClass(nameBlock, 'capcodeMod');
$.add(nameBlock, [
$.tn(' '), $.el('img'), {
src: '//static.4chan.org/image/adminicon.gif',
alt: 'This user is a 4chan Moderator.',
title: 'This user is a 4chan Moderator.',
className: 'identityIcon'
}
]);
}
bq = $.el('blockquote', {
id: "m" + postID,
className: 'postMessage',
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>';
}
});
bq.innerHTML = bq.innerHTML.replace(/(^|>)(&gt;[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3');
$.add(p, [piM, pi, bq]);
if (data.media) {
file = $.el('div', {
id: "f" + postID,
className: 'file'
});
filesize = data.media_size;
unit = 0;
while (filesize >= 1024) {
filesize /= 1024;
unit++;
}
filesize = unit > 1 ? (a * 100).toFixed() / 100 : filesize.toFixed();
$.add(file, $.el('div', {
className: 'fileInfo',
innerHTML: "<span id=fT" + postID + " class=fileText>File: <a href='" + (data.media_link || data.remote_media_link) + "' target=_blank>" + data.media_orig + "</a>-(" + filesize + " " + ['B', 'KB', 'MB', 'GB'][unit] + ", " + data.media_w + "x" + data.media_h + ", <span title></span>)</span>"
}));
span = $('span[title]', file);
span.title = data.media_filename;
span.textContent = data.media_filename.length < 40 ? data.media_filename : "" + data.media_filename.slice(0, 30) + "(...)" + data.media_filename.slice(-4);
$.add(file, $.el('a', {
className: 'fileThumb',
href: data.media_link || data.remote_media_link,
target: '_blank',
innerHTML: "<img src=" + data.thumb_link + " alt='" + (data.spoiler === '1' ? 'Spoiler Image, ' : '') + filesize + " " + ['B', 'KB', 'MB', 'GB'][unit] + "' data-md5=" + data.media_hash + " style='height: " + data.preview_h + "px; width: " + data.preview_w + "px;'>"
}));
$.after((isOP ? $('.postInfoM', p) : $('.postInfo', p)), file);
}
$.replace(root.firstChild, pc);
if (cb) {
return cb();
}
},
cleanPost: function(root) {
var child, el, now, post, _i, _j, _len, _len1, _ref, _ref1;
post = $('.post', root);
@ -3143,7 +3310,7 @@
_ref = post.quotes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i];
if (!quote.hash) {
if (!(quote.hash || /\bdeadlink\b/.test(quote.className))) {
continue;
}
quote.removeAttribute('onclick');
@ -3161,7 +3328,7 @@
return;
}
e.preventDefault();
id = this.hash.slice(2);
id = this.dataset.id || this.hash.slice(2);
if (/\binlined\b/.test(this.className)) {
QuoteInline.rm(this, id);
} else {
@ -3173,21 +3340,30 @@
return this.classList.toggle('inlined');
},
add: function(q, id) {
var el, i, inline, isBacklink, path, root;
var board, el, i, inline, isBacklink, path, postID, root, threadID;
if (!(isBacklink = /\bbacklink\b/.test(q.className))) {
root = q;
while (root.parentNode.nodeName !== 'BLOCKQUOTE') {
root = root.parentNode;
}
}
path = q.pathname.split('/');
el = path[1] === g.BOARD ? $.id("p" + id) : false;
if (q.host === 'boards.4chan.org') {
path = q.pathname.split('/');
board = path[1];
threadID = path[3];
postID = id;
} else {
board = q.dataset.board;
threadID = 0;
postID = q.dataset.id;
}
el = board === g.BOARD ? $.id("p" + postID) : false;
inline = $.el('div', {
id: "i" + id,
id: "i" + postID,
className: el ? 'inline' : 'inline crosspost'
});
$.after((isBacklink ? q.parentNode : root), inline);
Get.post(path[1], path[3], id, inline);
Get.post(board, threadID, postID, inline);
if (!el) {
return;
}
@ -3233,7 +3409,7 @@
_ref = post.quotes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i];
if (quote.hash) {
if (quote.hash || /\bdeadlink\b/.test(quote.className)) {
$.on(quote, 'mouseover', QuotePreview.mouseover);
}
}
@ -3244,7 +3420,7 @@
}
},
mouseover: function(e) {
var el, id, parent, path, qp, quote, quoterID, _i, _len, _ref;
var board, el, parent, path, postID, qp, quote, quoterID, threadID, _i, _len, _ref;
if (/\binlined\b/.test(this.className)) {
return;
}
@ -3257,20 +3433,29 @@
if (UI.el) {
return;
}
path = this.pathname.split('/');
id = this.hash.slice(2);
if (this.host === 'boards.4chan.org') {
path = this.pathname.split('/');
board = path[1];
threadID = path[3];
postID = this.hash.slice(2);
} else {
board = this.dataset.board;
threadID = 0;
postID = this.dataset.id;
}
qp = UI.el = $.el('div', {
id: 'qp',
className: 'reply dialog'
});
UI.hover(e);
$.add(d.body, qp);
Get.post(path[1], path[3], id, qp, function() {
Get.post(board, threadID, postID, qp, function() {
var bq, fileInfo, img, post;
bq = $('blockquote', qp);
Main.prettify(bq);
post = {
el: qp
el: qp,
blockquote: bq
};
if (fileInfo = $('.fileInfo', qp)) {
img = fileInfo.nextElementSibling.firstElementChild;
@ -3286,10 +3471,13 @@
Time.node(post);
}
if (Conf['File Info Formatting']) {
return FileInfo.node(post);
FileInfo.node(post);
}
if (Conf['Resurrect Quotes']) {
return Quotify.node(post);
}
});
if (path[1] === g.BOARD && (el = $.id("p" + id))) {
if (board === g.BOARD && (el = $.id("p" + postID))) {
if (Conf['Quote Highlighting']) {
if (/\bop\b/.test(el.className)) {
$.addClass(el.parentNode, 'qphl');
@ -3403,6 +3591,11 @@
a.href = Redirect.thread(board, id, 'post');
a.className = 'deadlink';
a.target = '_blank';
if (Redirect.post(board, id)) {
$.addClass(a, 'quotelink');
a.dataset.board = board;
a.dataset.id = id;
}
}
data = data.slice(index + quote.length);
}
@ -3625,6 +3818,20 @@
return "http://archive.foolz.us/" + board + "/full_image/" + filename;
}
},
post: function(board, postID) {
switch (board) {
case 'a':
case 'co':
case 'jp':
case 'm':
case 'tg':
case 'tv':
case 'u':
case 'v':
case 'vg':
return "http://archive.foolz.us/api/chan/post/board/" + board + "/num/" + postID + "/format/json";
}
},
thread: function(board, id, mode) {
if (board == null) {
board = g.BOARD;

View File

@ -2292,30 +2292,34 @@ Get =
if threadID
$.cache "/#{board}/res/#{threadID}", ->
Get.parsePost @, board, threadID, postID, root, cb
# else if url = Redirect.???
# $.cache url, ->
# Get.parseArchivedPost @, board, postID, root, cb
else if url = Redirect.post board, postID
$.cache url, ->
Get.parseArchivedPost @, board, postID, root, cb
parsePost: (req, board, threadID, postID, root, cb) ->
{status} = req
if status isnt 200
# thread can die by the time we check a post
# try archive if possible
# else
root.textContent =
if status is 404
"Thread No.#{threadID} has not been found."
else
"Error #{req.status}: #{req.statusText}."
# The thread can die by the time we check a quote.
if url = Redirect.post board, postID
$.cache url, ->
Get.parseArchivedPost @, board, postID, root, cb
else
root.textContent =
if status is 404
"Thread No.#{threadID} has not been found."
else
"Error #{req.status}: #{req.statusText}."
return
doc = d.implementation.createHTMLDocument ''
doc.documentElement.innerHTML = req.response
unless pc = doc.getElementById "pc#{postID}"
# post can be deleted by the time we check for it
# try archive if possible
# else
root.textContent = "Post No.#{postID} has not been found."
# The post can be deleted by the time we check a quote.
if url = Redirect.post board, postID
$.cache url, ->
Get.parseArchivedPost @, board, postID, root, cb
else
root.textContent = "Post No.#{postID} has not been found."
return
pc = Get.cleanPost d.importNode pc, true
@ -2330,7 +2334,169 @@ Get =
$.replace root.firstChild, pc
cb() if cb
parseArchivedPost: (req, board, postID, root, cb) ->
# $.replace root.firstChild,
# unarchived
# >>>/a/1
# op
# >>>/a/66734868
# reply
# >>>/a/66718194
# reply with image and spoiler text
# >>>/v/142341606
data = JSON.parse req.response
if data.error
root.textContent = data.error
return
isOP = postID is data.thread_num
# post containers
pc = $.el 'div',
id: "pc#{}"
className: if isOP then 'postContainer opContainer' else 'postContainer replyContainer'
p = $.el 'div',
id: "p#{postID}"
className: if isOP then 'post op' else 'post reply'
$.add pc, p
# post info (mobile)
piM = $.el 'div',
id: "pim#{postID}"
className: 'postInfoM mobile'
innerHTML: '' # XXX
# post info
pi = $.el 'div',
id: "pi#{postID}"
className: 'postInfo desktop'
innerHTML: "<input type=checkbox name=#{postID} value=delete> <span class=userInfo><span class=subject></span> <span class=nameBlock></span></span> <span class=dateTime data-utc=#{data.timestamp}></span> <span class='postNum desktop'><a href='/#{board}/res/#{data.thread_num}#p#{postID}' title='Highlight this post'>No.</a><a href='/#{board}/res/#{data.thread_num}#q#{postID}' title='Quote this post'>#{postID}</a>#{if isOP then ' &nbsp; ' else ''}</span> "
# time
time = $ '.dateTime', pi
date = new Date data.timestamp * 1000
time.textContent = date.toString() # XXX needs to be 4chan formatted
# subject
$('.subject', pi).textContent = data.title
nameBlock = $ '.nameBlock', pi
if data.email
email = $.el 'a',
className: 'useremail'
href: "mailto:#{data.email}"
$.add nameBlock, email
nameBlock = email
$.add nameBlock, $.el 'span',
className: 'name'
textContent: data.name
if data.trip
$.add nameBlock, [$.tn(' '), $.el('span', className: 'postertrip', textContent: data.trip)]
if data.capcode is 'A' # admin
$.add nameBlock, [
$.tn(' '),
$.el('strong', className: 'capcode capcodeAdmin', textContent: '## Admin')
]
nameBlock = $ '.nameBlock', pi
$.addClass nameBlock, 'capcodeAdmin'
$.add nameBlock, [
$.tn(' '),
$.el('img'), src: '//static.4chan.org/image/adminicon.gif', alt: 'This user is the 4chan Administrator.', title: 'This user is the 4chan Administrator.', className: 'identityIcon'
]
else if data.capcode is 'M' # mod
$.add nameBlock, [
$.tn(' '),
$.el('strong', className: 'capcode', textContent: '## Mod')
]
nameBlock = $ '.nameBlock', pi
$.addClass nameBlock, 'capcodeMod'
$.add nameBlock, [
$.tn(' '),
$.el('img'), src: '//static.4chan.org/image/adminicon.gif', alt: 'This user is a 4chan Moderator.', title: 'This user is a 4chan Moderator.', className: 'identityIcon'
]
# comment
bq = $.el 'blockquote',
id: "m#{postID}"
className: 'postMessage'
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>'
bq.innerHTML = bq.innerHTML.replace /(^|>)(&gt;[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3'
$.add p, [piM, pi, bq]
# file
if data.media
file = $.el 'div',
id: "f#{postID}"
className: 'file'
filesize = data.media_size
unit = 0 # Bytes
while filesize >= 1024
filesize /= 1024
unit++
# Keep the filesize as a float if the unit is in MBs.
# Remove trailing 0s.
filesize =
if unit > 1
(a * 100).toFixed() / 100
else
filesize.toFixed()
$.add file, $.el 'div',
className: 'fileInfo'
innerHTML: "<span id=fT#{postID} class=fileText>File: <a href='#{data.media_link or data.remote_media_link}' target=_blank>#{data.media_orig}</a>-(#{filesize} #{['B', 'KB', 'MB', 'GB'][unit]}, #{data.media_w}x#{data.media_h}, <span title></span>)</span>"
span = $ 'span[title]', file
span.title = data.media_filename
span.textContent =
if data.media_filename.length < 40
data.media_filename
else
"#{data.media_filename[...30]}(...)#{data.media_filename[-4...]}"
$.add file, $.el 'a',
className: 'fileThumb'
href: data.media_link or data.remote_media_link
target: '_blank'
innerHTML: "<img src=#{data.thumb_link} alt='#{if data.spoiler is '1' then 'Spoiler Image, ' else ''}#{filesize} #{['B', 'KB', 'MB', 'GB'][unit]}' data-md5=#{data.media_hash} style='height: #{data.preview_h}px; width: #{data.preview_w}px;'>"
$.after (if isOP then $('.postInfoM', p) else $('.postInfo', p)), file
$.replace root.firstChild, pc
cb() if cb
cleanPost: (root) ->
post = $ '.post', root
for child in Array::slice.call root.childNodes
@ -2341,8 +2507,6 @@ Get =
for el in $$ '[id]', root
el.id = "#{now}_#{el.id}"
# $.rmClass post, 'opContainer'
# $.rmClass post, 'replyContainer'
$.rmClass root, 'forwarded'
$.rmClass root, 'qphl' # op
$.rmClass post, 'highlight'
@ -2404,7 +2568,7 @@ QuoteInline =
Main.callbacks.push @node
node: (post) ->
for quote in post.quotes
continue unless quote.hash
continue unless quote.hash or /\bdeadlink\b/.test quote.className
quote.removeAttribute 'onclick'
$.on quote, 'click', QuoteInline.toggle
for quote in post.backlinks
@ -2413,7 +2577,7 @@ QuoteInline =
toggle: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
e.preventDefault()
id = @hash[2..]
id = @dataset.id or @hash[2..]
if /\binlined\b/.test @className
QuoteInline.rm @, id
else
@ -2429,13 +2593,22 @@ QuoteInline =
until root.parentNode.nodeName is 'BLOCKQUOTE'
root = root.parentNode
path = q.pathname.split '/'
el = if path[1] is g.BOARD then $.id "p#{id}" else false
if q.host is 'boards.4chan.org'
path = q.pathname.split '/'
board = path[1]
threadID = path[3]
postID = id
else
board = q.dataset.board
threadID = 0
postID = q.dataset.id
el = if board is g.BOARD then $.id "p#{postID}" else false
inline = $.el 'div',
id: "i#{id}"
id: "i#{postID}"
className: if el then 'inline' else 'inline crosspost'
$.after (if isBacklink then q.parentNode else root), inline
Get.post path[1], path[3], id, inline
Get.post board, threadID, postID, inline
return unless el
@ -2466,7 +2639,7 @@ QuotePreview =
Main.callbacks.push @node
node: (post) ->
for quote in post.quotes
$.on quote, 'mouseover', QuotePreview.mouseover if quote.hash
$.on quote, 'mouseover', QuotePreview.mouseover if quote.hash or /\bdeadlink\b/.test quote.className
for quote in post.backlinks
$.on quote, 'mouseover', QuotePreview.mouseover
return
@ -2483,18 +2656,27 @@ QuotePreview =
# Don't stop other elements from dragging
return if UI.el
path = @pathname.split '/'
id = @hash[2..]
qp = UI.el = $.el 'div',
if @host is 'boards.4chan.org'
path = @pathname.split '/'
board = path[1]
threadID = path[3]
postID = @hash[2..]
else
board = @dataset.board
threadID = 0
postID = @dataset.id
qp = UI.el = $.el 'div',
id: 'qp'
className: 'reply dialog'
UI.hover e
$.add d.body, qp
Get.post path[1], path[3], id, qp, ->
Get.post board, threadID, postID, qp, ->
bq = $ 'blockquote', qp
Main.prettify bq
post =
el: qp
blockquote: bq
if fileInfo = $ '.fileInfo', qp
img = fileInfo.nextElementSibling.firstElementChild
if img.alt isnt 'File deleted.'
@ -2506,8 +2688,10 @@ QuotePreview =
Time.node post
if Conf['File Info Formatting']
FileInfo.node post
if Conf['Resurrect Quotes']
Quotify.node post
if path[1] is g.BOARD and el = $.id "p#{id}"
if board is g.BOARD and el = $.id "p#{postID}"
if Conf['Quote Highlighting']
if /\bop\b/.test el.className
$.addClass el.parentNode, 'qphl'
@ -2603,8 +2787,11 @@ Quotify =
else
a.href = Redirect.thread board, id, 'post'
a.className = 'deadlink'
# a.className = if JSONable then 'quotelink deadlink' else 'deadlink'
a.target = '_blank'
if Redirect.post board, id
$.addClass a, 'quotelink'
a.dataset.board = board
a.dataset.id = id
data = data[index + quote.length..]
@ -2793,6 +2980,10 @@ Redirect =
# "http://archive.xfiles.to/#{board}/full_image/#{filename}"
# when 'e'
# "https://md401.homelinux.net/4chan/cgi-board.pl/#{board}/full_image/#{filename}"
post: (board, postID) ->
switch board
when 'a', 'co', 'jp', 'm', 'tg', 'tv', 'u', 'v', 'vg'
"http://archive.foolz.us/api/chan/post/board/#{board}/num/#{postID}/format/json"
thread: (board=g.BOARD, id=g.THREAD_ID, mode='thread') ->
return unless Conf['404 Redirect'] or mode is 'post'
switch board