/* 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 $('', {
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('
', $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 $('', {
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('
', $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('
', $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);
}
})();