Add Comment Expansion.

Fix a bug in Quote Backlinks.
This commit is contained in:
Nicolas Stepien 2013-02-16 22:38:38 +01:00
parent acc359ca30
commit b8db488591
3 changed files with 250 additions and 100 deletions

View File

@ -43,7 +43,7 @@
*/ */
(function() { (function() {
var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, Favicon, FileInfo, Filter, Get, Header, ImageExpand, ImageHover, Main, Menu, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, Time, UI, Unread, d, doc, g, var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, ExpandComment, Favicon, FileInfo, Filter, Get, Header, ImageExpand, ImageHover, Main, Menu, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, Time, UI, Unread, d, doc, g,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty, __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; }; __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; };
@ -2653,7 +2653,7 @@
}); });
} else { } else {
$.addClass(root, 'warning'); $.addClass(root, 'warning');
root.textContent = status === 404 ? "Thread No." + threadID + " 404'd." : "Error " + req.status + ": " + req.statusText + "."; root.textContent = status === 404 ? "Thread No." + threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ").";
} }
return; return;
} }
@ -3069,7 +3069,7 @@
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quote = _ref[_i]; quote = _ref[_i];
containers = [QuoteBacklink.getContainer(quote)]; containers = [QuoteBacklink.getContainer(quote)];
if (post = g.posts[quote]) { if ((post = g.posts[quote]) && post.nodes.backlinkContainer) {
_ref1 = post.clones; _ref1 = post.clones;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
clone = _ref1[_j]; clone = _ref1[_j];
@ -3091,11 +3091,11 @@
}, },
secondNode: function() { secondNode: function() {
var container; var container;
if (this.isClone && this.origin.nodes.backlinkContainer) { if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) {
this.nodes.backlinkContainer = $('.container', this.nodes.info); this.nodes.backlinkContainer = $('.container', this.nodes.info);
return; return;
} }
if (!(Conf['OP Backlinks'] || this.isReply)) { if (!(this.isReply || Conf['OP Backlinks'])) {
return; return;
} }
container = QuoteBacklink.getContainer(this.fullID); container = QuoteBacklink.getContainer(this.fullID);
@ -3891,6 +3891,86 @@
} }
}; };
ExpandComment = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Comment Expansion']) {
return;
}
return Post.prototype.callbacks.push({
name: 'Comment Expansion',
cb: this.node
});
},
node: function() {
var a;
if (a = $('.abbr > a', this.nodes.comment)) {
return $.on(a, 'click', ExpandComment.expand);
}
},
expand: function(e) {
var a, post;
e.preventDefault();
post = Get.postFromNode(this);
this.textContent = "Post No." + post + " Loading...";
a = this;
return $.cache("//api.4chan.org" + this.pathname + ".json", function() {
return ExpandComment.parse(this, a, post);
});
},
parse: function(req, a, post) {
var comment, href, postObj, posts, prev, quote, spoilerRange, _i, _j, _len, _len1, _ref;
if (req.status !== 200) {
a.textContent = "Error " + req.statusText + " (" + req.status + ")";
return;
}
posts = JSON.parse(req.response).posts;
if (spoilerRange = posts[0].custom_spoiler) {
Build.spoilerRange[g.BOARD] = spoilerRange;
}
for (_i = 0, _len = posts.length; _i < _len; _i++) {
postObj = posts[_i];
if (postObj.no === post.ID) {
break;
}
}
if (postObj.no !== post.ID) {
a.textContent = "Post No." + post + " not found.";
return;
}
comment = post.nodes.comment;
comment.innerHTML = postObj.com;
_ref = $$('.quotelink', comment);
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
quote = _ref[_j];
href = quote.getAttribute('href');
if (href[0] === '/') {
continue;
}
quote.href = "/" + post.board + "/res/" + href;
}
post.parseComment();
post.parseQuotes();
if (Conf['Resurrect Quotes']) {
Quotify.node.call(post);
}
if (Conf['Quote Preview']) {
QuotePreview.node.call(post);
}
if (Conf['Quote Inline']) {
QuoteInline.node.call(post);
}
if (Conf['Mark OP Quotes']) {
QuoteOP.node.call(post);
}
if (Conf['Mark Cross-thread Quotes']) {
QuoteCT.node.call(post);
}
prev = comment.previousSibling;
$.rm(comment);
return $.after(prev, comment);
}
};
ThreadExcerpt = { ThreadExcerpt = {
init: function() { init: function() {
if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) { if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) {
@ -5373,7 +5453,7 @@
}; };
function Post(root, thread, board, that) { function Post(root, thread, board, that) {
var alt, anchor, bq, capcode, data, date, email, file, fileInfo, flag, hash, i, info, name, node, nodes, pathname, post, quotelink, quotes, size, subject, text, thumb, tripcode, uniqueID, unit, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2; var alt, anchor, capcode, date, email, file, fileInfo, flag, info, name, post, size, subject, thumb, tripcode, uniqueID, unit;
this.thread = thread; this.thread = thread;
this.board = board; this.board = board;
if (that == null) { if (that == null) {
@ -5424,40 +5504,8 @@
this.nodes.date = date; this.nodes.date = date;
this.info.date = new Date(date.dataset.utc * 1000); this.info.date = new Date(date.dataset.utc * 1000);
} }
bq = this.nodes.comment.cloneNode(true); this.parseComment();
_ref = $$('.abbr, .capcodeReplies, .exif, b', bq); this.parseQuotes();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
$.rm(node);
}
text = [];
nodes = d.evaluate('.//br|.//text()', bq, null, 7, null);
for (i = _j = 0, _ref1 = nodes.snapshotLength; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
text.push((data = nodes.snapshotItem(i).data) ? data : '\n');
}
this.info.comment = text.join('').trim().replace(/\s+$/gm, '');
quotes = {};
_ref2 = $$('.quotelink', this.nodes.comment);
for (_k = 0, _len1 = _ref2.length; _k < _len1; _k++) {
quotelink = _ref2[_k];
hash = quotelink.hash;
if (!hash) {
continue;
}
pathname = quotelink.pathname;
if (/catalog$/.test(pathname)) {
continue;
}
if (quotelink.hostname !== 'boards.4chan.org') {
continue;
}
this.nodes.quotelinks.push(quotelink);
if (quotelink.parentNode.parentNode.className === 'capcodeReplies') {
continue;
}
quotes["" + (pathname.split('/')[1]) + "." + hash.slice(2)] = true;
}
this.quotes = Object.keys(quotes);
if ((file = $('.file', post)) && (thumb = $('img[data-md5]', file))) { if ((file = $('.file', post)) && (thumb = $('img[data-md5]', file))) {
alt = thumb.alt; alt = thumb.alt;
anchor = thumb.parentNode; anchor = thumb.parentNode;
@ -5491,6 +5539,51 @@
} }
} }
Post.prototype.parseComment = function() {
var bq, data, i, node, nodes, text, _i, _j, _len, _ref, _ref1;
bq = this.nodes.comment.cloneNode(true);
_ref = $$('.abbr, .capcodeReplies, .exif, b', bq);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
$.rm(node);
}
text = [];
nodes = d.evaluate('.//br|.//text()', bq, null, 7, null);
for (i = _j = 0, _ref1 = nodes.snapshotLength; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
text.push((data = nodes.snapshotItem(i).data) ? data : '\n');
}
return this.info.comment = text.join('').trim().replace(/\s+$/gm, '');
};
Post.prototype.parseQuotes = function() {
var hash, pathname, quotelink, quotes, _i, _len, _ref;
quotes = {};
_ref = $$('.quotelink', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quotelink = _ref[_i];
hash = quotelink.hash;
if (!hash) {
continue;
}
pathname = quotelink.pathname;
if (/catalog$/.test(pathname)) {
continue;
}
if (quotelink.hostname !== 'boards.4chan.org') {
continue;
}
this.nodes.quotelinks.push(quotelink);
if (quotelink.parentNode.parentNode.className === 'capcodeReplies') {
continue;
}
quotes["" + (pathname.split('/')[1]) + "." + hash.slice(2)] = true;
}
if (this.isClone) {
return;
}
return this.quotes = Object.keys(quotes);
};
Post.prototype.kill = function(file, now) { Post.prototype.kill = function(file, now) {
var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1; var clone, quotelink, strong, _i, _j, _len, _len1, _ref, _ref1;
now || (now = new Date()); now || (now = new Date());
@ -5556,7 +5649,7 @@
__extends(Clone, _super); __extends(Clone, _super);
function Clone(origin, context) { function Clone(origin, context) {
var file, index, info, inline, inlined, key, nodes, post, quotelink, root, val, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4; var file, index, info, inline, inlined, key, nodes, post, root, val, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3;
this.origin = origin; this.origin = origin;
this.context = context; this.context = context;
_ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; _ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply'];
@ -5612,18 +5705,12 @@
if (nodes.date) { if (nodes.date) {
this.nodes.date = $('.dateTime', info); this.nodes.date = $('.dateTime', info);
} }
_ref3 = $$('.quotelink', this.nodes.comment); this.parseQuotes();
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
quotelink = _ref3[_l];
if (quotelink.hash || $.hasClass(quotelink, 'deadlink')) {
this.nodes.quotelinks.push(quotelink);
}
}
if (origin.file) { if (origin.file) {
this.file = {}; this.file = {};
_ref4 = origin.file; _ref3 = origin.file;
for (key in _ref4) { for (key in _ref3) {
val = _ref4[key]; val = _ref3[key];
this.file[key] = val; this.file[key] = val;
} }
file = $('.file', post); file = $('.file', post);
@ -5739,6 +5826,7 @@
initFeature('Reveal Spoilers', RevealSpoilers); initFeature('Reveal Spoilers', RevealSpoilers);
initFeature('Auto-GIF', AutoGIF); initFeature('Auto-GIF', AutoGIF);
initFeature('Image Hover', ImageHover); initFeature('Image Hover', ImageHover);
initFeature('Comment Expansion', ExpandComment);
initFeature('Thread Excerpt', ThreadExcerpt); initFeature('Thread Excerpt', ThreadExcerpt);
initFeature('Favicon', Favicon); initFeature('Favicon', Favicon);
initFeature('Unread', Unread); initFeature('Unread', Unread);

View File

@ -1469,7 +1469,7 @@ Get =
if status is 404 if status is 404
"Thread No.#{threadID} 404'd." "Thread No.#{threadID} 404'd."
else else
"Error #{req.status}: #{req.statusText}." "Error #{req.statusText} (#{req.status})."
return return
posts = JSON.parse(req.response).posts posts = JSON.parse(req.response).posts
@ -1839,7 +1839,9 @@ QuoteBacklink =
textContent: QuoteBacklink.funk @ID textContent: QuoteBacklink.funk @ID
for quote in @quotes for quote in @quotes
containers = [QuoteBacklink.getContainer quote] containers = [QuoteBacklink.getContainer quote]
if post = g.posts[quote] if (post = g.posts[quote]) and post.nodes.backlinkContainer
# Don't add OP clones when OP Backlinks is disabled,
# as the clones won't have the backlink containers.
for clone in post.clones for clone in post.clones
containers.push clone.nodes.backlinkContainer containers.push clone.nodes.backlinkContainer
for container in containers for container in containers
@ -1851,11 +1853,11 @@ QuoteBacklink =
$.add container, [$.tn(' '), link] $.add container, [$.tn(' '), link]
return return
secondNode: -> secondNode: ->
if @isClone and @origin.nodes.backlinkContainer if @isClone and (@origin.isReply or Conf['OP Backlinks'])
@nodes.backlinkContainer = $ '.container', @nodes.info @nodes.backlinkContainer = $ '.container', @nodes.info
return return
# Don't backlink the OP. # Don't backlink the OP.
return unless Conf['OP Backlinks'] or @isReply return unless @isReply or Conf['OP Backlinks']
container = QuoteBacklink.getContainer @fullID container = QuoteBacklink.getContainer @fullID
@nodes.backlinkContainer = container @nodes.backlinkContainer = container
$.add @nodes.info, container $.add @nodes.info, container
@ -2420,6 +2422,62 @@ ImageHover =
$.ajax URL, onreadystatechange: (-> clearTimeout timeoutID if @status is 404), $.ajax URL, onreadystatechange: (-> clearTimeout timeoutID if @status is 404),
type: 'head' type: 'head'
ExpandComment =
init: ->
return if g.VIEW isnt 'index' or !Conf['Comment Expansion']
Post::callbacks.push
name: 'Comment Expansion'
cb: @node
node: ->
if a = $ '.abbr > a', @nodes.comment
$.on a, 'click', ExpandComment.expand
expand: (e) ->
e.preventDefault()
post = Get.postFromNode @
@textContent = "Post No.#{post} Loading..."
a = @
$.cache "//api.4chan.org#{@pathname}.json", -> ExpandComment.parse @, a, post
parse: (req, a, post) ->
if req.status isnt 200
a.textContent = "Error #{req.statusText} (#{req.status})"
return
posts = JSON.parse(req.response).posts
if spoilerRange = posts[0].custom_spoiler
Build.spoilerRange[g.BOARD] = spoilerRange
for postObj in posts
break if postObj.no is post.ID
if postObj.no isnt post.ID
a.textContent = "Post No.#{post} not found."
return
{comment} = post.nodes
comment.innerHTML = postObj.com
for quote in $$ '.quotelink', comment
href = quote.getAttribute 'href'
continue if href[0] is '/' # Cross-board quote, or board link
quote.href = "/#{post.board}/res/#{href}" # Fix pathnames
post.parseComment()
post.parseQuotes()
if Conf['Resurrect Quotes']
Quotify.node.call post
if Conf['Quote Preview']
QuotePreview.node.call post
if Conf['Quote Inline']
QuoteInline.node.call post
if Conf['Mark OP Quotes']
QuoteOP.node.call post
if Conf['Mark Cross-thread Quotes']
QuoteCT.node.call post
# XXX g code
# XXX sci math
# Fix linkifiers:
prev = comment.previousSibling
$.rm comment
$.after prev, comment
ThreadExcerpt = ThreadExcerpt =
init: -> init: ->
return if g.VIEW isnt 'thread' or !Conf['Thread Excerpt'] return if g.VIEW isnt 'thread' or !Conf['Thread Excerpt']

View File

@ -66,47 +66,8 @@ class Post
@nodes.date = date @nodes.date = date
@info.date = new Date date.dataset.utc * 1000 @info.date = new Date date.dataset.utc * 1000
# Get the comment's text. @parseComment()
# <br> -> \n @parseQuotes()
# Remove:
# 'Comment too long'...
# Admin/Mod/Dev replies. (/q/)
# EXIF data. (/p/)
# Rolls. (/tg/)
# Preceding and following new lines.
# Trailing spaces.
bq = @nodes.comment.cloneNode true
for node in $$ '.abbr, .capcodeReplies, .exif, b', bq
$.rm node
text = []
# XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
nodes = d.evaluate './/br|.//text()', bq, null, 7, null
for i in [0...nodes.snapshotLength]
text.push if data = nodes.snapshotItem(i).data then data else '\n'
@info.comment = text.join('').trim().replace /\s+$/gm, ''
quotes = {}
for quotelink in $$ '.quotelink', @nodes.comment
# Don't add board links. (>>>/b/)
hash = quotelink.hash
continue unless hash
# Don't add catalog links. (>>>/b/catalog or >>>/b/search)
pathname = quotelink.pathname
continue if /catalog$/.test pathname
# Don't add rules links. (>>>/a/rules)
# Don't add text-board quotelinks. (>>>/img/1234)
continue if quotelink.hostname isnt 'boards.4chan.org'
@nodes.quotelinks.push quotelink
# Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...)
continue if quotelink.parentNode.parentNode.className is 'capcodeReplies'
# Basically, only add quotes that link to posts on an imageboard.
quotes["#{pathname.split('/')[1]}.#{hash[2..]}"] = true
@quotes = Object.keys quotes
if (file = $ '.file', post) and thumb = $ 'img[data-md5]', file if (file = $ '.file', post) and thumb = $ 'img[data-md5]', file
# Supports JPG/PNG/GIF/PDF. # Supports JPG/PNG/GIF/PDF.
@ -145,6 +106,51 @@ class Post
g.posts["#{board}.#{@}"] = thread.posts[@] = board.posts[@] = @ g.posts["#{board}.#{@}"] = thread.posts[@] = board.posts[@] = @
@kill() if that.isArchived @kill() if that.isArchived
parseComment: ->
# Get the comment's text.
# <br> -> \n
# Remove:
# 'Comment too long'...
# Admin/Mod/Dev replies. (/q/)
# EXIF data. (/p/)
# Rolls. (/tg/)
# Preceding and following new lines.
# Trailing spaces.
bq = @nodes.comment.cloneNode true
for node in $$ '.abbr, .capcodeReplies, .exif, b', bq
$.rm node
text = []
# XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
nodes = d.evaluate './/br|.//text()', bq, null, 7, null
for i in [0...nodes.snapshotLength]
text.push if data = nodes.snapshotItem(i).data then data else '\n'
@info.comment = text.join('').trim().replace /\s+$/gm, ''
parseQuotes: ->
quotes = {}
for quotelink in $$ '.quotelink', @nodes.comment
# Don't add board links. (>>>/b/)
hash = quotelink.hash
continue unless hash
# Don't add catalog links. (>>>/b/catalog or >>>/b/search)
pathname = quotelink.pathname
continue if /catalog$/.test pathname
# Don't add rules links. (>>>/a/rules)
# Don't add text-board quotelinks. (>>>/img/1234)
continue if quotelink.hostname isnt 'boards.4chan.org'
@nodes.quotelinks.push quotelink
# Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...)
continue if quotelink.parentNode.parentNode.className is 'capcodeReplies'
# Basically, only add quotes that link to posts on an imageboard.
quotes["#{pathname.split('/')[1]}.#{hash[2..]}"] = true
return if @isClone
@quotes = Object.keys quotes
kill: (file, now) -> kill: (file, now) ->
now or= new Date() now or= new Date()
if file if file
@ -228,10 +234,7 @@ class Clone extends Post
if nodes.date if nodes.date
@nodes.date = $ '.dateTime', info @nodes.date = $ '.dateTime', info
for quotelink in $$ '.quotelink', @nodes.comment @parseQuotes()
# See comments in Post's constructor.
if quotelink.hash or $.hasClass quotelink, 'deadlink'
@nodes.quotelinks.push quotelink
if origin.file if origin.file
# Copy values, point to relevant elements. # Copy values, point to relevant elements.
@ -333,6 +336,7 @@ Main =
initFeature 'Reveal Spoilers', RevealSpoilers initFeature 'Reveal Spoilers', RevealSpoilers
initFeature 'Auto-GIF', AutoGIF initFeature 'Auto-GIF', AutoGIF
initFeature 'Image Hover', ImageHover initFeature 'Image Hover', ImageHover
initFeature 'Comment Expansion', ExpandComment
initFeature 'Thread Excerpt', ThreadExcerpt initFeature 'Thread Excerpt', ThreadExcerpt
initFeature 'Favicon', Favicon initFeature 'Favicon', Favicon
initFeature 'Unread', Unread initFeature 'Unread', Unread