forked from flanchan/doushio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
6.5 KiB
266 lines
6.5 KiB
window.hot = new Backbone.Model({
|
|
readOnly: false,
|
|
});
|
|
|
|
var Post = Backbone.Model.extend({
|
|
idAttribute: 'num',
|
|
});
|
|
|
|
var Replies = Backbone.Collection.extend({model: Post});
|
|
|
|
var Thread = Backbone.Model.extend({
|
|
idAttribute: 'num',
|
|
initialize: function () {
|
|
if (!this.get('replies'))
|
|
this.set('replies', new Replies([]));
|
|
},
|
|
});
|
|
|
|
var ThreadCollection = Backbone.Collection.extend({
|
|
model: Thread,
|
|
|
|
lookup: function (num, op) {
|
|
var thread = this.get(op) || UnknownThread;
|
|
return (num == op) ? thread : thread.get('replies').get(num);
|
|
},
|
|
});
|
|
|
|
var Threads = new ThreadCollection();
|
|
var UnknownThread = new Thread();
|
|
|
|
function model_link(key) {
|
|
return function (event) {
|
|
this.model.set(key, $(event.target).val());
|
|
};
|
|
}
|
|
|
|
var Section = Backbone.View.extend({
|
|
tagName: 'section',
|
|
|
|
initialize: function () {
|
|
this.listenTo(this.model, {
|
|
'change:hide': this.renderHide,
|
|
'change:locked': this.renderLocked,
|
|
'change:spoiler': this.renderSpoiler,
|
|
destroy: this.remove,
|
|
});
|
|
this.listenTo(this.model.get('replies'), {
|
|
remove: this.removePost,
|
|
});
|
|
},
|
|
|
|
renderHide: function (model, hide) {
|
|
this.$el.next('hr').andSelf().toggle(!hide);
|
|
},
|
|
|
|
renderLocked: function (model, locked) {
|
|
this.$el.toggleClass('locked', !!locked);
|
|
},
|
|
|
|
renderSpoiler: function (model, spoiler) {
|
|
var $img = this.$el.children('figure').find('img');
|
|
var sp = oneeSama.spoiler_info(spoiler, true);
|
|
$img.replaceWith($('<img>', {
|
|
src: sp.thumb, width: sp.dims[0], height: sp.dims[1],
|
|
}));
|
|
},
|
|
|
|
remove: function () {
|
|
var replies = this.model.get('replies');
|
|
replies.each(function (post) {
|
|
clear_post_links(post, replies);
|
|
});
|
|
replies.reset();
|
|
|
|
this.$el.next('hr').andSelf().remove();
|
|
this.stopListening();
|
|
},
|
|
|
|
removePost: function (model) {
|
|
model.trigger('removeSelf');
|
|
},
|
|
});
|
|
|
|
/* XXX: Move into own views module once more substantial */
|
|
var Article = Backbone.View.extend({
|
|
tagName: 'article',
|
|
initialize: function () {
|
|
this.listenTo(this.model, {
|
|
'change:backlinks': this.renderBacklinks,
|
|
'change:editing': this.renderEditing,
|
|
'change:hide': this.renderHide,
|
|
'change:image': this.renderImage,
|
|
'change:spoiler': this.renderSpoiler,
|
|
'removeSelf': this.remove,
|
|
});
|
|
},
|
|
|
|
render: function () {
|
|
var html = oneeSama.mono(this.model.attributes);
|
|
this.setElement($($.parseHTML(html)).filter('article')[0]);
|
|
return this;
|
|
},
|
|
|
|
renderBacklinks: function () {
|
|
if (options.get('nobacklinks'))
|
|
return this; /* ought to disconnect handler? */
|
|
var backlinks = this.model.get('backlinks');
|
|
var $list = this.$el.find('small');
|
|
if (!backlinks || !backlinks.length) {
|
|
$list.remove();
|
|
return this;
|
|
}
|
|
if (!$list.length)
|
|
$list = $('<small/>', {text: 'Replies:'}).appendTo(
|
|
this.$el);
|
|
// TODO: Sync up DOM gracefully instead of clobbering
|
|
$list.find('a').remove();
|
|
backlinks.forEach(function (num) {
|
|
var $a = $('<a/>', {href: '#'+num, text: '>>'+num});
|
|
$list.append(' ', $a);
|
|
});
|
|
return this;
|
|
},
|
|
|
|
renderEditing: function (model, editing) {
|
|
this.$el.toggleClass('editing', !!editing);
|
|
if (!editing)
|
|
this.$('blockquote')[0].normalize();
|
|
},
|
|
|
|
renderHide: function (model, hide) {
|
|
this.$el.toggle(!hide);
|
|
},
|
|
|
|
renderImage: function (model, image) {
|
|
var hd = this.$('header'), fig = this.$('figure');
|
|
if (!image)
|
|
fig.remove();
|
|
else if (hd.length && !fig.length) {
|
|
/* Is this focus business necessary here? */
|
|
var focus = get_focus();
|
|
|
|
insert_image(image, hd, false);
|
|
|
|
if (focus)
|
|
focus.focus();
|
|
}
|
|
},
|
|
|
|
renderSpoiler: function (model, spoiler) {
|
|
var $img = this.$('figure').find('img');
|
|
var sp = oneeSama.spoiler_info(spoiler, false);
|
|
$img.replaceWith($('<img>', {
|
|
src: sp.thumb,
|
|
width: sp.dims[0], height: sp.dims[1],
|
|
}));
|
|
},
|
|
});
|
|
|
|
/* BATCH DOM UPDATE DEFER */
|
|
|
|
var deferredChanges = {links: {}, backlinks: {}};
|
|
var haveDeferredChanges = false;
|
|
|
|
/* this runs just before every _outermost_ wrap_dom completion */
|
|
Backbone.on('flushDomUpdates', function () {
|
|
if (!haveDeferredChanges)
|
|
return;
|
|
haveDeferredChanges = false;
|
|
|
|
for (var attr in deferredChanges) {
|
|
var deferred = deferredChanges[attr];
|
|
var empty = true;
|
|
for (var id in deferred) {
|
|
deferred[id].trigger('change:'+attr);
|
|
empty = false;
|
|
}
|
|
if (!empty)
|
|
deferredChanges[attr] = {};
|
|
}
|
|
});
|
|
|
|
/* LINKS */
|
|
|
|
function add_post_links(src, links, op) {
|
|
if (!src || !links)
|
|
return;
|
|
var thread = Threads.get(op);
|
|
var srcLinks = src.get('links') || [];
|
|
var repliedToMe = false;
|
|
for (var destId in links) {
|
|
var dest = thread && thread.get('replies').get(destId);
|
|
if (!dest) {
|
|
/* Dest doesn't exist yet; track it anyway */
|
|
dest = new Post({id: destId, shallow: true});
|
|
UnknownThread.get('replies').add(dest);
|
|
}
|
|
if (dest.get('mine'))
|
|
repliedToMe = true;
|
|
var destLinks = dest.get('backlinks') || [];
|
|
/* Update links and backlinks arrays in-order */
|
|
var i = _.sortedIndex(srcLinks, dest.id);
|
|
if (srcLinks[i] == dest.id)
|
|
continue;
|
|
srcLinks.splice(i, 0, dest.id);
|
|
destLinks.splice(_.sortedIndex(destLinks, src.id), 0, src.id);
|
|
force_post_change(src, 'links', srcLinks);
|
|
force_post_change(dest, 'backlinks', destLinks);
|
|
}
|
|
|
|
if (repliedToMe && !src.get('mine')) {
|
|
/* Should really be triggered only on the thread */
|
|
Backbone.trigger('repliedToMe');
|
|
}
|
|
}
|
|
|
|
function force_post_change(post, attr, val) {
|
|
if (val === undefined && post.has(attr))
|
|
post.unset(attr);
|
|
else if (post.get(attr) !== val)
|
|
post.set(attr, val);
|
|
else if (!(post.id in deferredChanges[attr])) {
|
|
/* We mutated the existing array, so `change` won't fire.
|
|
Dumb hack ensues. Should extend Backbone or something. */
|
|
/* Also, here we coalesce multiple changes just in case. */
|
|
/* XXX: holding a direct reference to post is gross */
|
|
deferredChanges[attr][post.id] = post;
|
|
haveDeferredChanges = true;
|
|
}
|
|
}
|
|
|
|
function clear_post_links(post, replies) {
|
|
if (!post)
|
|
return;
|
|
(post.get('links') || []).forEach(function (destId) {
|
|
var dest = replies.get(destId);
|
|
if (!dest)
|
|
return;
|
|
var backlinks = dest.get('backlinks') || [];
|
|
var i = backlinks.indexOf(post.id);
|
|
if (i < 0)
|
|
return;
|
|
backlinks.splice(i, 1);
|
|
if (!backlinks.length)
|
|
backlinks = undefined;
|
|
force_post_change(dest, 'backlinks', backlinks);
|
|
});
|
|
(post.get('backlinks') || []).forEach(function (srcId) {
|
|
var src = replies.get(srcId);
|
|
if (!src)
|
|
return;
|
|
var links = src.get('links') || [];
|
|
var i = links.indexOf(post.id);
|
|
if (i < 0)
|
|
return;
|
|
links.splice(i, 1);
|
|
if (!links.length)
|
|
links = undefined;
|
|
force_post_change(src, 'links', links);
|
|
});
|
|
post.unset('links', {silent: true});
|
|
post.unset('backlinks');
|
|
}
|
|
|
|
|