|
|
|
var internal_url_re = /(?:>>>)?(?:https?:\/\/)?flanchan\.moe\/s\/((?:\d|\w)(?:\d|\w|\/)*)/;
|
|
|
|
|
|
|
|
/* YOUTUBE */
|
|
|
|
|
|
|
|
// fairly liberal regexp that will accept things like
|
|
|
|
// https://m.youtube.com/watch/?v=abcdefghijk&t=2m
|
|
|
|
// youtube.com/watch/?foo=bar&v=abcdefghijk#t=5h2s
|
|
|
|
// >>>youtu.be/abcdefghijk
|
|
|
|
var youtube_url_re = /(?:>>>*?)?(?:https?:\/\/)?(?:www\.|m.)?(?:youtu\.be\/|youtube\.com\/watch\/?\?((?:[^\s#&=]+=[^\s#&]*&)*)?v=)([\w-]{11})((?:&[^\s#&=]+=[^\s#&]*)*)&?([#\?]t=[\dhms]{1,9})?/;
|
|
|
|
var youtube_time_re = /^[#\?]t=(?:(\d\d?)h)?(?:(\d{1,3})m)?(?:(\d{1,3})s)?$/;
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
|
|
function make_video(id, params, start) {
|
|
|
|
if (!params)
|
|
|
|
params = {allowFullScreen: 'true'};
|
|
|
|
params.allowScriptAccess = 'always';
|
|
|
|
var query = {
|
|
|
|
autohide: 1,
|
|
|
|
fs: 1,
|
|
|
|
modestbranding: 1,
|
|
|
|
origin: document.location.origin,
|
|
|
|
rel: 0,
|
|
|
|
showinfo: 0,
|
|
|
|
};
|
|
|
|
if (start)
|
|
|
|
query.start = start;
|
|
|
|
if (params.autoplay)
|
|
|
|
query.autoplay = params.autoplay;
|
|
|
|
if (params.loop) {
|
|
|
|
query.loop = '1';
|
|
|
|
query.playlist = id;
|
|
|
|
}
|
|
|
|
|
|
|
|
var uri = encodeURI('https://www.youtube.com/embed/' + id) + '?' +
|
|
|
|
$.param(query);
|
|
|
|
return $('<iframe></iframe>', {
|
|
|
|
type: 'text/html', src: uri,
|
|
|
|
frameborder: '0',
|
|
|
|
attr: video_dims(),
|
|
|
|
"class": 'youtube-player',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
window.make_video = make_video;
|
|
|
|
|
|
|
|
function video_dims() {
|
|
|
|
if (window.screen && screen.width <= 320)
|
|
|
|
return {width: 250, height: 150};
|
|
|
|
else
|
|
|
|
return {width: 560, height: 340};
|
|
|
|
}
|
|
|
|
|
|
|
|
$DOC.on('click', '.watch', function (e) {
|
|
|
|
if (e.which > 1 || e.metaKey || e.ctrlKey || e.altKey || e.shiftKey)
|
|
|
|
return;
|
|
|
|
var $target = $(e.target);
|
|
|
|
|
|
|
|
/* maybe squash that double-play bug? ugh, really */
|
|
|
|
if (!$target.is('a'))
|
|
|
|
return;
|
|
|
|
|
|
|
|
var $video = $target.find('iframe');
|
|
|
|
if ($video.length) {
|
|
|
|
$video.siblings('br').andSelf().remove();
|
|
|
|
$target.css('width', 'auto');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($target.data('noembed'))
|
|
|
|
return;
|
|
|
|
var m = $target.attr('href').match(youtube_url_re);
|
|
|
|
if (!m) {
|
|
|
|
/* Shouldn't happen, but degrade to normal click action */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var start = 0;
|
|
|
|
if (m[4]) {
|
|
|
|
var t = m[4].match(youtube_time_re);
|
|
|
|
if (t) {
|
|
|
|
if (t[1])
|
|
|
|
start += parseInt(t[1], 10) * 3600;
|
|
|
|
if (t[2])
|
|
|
|
start += parseInt(t[2], 10) * 60;
|
|
|
|
if (t[3])
|
|
|
|
start += parseInt(t[3], 10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var $obj = make_video(m[2], null, start);
|
|
|
|
with_dom(function () {
|
|
|
|
$target.css('width', video_dims().width).append('<br>', $obj);
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$DOC.on('mouseenter', '.watch', function (event) {
|
|
|
|
var $target = $(event.target);
|
|
|
|
if ($target.data('requestedTitle'))
|
|
|
|
return;
|
|
|
|
$target.data('requestedTitle', true);
|
|
|
|
/* Edit textNode in place so that we don't mess with the embed */
|
|
|
|
var node = text_child($target);
|
|
|
|
if (!node)
|
|
|
|
return;
|
|
|
|
var orig = node.textContent;
|
|
|
|
with_dom(function () {
|
|
|
|
node.textContent = orig + '...';
|
|
|
|
});
|
|
|
|
var m = $target.attr('href').match(youtube_url_re);
|
|
|
|
if (!m)
|
|
|
|
return;
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
url: 'https://www.googleapis.com/youtube/v3/videos',
|
|
|
|
data: {id: m[2],
|
|
|
|
key: config.GOOGLE_API_KEY,
|
|
|
|
part: 'snippet,status',
|
|
|
|
fields: 'items(snippet(title),status(embeddable))'},
|
|
|
|
dataType: 'json',
|
|
|
|
success: function (data) {
|
|
|
|
with_dom(gotInfo.bind(null, data));
|
|
|
|
},
|
|
|
|
error: function () {
|
|
|
|
with_dom(function () {
|
|
|
|
node.textContent = orig + '???';
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
function gotInfo(data) {
|
|
|
|
var title = data && data.items && data.items[0].snippet &&
|
|
|
|
data.items[0].snippet.title;
|
|
|
|
if (title) {
|
|
|
|
node.textContent = orig + ': ' + title;
|
|
|
|
$target.css({color: 'black'});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
node.textContent = orig + ' (gone?)';
|
|
|
|
|
|
|
|
if (data && data.items && data.items[0].status &&
|
|
|
|
data.items[0].status.embeddable == false) {
|
|
|
|
node.textContent += ' (EMBEDDING DISABLED)';
|
|
|
|
$target.data('noembed', true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function text_child($target) {
|
|
|
|
return $target.contents().filter(function () {
|
|
|
|
return this.nodeType === 3;
|
|
|
|
})[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* SOUNDCLOUD */
|
|
|
|
|
|
|
|
window.soundcloud_url_re = /(?:>>>*?)?(?:https?:\/\/)?(?:www\.)?soundcloud\.com\/([\w-]{1,40}\/[\w-]{1,80})\/?/;
|
|
|
|
|
|
|
|
function make_soundcloud(path, dims) {
|
|
|
|
var query = {
|
|
|
|
url: 'http://soundcloud.com/' + path,
|
|
|
|
color: 'ffaa66',
|
|
|
|
auto_play: false,
|
|
|
|
show_user: false,
|
|
|
|
show_comments: false,
|
|
|
|
};
|
|
|
|
var uri = 'https://w.soundcloud.com/player/?' + $.param(query);
|
|
|
|
return $('<iframe></iframe>', {
|
|
|
|
src: uri, width: dims.width, height: dims.height,
|
|
|
|
scrolling: 'no', frameborder: 'no',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
$DOC.on('click', '.soundcloud', function (e) {
|
|
|
|
if (e.which > 1 || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)
|
|
|
|
return;
|
|
|
|
var $target = $(e.target);
|
|
|
|
|
|
|
|
var $obj = $target.find('iframe');
|
|
|
|
if ($obj.length) {
|
|
|
|
$obj.siblings('br').andSelf().remove();
|
|
|
|
$target.css('width', 'auto');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var m = $target.attr('href').match(soundcloud_url_re);
|
|
|
|
if (!m) {
|
|
|
|
/* Shouldn't happen, but degrade to normal click action */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var width = Math.round($(window).innerWidth() * 0.75);
|
|
|
|
var $obj = make_soundcloud(m[1], {width: width, height: 166});
|
|
|
|
with_dom(function () {
|
|
|
|
$target.css('width', width).append('<br>', $obj);
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
/* lol copy pasta */
|
|
|
|
$DOC.on('mouseenter', '.soundcloud', function (event) {
|
|
|
|
var $target = $(event.target);
|
|
|
|
if ($target.data('requestedTitle'))
|
|
|
|
return;
|
|
|
|
$target.data('requestedTitle', true);
|
|
|
|
/* Edit textNode in place so that we don't mess with the embed */
|
|
|
|
var node = text_child($target);
|
|
|
|
if (!node)
|
|
|
|
return;
|
|
|
|
var orig = node.textContent;
|
|
|
|
with_dom(function () {
|
|
|
|
node.textContent = orig + '...';
|
|
|
|
});
|
|
|
|
var m = $target.attr('href').match(soundcloud_url_re);
|
|
|
|
if (!m)
|
|
|
|
return;
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
url: '//soundcloud.com/oembed',
|
|
|
|
data: {format: 'json', url: 'http://soundcloud.com/' + m[1]},
|
|
|
|
dataType: 'json',
|
|
|
|
success: function (data) {
|
|
|
|
with_dom(gotInfo.bind(null, data));
|
|
|
|
},
|
|
|
|
error: function () {
|
|
|
|
with_dom(function () {
|
|
|
|
node.textContent = orig + '???';
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
function gotInfo(data) {
|
|
|
|
var title = data && data.title;
|
|
|
|
if (title) {
|
|
|
|
node.textContent = orig + ': ' + title;
|
|
|
|
$target.css({color: 'black'});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
node.textContent = orig + ' (gone?)';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/* TWITTER */
|
|
|
|
|
|
|
|
window.twitter_url_re = /(?:>>>*?)?(?:https?:\/\/)?(?:www\.|mobile\.|m\.)?twitter\.com\/(\w{1,15})\/status\/(\d{4,20})\/?/;
|
|
|
|
|
|
|
|
$DOC.on('click', '.tweet', function (e) {
|
|
|
|
if (e.which > 1 || e.metaKey || e.ctrlKey || e.altKey || e.shiftKey)
|
|
|
|
return;
|
|
|
|
var $target = $(e.target);
|
|
|
|
if (!$target.is('a.tweet') || $target.data('tweet') == 'error')
|
|
|
|
return;
|
|
|
|
setup_tweet($target);
|
|
|
|
|
|
|
|
var $tweet = $target.find('.twitter-tweet');
|
|
|
|
if ($tweet.length) {
|
|
|
|
$tweet.siblings('br').andSelf().remove();
|
|
|
|
$target.css('width', 'auto');
|
|
|
|
text_child($target).textContent = $target.data('tweet-expanded');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fetch_tweet($target, function (err, info) {
|
|
|
|
var orig = $target.data('tweet-ref');
|
|
|
|
if (err) {
|
|
|
|
$target.data('tweet', 'error');
|
|
|
|
if (info && info.node) {
|
|
|
|
with_dom(function () {
|
|
|
|
info.node.textContent = orig + ' (error: ' + err + ')';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$target.data('tweet', info.tweet);
|
|
|
|
var w = 500;
|
|
|
|
if (window.screen && screen.width && screen.width < w)
|
|
|
|
w = screen.width - 20;
|
|
|
|
|
|
|
|
var $tweet = $($.parseHTML(info.tweet.html)[0]);
|
|
|
|
with_tweet_widget(function () {
|
|
|
|
$target.append('<br>', $tweet);
|
|
|
|
$target.css('width', w);
|
|
|
|
info.node.textContent = orig;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$DOC.on('mouseenter', '.tweet', function (event) {
|
|
|
|
var $target = $(event.target);
|
|
|
|
if (!$target.is('a.tweet') || $target.data('tweet'))
|
|
|
|
return;
|
|
|
|
setup_tweet($target);
|
|
|
|
|
|
|
|
fetch_tweet($target, function (err, info) {
|
|
|
|
if (err) {
|
|
|
|
if (info && info.node) {
|
|
|
|
$target.data('tweet', 'error');
|
|
|
|
with_dom(function () {
|
|
|
|
info.node.textContent += ' (error: ' + err + ')';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
console.warn(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var node = info.node;
|
|
|
|
var orig = $target.data('tweet-ref') || node.textContent;
|
|
|
|
var html = info.tweet && info.tweet.html;
|
|
|
|
if (!html) {
|
|
|
|
$target.data('tweet', 'error');
|
|
|
|
node.textContent = orig + ' (broken?)';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$target.data('tweet', info.tweet);
|
|
|
|
// twitter sends us HTML of the tweet; scrape it a little
|
|
|
|
var $tweet = $($.parseHTML(html)[0]);
|
|
|
|
var $p = $tweet.find('p');
|
|
|
|
if ($p.length) {
|
|
|
|
// chop the long ID number off our ref
|
|
|
|
var prefix = orig;
|
|
|
|
var m = /^(.+)\/\d{4,20}(?:s=\d+)?$/.exec(prefix);
|
|
|
|
if (m)
|
|
|
|
prefix = m[1];
|
|
|
|
|
|
|
|
var text = scrape_tweet_p($p);
|
|
|
|
with_dom(function () {
|
|
|
|
var expanded = prefix + ' \u00b7 ' + text;
|
|
|
|
$target.data('tweet-expanded', expanded);
|
|
|
|
node.textContent = expanded;
|
|
|
|
$target.css({color: 'black'});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
with_dom(function () {
|
|
|
|
node.textContent = orig + ' (could not scrape)';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
/// call this before fetch_tweet or any DOM modification of the ref
|
|
|
|
function setup_tweet($target) {
|
|
|
|
setup_tweet_widgets_script();
|
|
|
|
if ($target.data('tweet-ref'))
|
|
|
|
return;
|
|
|
|
var node = text_child($target);
|
|
|
|
if (!node)
|
|
|
|
return;
|
|
|
|
$target.data('tweet-ref', node.textContent);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// fetch & cache the json about the tweet referred to by >>>/@ref $target
|
|
|
|
function fetch_tweet($target, cb) {
|
|
|
|
var node = text_child($target);
|
|
|
|
if (!node)
|
|
|
|
return cb("ref's text node not found");
|
|
|
|
|
|
|
|
var cached = $target.data('tweet');
|
|
|
|
if (cached == 'error')
|
|
|
|
return cb('could not contact twitter', {node: node});
|
|
|
|
if (cached && cached.inflight) {
|
|
|
|
var queue = TW_CB[cached.inflight];
|
|
|
|
if (queue)
|
|
|
|
queue.callbacks.push(cb);
|
|
|
|
else
|
|
|
|
cb('gone', {node: node});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cached)
|
|
|
|
return cb(null, {tweet: cached, node: node});
|
|
|
|
|
|
|
|
var tweet_url = $target.attr('href');
|
|
|
|
var m = tweet_url.match(twitter_url_re);
|
|
|
|
if (!m)
|
|
|
|
return cb('invalid tweet ref', {node: node});
|
|
|
|
var handle = m[1];
|
|
|
|
var id = m[2];
|
|
|
|
|
|
|
|
// if this request is already in-flight, just wait on the result
|
|
|
|
var flight = TW_CB[id];
|
|
|
|
if (flight) {
|
|
|
|
flight.node = node;
|
|
|
|
flight.callbacks.push(cb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// chop the prefix off the url and add our own
|
|
|
|
var chop = tweet_url.indexOf(handle);
|
|
|
|
if (chop < 0)
|
|
|
|
return;
|
|
|
|
var our_url = '../outbound/tweet/' + tweet_url.substr(chop);
|
|
|
|
|
|
|
|
// we're ready, make the call
|
|
|
|
TW_CB[id] = {node: node, callbacks: [cb]};
|
|
|
|
$target.data('tweet', {inflight: id});
|
|
|
|
|
|
|
|
var theme = 'light'; // TODO tie into current theme
|
|
|
|
$.ajax({
|
|
|
|
url: our_url,
|
|
|
|
data: {theme: theme},
|
|
|
|
dataType: 'json',
|
|
|
|
success: function (json) {
|
|
|
|
got_tweet(json, id);
|
|
|
|
},
|
|
|
|
error: function (xhr, stat, error) {
|
|
|
|
failed_tweet(error, id);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
var orig = $target.data('tweet-ref') || node.textContent;
|
|
|
|
with_dom(function () {
|
|
|
|
node.textContent = orig + '...';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function scrape_tweet_p($p) {
|
|
|
|
var bits = $p.contents();
|
|
|
|
var text = "";
|
|
|
|
var i;
|
|
|
|
for (i = 0; i < bits.length; i++) {
|
|
|
|
var node = bits[i];
|
|
|
|
if (node.nodeType == 3)
|
|
|
|
text += node.textContent;
|
|
|
|
else if (node.nodeType == 1) {
|
|
|
|
if (node.tagName == 'A')
|
|
|
|
text += node.textContent;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (i < bits.length)
|
|
|
|
text += ' \u2026';
|
|
|
|
if (!text)
|
|
|
|
text = $p.text();
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
|
|
|
var TW_CB = {};
|
|
|
|
|
|
|
|
function failed_tweet(err, id) {
|
|
|
|
var req = TW_CB[id];
|
|
|
|
if (!req)
|
|
|
|
return;
|
|
|
|
delete TW_CB[id];
|
|
|
|
var node = req.node;
|
|
|
|
if (node) {
|
|
|
|
req.node = null;
|
|
|
|
var payload = {node: node};
|
|
|
|
while (req.callbacks.length)
|
|
|
|
req.callbacks.shift()(err || 'offline?', payload);
|
|
|
|
}
|
|
|
|
req.callbacks = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function got_tweet(tweet, id) {
|
|
|
|
var saved = TW_CB[id];
|
|
|
|
if (!saved) {
|
|
|
|
console.warn('tweet callback for non-pending tweet', tweet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
delete TW_CB[id];
|
|
|
|
|
|
|
|
var payload = {node: saved.node, tweet: tweet};
|
|
|
|
saved.node = null;
|
|
|
|
while (saved.callbacks.length)
|
|
|
|
saved.callbacks.shift()(null, payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
var TW_WG_SCRIPT;
|
|
|
|
|
|
|
|
function setup_tweet_widgets_script() {
|
|
|
|
TW_WG_SCRIPT = $.getScript('https://platform.twitter.com/widgets.js').done(function () {
|
|
|
|
TW_WG_SCRIPT = {done: twttr.ready};
|
|
|
|
twttr.ready(function () {
|
|
|
|
TW_WG_SCRIPT = true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
setup_tweet_widgets_script = function () {};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// when creating a tweet widget, wrap the DOM insertion with this
|
|
|
|
function with_tweet_widget(func) {
|
|
|
|
function go() {
|
|
|
|
func();
|
|
|
|
if (window.twttr)
|
|
|
|
twttr.widgets.load();
|
|
|
|
}
|
|
|
|
if (TW_WG_SCRIPT && TW_WG_SCRIPT.done) {
|
|
|
|
TW_WG_SCRIPT.done(function () {
|
|
|
|
with_dom(go);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
with_dom(go);
|
|
|
|
}
|
|
|
|
|
|
|
|
})();
|