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.
554 lines
13 KiB
554 lines
13 KiB
function inject(frag) {
|
|
var dest = this.buffer;
|
|
for (var i = 0; i < this.state[1]; i++)
|
|
dest = dest.children('del:last');
|
|
if (this.state[0] & S_BIG)
|
|
dest = dest.children('h4:last');
|
|
if (this.state[0] & S_QUOTE)
|
|
dest = dest.children('em:last');
|
|
if (this.strong)
|
|
dest = dest.children('strong:last');
|
|
if (this.sup_level) {
|
|
for (var i = 0; i < this.sup_level; i++)
|
|
dest = dest.children('sup:last');
|
|
}
|
|
var out = null;
|
|
if (frag.safe) {
|
|
var m = frag.safe.match(/^<(\w+)>$/);
|
|
if (m)
|
|
out = document.createElement(m[1]);
|
|
else if (/^<\/\w+>$/.test(frag.safe))
|
|
out = '';
|
|
}
|
|
if (out === null) {
|
|
if (Array.isArray(frag))
|
|
out = $(flatten(frag).join(''));
|
|
else
|
|
out = escape_fragment(frag);
|
|
}
|
|
if (out)
|
|
dest.append(out);
|
|
return out;
|
|
}
|
|
|
|
// TODO: Unify self-updates with OneeSama; this is redundant
|
|
oneeSama.hook('insertOwnPost', function (info) {
|
|
if (!postForm || !info.links)
|
|
return;
|
|
postForm.buffer.find('.nope').each(function () {
|
|
var $a = $(this);
|
|
var text = $a.text();
|
|
var m = text.match(/^>>(\d+)/);
|
|
if (!m)
|
|
return;
|
|
var num = m[1], op = info.links[num];
|
|
if (!op)
|
|
return;
|
|
var realRef = postForm.imouto.post_ref(num, op, false);
|
|
var $ref = $(flatten([realRef]).join(''));
|
|
$a.attr('href', $ref.attr('href')).removeAttr('class');
|
|
var refText = $ref.text();
|
|
if (refText != text)
|
|
$a.text(refText);
|
|
});
|
|
});
|
|
|
|
/* Mobile */
|
|
function touchable_spoiler_tag(del) {
|
|
del.html = '<del onclick="void(0)">';
|
|
}
|
|
oneeSama.hook('spoilerTag', touchable_spoiler_tag);
|
|
|
|
function get_focus() {
|
|
var $focus = $(window.getSelection().focusNode);
|
|
if ($focus.is('blockquote'))
|
|
return $focus.find('textarea');
|
|
}
|
|
|
|
function section_abbrev(section) {
|
|
var stat = section.find('.omit');
|
|
var m = stat.text().match(/(\d+)\D+(\d+)?/);
|
|
if (!m)
|
|
return false;
|
|
return {stat: stat, omit: parseInt(m[1], 10),
|
|
img: parseInt(m[2] || 0, 10)};
|
|
}
|
|
|
|
function shift_replies(section) {
|
|
if (THREAD)
|
|
return;
|
|
var shown = section.children('article[id]:not(:has(form))');
|
|
var rem = shown.length;
|
|
if (rem < ABBREVIATED_REPLIES)
|
|
return;
|
|
var $stat, omit = 0, img = 0;
|
|
var info = section_abbrev(section);
|
|
if (info) {
|
|
$stat = info.stat;
|
|
omit = info.omit;
|
|
img = info.img;
|
|
}
|
|
else {
|
|
$stat = $('<span class="omit"></span>');
|
|
section.children('blockquote,form').last().after($stat);
|
|
}
|
|
var omitsBefore = omit;
|
|
for (var i = 0; i < shown.length; i++) {
|
|
var cull = $(shown[i]);
|
|
if (rem-- < ABBREVIATED_REPLIES)
|
|
break;
|
|
if (cull.has('figure').length)
|
|
img++;
|
|
omit++;
|
|
cull.remove();
|
|
}
|
|
$stat.text(abbrev_msg(omit, img));
|
|
if (omitsBefore <= THREAD_LAST_N && omit > THREAD_LAST_N) {
|
|
var $expand = section.find('header .act');
|
|
if ($expand.length == 1) {
|
|
var num = extract_num(section);
|
|
var $lastN = $(oneeSama.last_n_html(num));
|
|
$expand.after(' ', $lastN);
|
|
}
|
|
}
|
|
}
|
|
|
|
function spill_page() {
|
|
if (THREAD)
|
|
return;
|
|
/* Ugh, this could be smarter. */
|
|
var ss = $('body > section[id]:visible');
|
|
for (var i = THREADS_PER_PAGE; i < ss.length; i++)
|
|
$(ss[i]).prev('hr').andSelf().hide();
|
|
|
|
}
|
|
|
|
var dispatcher = {};
|
|
|
|
dispatcher[PING] = function (msg) {};
|
|
|
|
dispatcher[INSERT_POST] = function (msg) {
|
|
var orig_focus = get_focus();
|
|
var num = msg[0];
|
|
msg = msg[1];
|
|
var isThread = !msg.op;
|
|
if (isThread)
|
|
syncs[num] = 1;
|
|
msg.editing = true;
|
|
msg.num = num;
|
|
|
|
// did I create this post?
|
|
var el;
|
|
var nonce = msg.nonce;
|
|
delete msg.nonce;
|
|
var myNonce = get_nonces()[nonce];
|
|
var bump = BUMP;
|
|
var myTab = myNonce && myNonce.tab == TAB_ID;
|
|
if (myTab) {
|
|
// posted in this tab; transform placeholder
|
|
ownPosts[num] = true;
|
|
oneeSama.trigger('insertOwnPost', msg);
|
|
postSM.feed('alloc', msg);
|
|
bump = false;
|
|
// delete only after a delay so all tabs notice that it's ours
|
|
setTimeout(destroy_nonce.bind(null, nonce), 10*1000);
|
|
// if we've already made a placeholder for this post, use it
|
|
if (postForm && postForm.el)
|
|
el = postForm.el;
|
|
}
|
|
|
|
/* This conflict is really dumb. */
|
|
var links = oneeSama.links = msg.links;
|
|
delete msg.links;
|
|
|
|
// create model or fill existing shallow model
|
|
var model;
|
|
if (!isThread) {
|
|
model = UnknownThread.get('replies').get(num);
|
|
if (model) {
|
|
UnknownThread.get('replies').remove(num);
|
|
model.unset('shallow');
|
|
model.set(msg);
|
|
}
|
|
else
|
|
model = new Post(msg);
|
|
}
|
|
else {
|
|
model = new Thread(msg);
|
|
}
|
|
|
|
if (myNonce) {
|
|
model.set('mine', true);
|
|
Mine.write(num, Mine.now());
|
|
}
|
|
|
|
// insert it into the DOM
|
|
var $section, $hr;
|
|
if (!isThread) {
|
|
var article = new Article({model: model, id: num, el: el});
|
|
if (!el)
|
|
el = article.render().el;
|
|
|
|
var thread = Threads.lookup(msg.op, msg.op);
|
|
thread.get('replies').add(model);
|
|
add_post_links(model, links, msg.op);
|
|
|
|
$section = $('#' + msg.op);
|
|
shift_replies($section);
|
|
$section.children('blockquote,.omit,form,article[id]:last'
|
|
).last().after(el);
|
|
if (is_sage(msg.email)) {
|
|
bump = false;
|
|
}
|
|
if (postForm) {
|
|
// don't bump due to replies while posting (!)
|
|
bump = false;
|
|
}
|
|
if (bump) {
|
|
$hr = $section.next();
|
|
$section.detach();
|
|
$hr.detach();
|
|
}
|
|
}
|
|
else {
|
|
Threads.add(model);
|
|
}
|
|
|
|
// only add new threads on /live
|
|
if (isThread && BUMP) {
|
|
if (!el) {
|
|
$section = $($.parseHTML(oneeSama.monomono(msg
|
|
).join('')));
|
|
$section = $section.filter('section');
|
|
el = $section[0];
|
|
}
|
|
else {
|
|
$section = $(el);
|
|
}
|
|
var section = new Section({model: model, id: num, el: el});
|
|
$hr = $('<hr/>');
|
|
if (!postForm)
|
|
$section.append(make_reply_box());
|
|
}
|
|
|
|
Backbone.trigger('afterInsert', model, $(el));
|
|
if (bump) {
|
|
var fencepost = $('body > aside');
|
|
$section.insertAfter(fencepost.length ? fencepost : $ceiling);
|
|
if ($hr)
|
|
$section.after($hr);
|
|
spill_page();
|
|
}
|
|
if (orig_focus)
|
|
orig_focus.focus();
|
|
};
|
|
|
|
dispatcher[MOVE_THREAD] = function (msg, op) {
|
|
msg = msg[0];
|
|
msg.num = op;
|
|
var orig_focus = get_focus();
|
|
|
|
var model = new Thread(msg);
|
|
Threads.add(model);
|
|
|
|
oneeSama.links = msg.links;
|
|
var $el = $($.parseHTML(oneeSama.monomono(msg).join('')));
|
|
var el = $el.filter('section')[0];
|
|
|
|
var section = new Section({model: model, id: op, el: el});
|
|
var $hr = $('<hr/>');
|
|
// No make_reply_box since this is archive-only for now
|
|
if (!BUMP) {
|
|
$el.hide();
|
|
$hr.hide();
|
|
}
|
|
if (msg.replyctr > 0) {
|
|
var omitMsg = abbrev_msg(msg.replyctr, msg.imgctr - 1);
|
|
$('<span class="omit"/>').text(omitMsg).appendTo($el);
|
|
}
|
|
|
|
Backbone.trigger('afterInsert', model, $el);
|
|
if (BUMP) {
|
|
var fencepost = $('body > aside');
|
|
$el.insertAfter(fencepost.length ? fencepost : $ceiling
|
|
).after($hr);
|
|
spill_page();
|
|
}
|
|
if (orig_focus)
|
|
orig_focus.focus();
|
|
};
|
|
|
|
dispatcher[INSERT_IMAGE] = function (msg, op) {
|
|
var focus = get_focus();
|
|
var num = msg[0];
|
|
var post = Threads.lookup(num, op);
|
|
|
|
if (saku && saku.get('num') == num) {
|
|
if (post)
|
|
post.set('image', msg[1], {silent: true}); // TEMP
|
|
postForm.insert_uploaded(msg[1]);
|
|
}
|
|
else if (post)
|
|
post.set('image', msg[1]);
|
|
|
|
if (num == MILLION) {
|
|
var $el = $('#' + num);
|
|
$el.css('background-image', oneeSama.gravitas_style(msg[1]));
|
|
var bg = $el.css('background-color');
|
|
$el.css('background-color', 'black');
|
|
setTimeout(function () { $el.css('background-color', bg); }, 500);
|
|
}
|
|
|
|
if (focus)
|
|
focus.focus();
|
|
};
|
|
|
|
dispatcher[UPDATE_POST] = function (msg, op) {
|
|
var num = msg[0], links = msg[4], extra = msg[5];
|
|
var state = [msg[2] || 0, msg[3] || 0];
|
|
var post = Threads.lookup(num, op);
|
|
if (post) {
|
|
add_post_links(post, links, op);
|
|
var body = post.get('body') || '';
|
|
post.set({body: body + msg[1], state: state});
|
|
}
|
|
|
|
if (num in ownPosts) {
|
|
if (extra)
|
|
extra.links = links;
|
|
else
|
|
extra = {links: links};
|
|
oneeSama.trigger('insertOwnPost', extra);
|
|
return;
|
|
}
|
|
var bq = $('#' + num + ' > blockquote');
|
|
if (bq.length) {
|
|
oneeSama.dice = extra && extra.dice;
|
|
oneeSama.links = links || {};
|
|
oneeSama.callback = inject;
|
|
oneeSama.buffer = bq;
|
|
oneeSama.state = state;
|
|
oneeSama.fragment(msg[1]);
|
|
}
|
|
};
|
|
|
|
dispatcher[FINISH_POST] = function (msg, op) {
|
|
var num = msg[0];
|
|
delete ownPosts[num];
|
|
var thread = Threads.get(op);
|
|
var post;
|
|
if (op == num) {
|
|
if (!thread)
|
|
return;
|
|
post = thread;
|
|
}
|
|
else {
|
|
if (!thread)
|
|
thread = UnknownThread;
|
|
post = thread.get('replies').get(num);
|
|
}
|
|
|
|
if (post)
|
|
post.set('editing', false);
|
|
};
|
|
|
|
dispatcher[DELETE_POSTS] = function (msg, op) {
|
|
var replies = Threads.lookup(op, op).get('replies');
|
|
var $section = $('#' + op);
|
|
var ownNum = saku && saku.get('num');
|
|
msg.forEach(function (num) {
|
|
var postVisible = $('#' + num).is('article');
|
|
delete ownPosts[num];
|
|
var post = replies.get(num);
|
|
clear_post_links(post, replies);
|
|
if (num === ownNum)
|
|
return postSM.feed('done');
|
|
if (num == lockTarget)
|
|
set_lock_target(null);
|
|
if (post)
|
|
replies.remove(post);
|
|
|
|
if (!THREAD && !postVisible) {
|
|
/* post not visible; decrease omit count */
|
|
var info = section_abbrev($section);
|
|
if (info && info.omit > 0) {
|
|
/* No way to know if there was an image. Doh */
|
|
var omit = info.omit - 1;
|
|
if (omit > 0)
|
|
info.stat.text(abbrev_msg(omit,
|
|
info.img));
|
|
else
|
|
info.stat.remove();
|
|
}
|
|
}
|
|
|
|
});
|
|
};
|
|
|
|
dispatcher[DELETE_THREAD] = function (msg, op) {
|
|
delete syncs[op];
|
|
delete ownPosts[op];
|
|
if (saku) {
|
|
var num = saku.get('num');
|
|
if ((saku.get('op') || num) == op)
|
|
postSM.feed('done');
|
|
if (num == op)
|
|
return;
|
|
}
|
|
var thread = Threads.get(op);
|
|
if (thread)
|
|
thread.trigger('destroy', thread, thread.collection);
|
|
};
|
|
|
|
dispatcher[LOCK_THREAD] = function (msg, op) {
|
|
var thread = Threads.get(op);
|
|
if (thread)
|
|
thread.set('locked', true);
|
|
};
|
|
|
|
dispatcher[UNLOCK_THREAD] = function (msg, op) {
|
|
var thread = Threads.get(op);
|
|
if (thread)
|
|
thread.set('locked', false);
|
|
};
|
|
|
|
dispatcher[DELETE_IMAGES] = function (msg, op) {
|
|
var replies = Threads.lookup(op, op).get('replies');
|
|
msg.forEach(function (num) {
|
|
var post = replies.get(num);
|
|
if (post)
|
|
post.unset('image');
|
|
});
|
|
};
|
|
|
|
dispatcher[SPOILER_IMAGES] = function (msg, op) {
|
|
var thread = Threads.get(op);
|
|
var replies = thread.get('replies');
|
|
msg.forEach(function (info) {
|
|
var num = info[0];
|
|
var post = (num == op) ? thread : replies.get(num);
|
|
if (post)
|
|
post.set('spoiler', info[1]);
|
|
});
|
|
};
|
|
|
|
function insert_image(info, header, toppu) {
|
|
var html = flatten(oneeSama.gazou(info, toppu)).join('');
|
|
// HACK ought to add `class="new"` in common?
|
|
html = html.replace(/<img src/, '<img class="new" src');
|
|
var fig = $(html);
|
|
if (toppu)
|
|
header.before(fig);
|
|
else
|
|
header.after(fig);
|
|
|
|
// fade in
|
|
setTimeout(function () { fig.find('img').removeClass('new'); }, 10);
|
|
}
|
|
|
|
function set_highlighted_post(num) {
|
|
$('.highlight').removeClass('highlight');
|
|
$('article#' + num).addClass('highlight');
|
|
}
|
|
|
|
var samePage = new RegExp('^(?:' + THREAD + ')?#(\\d+)$');
|
|
$DOC.on('click', 'a', function (event) {
|
|
var target = $(this);
|
|
var href = target.attr('href');
|
|
if (href && (THREAD || postForm)) {
|
|
var q = href.match(/#q(\d+)/);
|
|
if (q) {
|
|
event.preventDefault();
|
|
var id = parseInt(q[1], 10);
|
|
set_highlighted_post(id);
|
|
with_dom(function () {
|
|
open_post_box(id);
|
|
postForm.add_ref(id);
|
|
});
|
|
}
|
|
else if (THREAD) {
|
|
q = href.match(samePage);
|
|
if (q)
|
|
set_highlighted_post(q[1]);
|
|
}
|
|
}
|
|
});
|
|
|
|
$DOC.on('click', 'del', function (event) {
|
|
if (!event.spoilt) {
|
|
event.spoilt = true;
|
|
$(event.target).toggleClass('reveal');
|
|
}
|
|
});
|
|
|
|
$DOC.on('click', '.pagination input', function (event) {
|
|
location.href = $('link[rel=next]').prop('href');
|
|
});
|
|
|
|
dispatcher[SYNCHRONIZE] = connSM.feeder('sync');
|
|
dispatcher[INVALID] = connSM.feeder('invalid');
|
|
|
|
function lookup_model_path(path) {
|
|
var o = window;
|
|
if (!Array.isArray(path))
|
|
return o[path];
|
|
o = o[path[0]];
|
|
if (o) {
|
|
for (var i = 1; i < path.length; i++) {
|
|
o = o.get(path[i]);
|
|
if (!o)
|
|
break;
|
|
}
|
|
}
|
|
return o;
|
|
}
|
|
|
|
dispatcher[MODEL_SET] = function (msg, op) {
|
|
var target = lookup_model_path(msg[0]);
|
|
if (target && target.set)
|
|
target.set(msg[1]);
|
|
};
|
|
|
|
dispatcher[COLLECTION_RESET] = function (msg, op) {
|
|
var target = lookup_model_path(msg[0]);
|
|
if (target && target.reset)
|
|
target.reset(msg[1]);
|
|
};
|
|
|
|
dispatcher[COLLECTION_ADD] = function (msg, op) {
|
|
var target = lookup_model_path(msg[0]);
|
|
if (target && target.add)
|
|
target.add(msg[1], {merge: true});
|
|
};
|
|
|
|
(function () {
|
|
var m = window.location.hash.match(/^#q?(\d+)$/);
|
|
if (m)
|
|
set_highlighted_post(m[1]);
|
|
|
|
$('section').each(function () {
|
|
var s = $(this);
|
|
syncs[s.attr('id')] = parseInt(s.attr('data-sync'));
|
|
|
|
/* Insert image omission count (kinda dumb) */
|
|
if (!THREAD) {
|
|
var img = parseInt(s.attr('data-imgs')) -
|
|
s.find('img').length;
|
|
if (img > 0) {
|
|
var stat = s.find('.omit');
|
|
var o = stat.text().match(/(\d*)/)[0];
|
|
stat.text(abbrev_msg(parseInt(o), img));
|
|
}
|
|
}
|
|
});
|
|
|
|
$('del').attr('onclick', 'void(0)');
|
|
|
|
// Android browsers have no easy way to return to the top, so link it
|
|
var android = /Android/.test(navigator.userAgent);
|
|
if (android) {
|
|
var t = $.parseHTML(action_link_html('#', 'Top'))[0];
|
|
$('#bottom').css('min-width', 'inherit').after(' ', t);
|
|
}
|
|
})();
|