diff --git a/4chan_x.user.js b/4chan_x.user.js
index 09984f8c3..ae20c6d3c 100644
--- a/4chan_x.user.js
+++ b/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(" ");
+ html.push("" + subject + " ");
+ html.push("");
+ if (email) {
+ html.push("");
+ }
+ html.push("" + name + "");
+ if (tripcode) {
+ html.push(" " + tripcode + "");
+ }
+ if (uniqueID) {
+ html.push(" (ID: " + uniqueID + ")");
+ }
+ switch (capcode) {
+ case 'M':
+ html.push(' ## Mod');
+ html.push('
');
+ break;
+ case 'A':
+ html.push(' ## Admin');
+ html.push('
');
+ break;
+ case 'D':
+ html.push(' ## Mod');
+ html.push('
');
+ }
+ if (email) {
+ html.push('');
+ }
+ if (flag) {
+ html.push("
");
+ }
+ html.push(' ');
+ html.push("" + date + " ");
+ html.push('');
+ html.push("No.");
+ html.push("" + postID + "");
+ html.push('');
+ 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('
');
+ html.push("
File: ");
+ html.push("" + file.origin + "");
+ 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(", " + (Build.shortFilename(file.name)) + "");
+ }
+ html.push(") ");
+ html.push("");
+ html.push("
");
+ html.push('');
+ 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 '
';
+ case '[b]':
+ return '';
+ case '[/b]':
+ return '';
+ case '[spoiler]':
+ return '';
+ case '[/spoiler]':
+ return '';
+ case '[code]':
+ return '';
+ case '[/code]':
+ return '
';
+ case '[moot]':
+ return '';
+ case '[/moot]':
+ return '
';
+ case '[banned]':
+ return '';
+ case '[/banned]':
+ return '';
+ }
+ });
+ comment = bq.innerHTML.replace(/(^|>)(>[^<$]+)(<|$)/g, '$1$2$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);
diff --git a/Cakefile b/Cakefile
index 7619ac4cb..68a89ad75 100644
--- a/Cakefile
+++ b/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
diff --git a/script.coffee b/script.coffee
index caee1c281..89eddf2de 100644
--- a/script.coffee
+++ b/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 " "
+ # subject
+ html.push "#{subject} "
+ # name block
+ html.push ""
+ # mail start
+ html.push "" if email
+ # name
+ html.push "#{name}"
+ # tripcode
+ html.push " #{tripcode}" if tripcode
+ # user id
+ html.push " (ID: #{uniqueID})" if uniqueID
+ # capcode
+ switch capcode
+ when 'M'
+ html.push ' ## Mod'
+ html.push '
'
+ when 'A'
+ html.push ' ## Admin'
+ html.push '
'
+ when 'D'
+ html.push ' ## Mod'
+ html.push '
'
+ # mail end
+ html.push '' if email
+ # flag
+ # XXX what about troll flags in /pol/?
+ html.push "
" if flag
+ # end name block
+ html.push ' '
+ # date
+ html.push "#{date} "
+ # post num
+ html.push ''
+ html.push "No."
+ html.push "#{postID}"
+ # XXX closed/sticky?
+ html.push ''
+ 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 ''
+ html.push "
File: "
+ html.push "#{file.origin}"
+ 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 ", #{Build.shortFilename file.name}" unless file.isSpoiler
+ html.push ") "
+ html.push ""
+ html.push "
"
+ html.push ''
+ 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'
+ '
'
+ when '[b]'
+ ''
+ when '[/b]'
+ ''
+ when '[spoiler]'
+ ''
+ when '[/spoiler]'
+ ''
+ when '[code]'
+ ''
+ when '[/code]'
+ '
'
+ when '[moot]'
+ ''
+ when '[/moot]'
+ '
'
+ when '[banned]'
+ ''
+ when '[/banned]'
+ ''
+ # greentext
+ comment = bq.innerHTML.replace /(^|>)(>[^<$]+)(<|$)/g, '$1$2$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