diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js
index 336b9391a..96fdb725f 100644
--- a/builds/appchan-x.user.js
+++ b/builds/appchan-x.user.js
@@ -115,7 +115,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
+ var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__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,
@@ -11632,10 +11632,10 @@
}
};
- Linkify = {
+ Embedding = {
init: function() {
var type, _i, _len, _ref;
- if (!Conf['Linkify']) {
+ if (!(Conf['Embedding'] || Conf['Link Title'])) {
return;
}
this.types = {};
@@ -11644,39 +11644,689 @@
type = _ref[_i];
this.types[type.key] = type;
}
- if (Conf['Comment Expansion']) {
- ExpandComment.callbacks.push(this.node);
+ if (Conf['Floating Embeds']) {
+ this.dialog = UI.dialog('embedding', 'top: 50px; right: 0px;', {
+ innerHTML: "
\r\r"
+ });
+ this.media = $('#media-embed', this.dialog);
+ $.one(d, '4chanXInitFinished', this.ready);
}
- if (Conf['Embedding'] || Conf['Link Title']) {
- this.embedProcess = Function('link', 'post', "var data = this.services(link);\nif (data) {" + ((Conf['Embedding'] ? 'this.embed(data); ' : '') + (Conf['Link Title'] ? 'data.push(post); this.title(data);' : '')) + "}");
+ if (Conf['Link Title']) {
+ return $.on(d, '4chanXInitFinished PostsInserted', function() {
+ var key, service, _ref1, _ref2;
+ _ref1 = Embedding.types;
+ for (key in _ref1) {
+ service = _ref1[key];
+ if ((_ref2 = service.title) != null ? _ref2.batchSize : void 0) {
+ Embedding.flushTitles(service.title);
+ }
+ }
+ });
}
- return Post.callbacks.push({
- name: 'Linkify',
- cb: this.node
- });
},
events: function(post) {
var el, i, items;
+ if (!Conf['Embedding']) {
+ return;
+ }
i = 0;
items = $$('.embedder', post.nodes.comment);
while (el = items[i++]) {
- $.on(el, 'click', Linkify.cb.toggle);
+ $.on(el, 'click', Embedding.cb.toggle);
if ($.hasClass(el, 'embedded')) {
- Linkify.cb.toggle.call(el);
+ Embedding.cb.toggle.call(el);
}
}
},
+ process: function(link, post) {
+ var data;
+ if (!(Conf['Embedding'] || Conf['Link Title'])) {
+ return;
+ }
+ if ($.x('ancestor::pre', link)) {
+ return;
+ }
+ if (data = Embedding.services(link)) {
+ data.post = post;
+ if (Conf['Embedding']) {
+ Embedding.embed(data);
+ }
+ if (Conf['Link Title']) {
+ return Embedding.title(data);
+ }
+ }
+ },
+ services: function(link) {
+ var href, match, type, _i, _len, _ref;
+ href = link.href;
+ _ref = Embedding.ordered_types;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ type = _ref[_i];
+ if (!(match = type.regExp.exec(href))) {
+ continue;
+ }
+ if (type.dummy || type.httpOnly && location.protocol !== 'http:') {
+ return;
+ }
+ return {
+ key: type.key,
+ uid: match[1],
+ options: match[2],
+ link: link
+ };
+ }
+ },
+ embed: function(data) {
+ var embed, key, link, name, options, post, uid, value, _ref;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ embed = $.el('a', {
+ className: 'embedder',
+ rel: 'nofollow noreferrer',
+ href: link.href,
+ textContent: '(embed)'
+ });
+ _ref = {
+ key: key,
+ uid: uid,
+ options: options
+ };
+ for (name in _ref) {
+ value = _ref[name];
+ embed.dataset[name] = value;
+ }
+ $.addClass(link, "" + embed.dataset.key);
+ $.on(embed, 'click', Embedding.cb.toggle);
+ $.after(link, [$.tn(' '), embed]);
+ if (Conf['Auto-embed'] && !Conf['Floating Embeds'] && !post.isFetchedQuote) {
+ return $.asap((function() {
+ return doc.contains(embed);
+ }), function() {
+ return Embedding.cb.toggle.call(embed);
+ });
+ }
+ },
+ ready: function() {
+ $.addClass(Embedding.dialog, 'empty');
+ $.on($('.close', Embedding.dialog), 'click', Embedding.closeFloat);
+ $.on($('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed);
+ $.on($('.jump', Embedding.dialog), 'click', function() {
+ if (doc.contains(Embedding.lastEmbed)) {
+ return Header.scrollTo(Embedding.lastEmbed);
+ }
+ });
+ return $.add(d.body, Embedding.dialog);
+ },
+ closeFloat: function() {
+ delete Embedding.lastEmbed;
+ $.addClass(Embedding.dialog, 'empty');
+ return $.replace(Embedding.media.firstChild, $.el('div'));
+ },
+ dragEmbed: function() {
+ var style;
+ style = Embedding.media.style;
+ if (Embedding.dragEmbed.mouseup) {
+ $.off(d, 'mouseup', Embedding.dragEmbed);
+ Embedding.dragEmbed.mouseup = false;
+ style.visibility = '';
+ return;
+ }
+ $.on(d, 'mouseup', Embedding.dragEmbed);
+ Embedding.dragEmbed.mouseup = true;
+ return style.visibility = 'hidden';
+ },
+ title: function(data) {
+ var key, link, options, post, service, uid;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ if (!(service = Embedding.types[key].title)) {
+ return;
+ }
+ if (service.batchSize) {
+ (service.queue || (service.queue = [])).push(data);
+ if (service.queue.length >= service.batchSize) {
+ return Embedding.flushTitles(service);
+ }
+ } else {
+ if (!$.cache(service.api(uid), (function() {
+ return Embedding.cb.title(this, data);
+ }), {
+ responseType: 'json'
+ })) {
+ return $.extend(link, {
+ innerHTML: "[" + E(key) + "] Title Link Blocked (are you using NoScript?)"
+ });
+ }
+ }
+ },
+ flushTitles: function(service) {
+ var cb, data, queue, _i, _len;
+ queue = service.queue;
+ if (!(queue != null ? queue.length : void 0)) {
+ return;
+ }
+ service.queue = [];
+ cb = function() {
+ var data, _i, _len;
+ for (_i = 0, _len = queue.length; _i < _len; _i++) {
+ data = queue[_i];
+ Embedding.cb.title(this, data);
+ }
+ };
+ if (!$.cache(service.api((function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = queue.length; _i < _len; _i++) {
+ data = queue[_i];
+ _results.push(data.uid);
+ }
+ return _results;
+ })()), cb, {
+ responseType: 'json'
+ })) {
+ for (_i = 0, _len = queue.length; _i < _len; _i++) {
+ data = queue[_i];
+ $.extend(data.link, {
+ innerHTML: "[" + E(data.key) + "] Title Link Blocked (are you using NoScript?)"
+ });
+ }
+ }
+ },
+ cb: {
+ toggle: function(e) {
+ var div;
+ if (e != null) {
+ e.preventDefault();
+ }
+ if (Conf['Floating Embeds']) {
+ if (!(div = Embedding.media.firstChild)) {
+ return;
+ }
+ $.replace(div, Embedding.cb.embed(this));
+ Embedding.lastEmbed = Get.postFromNode(this).nodes.root;
+ $.rmClass(Embedding.dialog, 'empty');
+ return;
+ }
+ if ($.hasClass(this, "embedded")) {
+ if (!$.hasClass(this.previousElementSibling, 'linkify')) {
+ $.rm(this.previousElementSibling);
+ }
+ this.previousElementSibling.hidden = false;
+ this.textContent = '(embed)';
+ } else {
+ this.previousElementSibling.hidden = true;
+ $.before(this, Embedding.cb.embed(this));
+ this.textContent = '(unembed)';
+ }
+ return $.toggleClass(this, 'embedded');
+ },
+ embed: function(a) {
+ var el, type;
+ el = (type = Embedding.types[a.dataset.key]).el(a);
+ el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px";
+ return el;
+ },
+ title: function(req, data) {
+ var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ status = req.status;
+ service = Embedding.types[key].title;
+ text = "[" + key + "] " + ((function() {
+ switch (status) {
+ case 200:
+ case 304:
+ return service.text(req.response, uid);
+ case 404:
+ return "Not Found";
+ case 403:
+ return "Forbidden or Private";
+ default:
+ return "" + status + "'d";
+ }
+ })());
+ link.dataset.original = link.textContent;
+ link.textContent = text;
+ _ref = post.clones;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ post2 = _ref[_i];
+ _ref1 = $$('a.linkify', post2.nodes.comment);
+ for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
+ link2 = _ref1[_j];
+ if (!(link2.href === link.href)) {
+ continue;
+ }
+ link2.dataset.original = link2.textContent;
+ link2.textContent = text;
+ }
+ }
+ }
+ },
+ ordered_types: [
+ {
+ key: 'audio',
+ regExp: /\.(?:mp3|ogg|wav)(?:\?|$)/i,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: a.href
+ });
+ }
+ }, {
+ key: 'gist',
+ regExp: /^\w+:\/\/gist\.github\.com\/(?:[\w\-]+\/)?(\w+)/,
+ el: function(a) {
+ var content, el;
+ el = $.el('iframe');
+ el.setAttribute('sandbox', 'allow-scripts');
+ content = {
+ innerHTML: "" + E(a.dataset.uid) + ""
+ };
+ el.src = "data:text/html;charset=utf-8," + (encodeURIComponent(content.innerHTML));
+ return el;
+ },
+ title: {
+ api: function(uid) {
+ return "https://api.github.com/gists/" + uid;
+ },
+ text: function(_arg) {
+ var file, files;
+ files = _arg.files;
+ for (file in files) {
+ if (files.hasOwnProperty(file)) {
+ return file;
+ }
+ }
+ }
+ }
+ }, {
+ key: 'image',
+ regExp: /\.(?:gif|png|jpg|jpeg|bmp)(?:\?|$)/i,
+ style: '',
+ el: function(a) {
+ return $.el('div', {
+ innerHTML: "
"
+ });
+ }
+ }, {
+ key: 'InstallGentoo',
+ regExp: /^\w+:\/\/paste\.installgentoo\.com\/view\/(?:raw\/|download\/|embed\/)?(\w+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://paste.installgentoo.com/view/embed/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'Twitter',
+ regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'LiveLeak',
+ regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/,
+ httpOnly: true,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ width: "640",
+ height: "360",
+ src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid,
+ frameborder: "0"
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'MediaCrush',
+ regExp: /^\w+:\/\/(?:www\.)?mediacru\.sh\/([\w\-]+)/,
+ style: '',
+ el: function(a) {
+ var el;
+ el = $.el('div');
+ $.queueTask(function() {
+ return $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() {
+ var embed, ext, file, files, i, status, type, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
+ if (!doc.contains(el)) {
+ return;
+ }
+ status = this.status;
+ if (status !== 200 && status !== 304) {
+ return el.textContent = "ERROR " + status;
+ }
+ files = this.response.files;
+ _ref = ['video/mp4', 'video/webm', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg'];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ type = _ref[_i];
+ for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
+ file = files[_j];
+ if (file.type === type) {
+ embed = file;
+ break;
+ }
+ }
+ if (embed) {
+ break;
+ }
+ }
+ if (!embed) {
+ return el.textContent = "ERROR: Not a valid filetype";
+ }
+ switch (embed.type) {
+ case 'video/mp4':
+ case 'video/webm':
+ case 'video/ogv':
+ $.extend(el, {
+ innerHTML: ""
+ });
+ _ref1 = ['mp4', 'webm'];
+ for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) {
+ ext = _ref1[i];
+ el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext;
+ }
+ break;
+ case 'image/svg+xml':
+ case 'image/png':
+ case 'image/gif':
+ case 'image/jpeg':
+ $.extend(el, {
+ innerHTML: "
"
+ });
+ break;
+ case 'audio/mpeg':
+ case 'audio/ogg':
+ $.extend(el, {
+ innerHTML: ""
+ });
+ break;
+ default:
+ el.textContent = "ERROR: No valid filetype.";
+ }
+ });
+ });
+ return el;
+ }
+ }, {
+ key: 'pastebin',
+ regExp: /^\w+:\/\/(?:\w+\.)?pastebin\.com\/(?!u\/)(?:[\w\.]+\?i\=)?(\w+)/,
+ httpOnly: true,
+ el: function(a) {
+ var div;
+ return div = $.el('iframe', {
+ src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'gfycat',
+ regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/,
+ el: function(a) {
+ var div;
+ return div = $.el('iframe', {
+ src: "//gfycat.com/iframe/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'SoundCloud',
+ regExp: /^\w+:\/\/(?:www\.)?(?:soundcloud\.com\/|snd\.sc\/)([\w\-\/]+)/,
+ style: 'border: 0; width: 500px; height: 400px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid))
+ });
+ },
+ title: {
+ api: function(uid) {
+ return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid));
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'StrawPoll',
+ regExp: /^\w+:\/\/(?:www\.)?strawpoll\.me\/(?:embed_\d+\/)?(\d+(?:\/r)?)/,
+ httpOnly: true,
+ style: 'border: 0; width: 600px; height: 406px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "http://strawpoll.me/embed_1/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'TwitchTV',
+ regExp: /^\w+:\/\/(?:www\.)?twitch\.tv\/([^#\&\?]*)/,
+ httpOnly: true,
+ style: "border: none; width: 640px; height: 360px;",
+ el: function(a) {
+ var channel, id, idparam, obj, result, type, _;
+ if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) {
+ _ = result[0], channel = result[1], type = result[2], id = result[3];
+ idparam = {
+ 'b': 'archive_id',
+ 'c': 'chapter_id'
+ };
+ obj = $.el('object', {
+ data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
+ });
+ $.extend(obj, {
+ innerHTML: ""
+ });
+ obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id;
+ return obj;
+ } else {
+ channel = (/(\w+)/.exec(a.dataset.uid))[0];
+ obj = $.el('object', {
+ data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel
+ });
+ $.extend(obj, {
+ innerHTML: ""
+ });
+ obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25";
+ return obj;
+ }
+ }
+ }, {
+ key: 'Vocaroo',
+ regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: "http://vocaroo.com/media_command.php?media=" + a.dataset.uid + "&command=download_ogg"
+ });
+ }
+ }, {
+ key: 'Vimeo',
+ regExp: /^\w+:\/\/(?:www\.)?vimeo\.com\/(\d+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque"
+ });
+ },
+ title: {
+ api: function(uid) {
+ return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid;
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'Vine',
+ regExp: /^\w+:\/\/(?:www\.)?vine\.co\/v\/(\w+)/,
+ style: 'border: none; width: 500px; height: 500px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://vine.co/v/" + a.dataset.uid + "/card"
+ });
+ }
+ }, {
+ key: 'YouTube',
+ regExp: /^\w+:\/\/(?:youtu.be\/|[\w\.]*youtube[\w\.]*\/.*(?:v=|\/embed\/|\/v\/|\/videos\/))([\w\-]{11})[^#\&\?]?(.*)/,
+ el: function(a) {
+ var el, start;
+ start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/);
+ if (start) {
+ start = start[1];
+ }
+ if (start && !/^\d+$/.test(start)) {
+ start += ' 0h0m0s';
+ start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1];
+ }
+ el = $.el('iframe', {
+ src: "//www.youtube.com/embed/" + a.dataset.uid + "?wmode=opaque" + (start ? '&start=' + start : '')
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ },
+ title: {
+ batchSize: 50,
+ api: function(uids) {
+ var ids, key;
+ ids = encodeURIComponent(uids.join(','));
+ key = '';
+ return "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=" + ids + "&fields=items%28id%2Csnippet%28title%29%29&key=" + key;
+ },
+ text: function(data, uid) {
+ var item, _i, _len, _ref;
+ _ref = data.items;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ item = _ref[_i];
+ if (item.id === uid) {
+ return item.snippet.title;
+ }
+ }
+ return 'Not Found';
+ }
+ }
+ }, {
+ key: 'Loopvid',
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/((?:pf|kd|lv|mc|gd|gh|db|nn)\/[\w\-]+(,[\w\-]+)*|fc\/\w+\/\d+)/,
+ style: 'max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ var base, el, host, name, names, type, types, url, _, _i, _j, _len, _len1, _ref, _ref1;
+ el = $.el('video', {
+ controls: true,
+ preload: 'auto',
+ loop: true
+ });
+ _ref = a.dataset.uid.match(/(\w+)\/(.*)/), _ = _ref[0], host = _ref[1], names = _ref[2];
+ types = host === 'gd' || host === 'fc' ? [''] : ['.webm', '.mp4'];
+ _ref1 = names.split(',');
+ for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
+ name = _ref1[_i];
+ for (_j = 0, _len1 = types.length; _j < _len1; _j++) {
+ type = types[_j];
+ base = "" + name + type;
+ url = (function() {
+ switch (host) {
+ case 'pf':
+ return "http://a.pomf.se/" + base;
+ case 'kd':
+ return "http://kastden.org/loopvid/" + base;
+ case 'lv':
+ return "http://loopvid.mooo.com/videos/" + base;
+ case 'mc':
+ return "https://cdn.mediacru.sh/" + base;
+ case 'gd':
+ return "https://docs.google.com/uc?export=download&id=" + base;
+ case 'gh':
+ return "https://googledrive.com/host/" + base;
+ case 'db':
+ return "https://googledrive.com/host/" + base;
+ case 'fc':
+ return "//i.4cdn.org/" + base + ".webm";
+ case 'nn':
+ return "http://naenara.eu/loopvids/" + base;
+ }
+ })();
+ $.add(el, $.el('source', {
+ src: url
+ }));
+ }
+ }
+ return el;
+ }
+ }, {
+ key: 'Clyp',
+ regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: "http://clyp.it/" + a.dataset.uid + ".ogg"
+ });
+ }
+ }, {
+ key: 'Loopvid-dummy',
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\//,
+ dummy: true
+ }, {
+ key: 'MediaFire-dummy',
+ regExp: /^\w+:\/\/(?:www\.)?mediafire.com\//,
+ dummy: true
+ }, {
+ key: 'video',
+ regExp: /\.(?:ogv|webm|mp4)(?:\?|$)/i,
+ style: 'max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ return $.el('video', {
+ controls: true,
+ preload: 'auto',
+ src: a.href
+ });
+ }
+ }
+ ]
+ };
+
+ Linkify = {
+ init: function() {
+ var _ref;
+ if (((_ref = g.VIEW) !== 'index' && _ref !== 'thread') || !Conf['Linkify']) {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ Post.callbacks.push({
+ name: 'Linkify',
+ cb: this.node
+ });
+ CatalogThread.callbacks.push({
+ name: 'Linkify',
+ cb: this.catalogNode
+ });
+ return Embedding.init();
+ },
node: function() {
- var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word;
+ var link, links, _i, _len;
if (this.isClone) {
- return (Conf['Embedding'] ? Linkify.events(this) : null);
+ return Embedding.events(this);
}
if (!Linkify.regString.test(this.info.comment)) {
return;
}
+ links = Linkify.process(this.nodes.comment);
+ for (_i = 0, _len = links.length; _i < _len; _i++) {
+ link = links[_i];
+ Embedding.process(link, this);
+ }
+ },
+ catalogNode: function() {
+ if (!Linkify.regString.test(this.thread.OP.info.comment)) {
+ return;
+ }
+ return Linkify.process(this.nodes.comment);
+ },
+ process: function(node) {
+ var data, end, endNode, i, index, length, links, result, saved, snapshot, space, test, word;
test = /[^\s'"]+/g;
space = /[\s'"]/;
- snapshot = $.X('.//br|.//text()', this.nodes.comment);
+ snapshot = $.X('.//br|.//text()', node);
i = 0;
links = [];
while (node = snapshot.snapshotItem(i++)) {
@@ -11696,16 +12346,18 @@
}
endNode = saved;
data = saved.data;
- word += data;
- length = data.length;
if (end = space.exec(data)) {
+ word += data.slice(0, end.index);
test.lastIndex = length = end.index;
i--;
break;
+ } else {
+ length = data.length;
+ word += data;
}
}
}
- if (Linkify.regString.exec(word)) {
+ if (Linkify.regString.test(word)) {
links.push(Linkify.makeRange(node, endNode, index, length));
}
if (!(test.lastIndex && node === endNode)) {
@@ -11715,12 +12367,11 @@
}
i = links.length;
while (i--) {
- link = links[i];
- Linkify.embedProcess(Linkify.makeLink(link), this);
+ links[i] = Linkify.makeLink(links[i]);
}
+ return links;
},
- embedProcess: function() {},
- regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i,
+ regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/?])|([-a-z\d]+[.])+(aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?![^\s'"]))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i,
makeRange: function(startNode, endNode, startOffset, endOffset) {
var range;
range = document.createRange();
@@ -11731,11 +12382,8 @@
makeLink: function(range) {
var a, i, t, text;
text = range.toString();
- i = 0;
- while (/[(\[{<>]/.test(text.charAt(i))) {
- i++;
- }
- if (i) {
+ i = text.search(Linkify.regString);
+ if (i > 0) {
text = text.slice(i);
while (range.startOffset + i >= range.startContainer.data.length) {
i--;
@@ -11760,7 +12408,7 @@
range.setEnd(range.endContainer, range.endOffset - i);
}
}
- if (!/(mailto:|.+:\/\/)/.test(text)) {
+ if (!/((mailto|magnet):|.+:\/\/)/.test(text)) {
text = (/@/.test(text) ? 'mailto:' : 'http://') + text;
}
a = $.el('a', {
@@ -11773,414 +12421,7 @@
range.insertNode(a);
range.detach();
return a;
- },
- services: function(link) {
- var href, match, type, _i, _len, _ref;
- href = link.href;
- _ref = Linkify.ordered_types;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- type = _ref[_i];
- if (!(match = type.regExp.exec(href))) {
- continue;
- }
- if (type.dummy) {
- return;
- }
- return [type.key, match[1], match[2], link];
- }
- },
- embed: function(data) {
- var embed, href, key, link, name, options, post, uid, value, _ref;
- key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
- href = link.href;
- embed = $.el('a', {
- className: 'embedder',
- href: 'javascript:;',
- textContent: '(embed)'
- });
- _ref = {
- key: key,
- href: href,
- uid: uid,
- options: options
- };
- for (name in _ref) {
- value = _ref[name];
- embed.dataset[name] = value;
- }
- $.addClass(link, "" + embed.dataset.key);
- $.on(embed, 'click', Linkify.cb.toggle);
- $.after(link, [$.tn(' '), embed]);
- if (Conf['Auto-embed']) {
- return Linkify.cb.toggle.call(embed);
- }
- },
- title: function(data) {
- var err, key, link, options, post, service, title, titles, uid;
- key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
- if (!(service = Linkify.types[key].title)) {
- return;
- }
- titles = Conf['CachedTitles'];
- if (title = titles[uid]) {
- return link.textContent = title[0];
- } else {
- try {
- return $.cache(service.api(uid), (function() {
- return Linkify.cb.title(this, data);
- }), {
- responseType: 'json'
- });
- } catch (_error) {
- err = _error;
- link.innerHTML = 'Title Link Blocked (are you using NoScript?)';
- $.prepend(link, $.tn("[" + key + "] "));
- }
- }
- },
- cb: {
- toggle: function() {
- if ($.hasClass(this, "embedded")) {
- $.rm(this.previousElementSibling);
- this.previousElementSibling.hidden = false;
- this.textContent = '(embed)';
- } else {
- this.previousElementSibling.hidden = true;
- $.before(this, Linkify.cb.embed(this));
- this.textContent = '(unembed)';
- }
- return $.toggleClass(this, 'embedded');
- },
- embed: function(a) {
- var el, type;
- el = (type = Linkify.types[a.dataset.key]).el(a);
- el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px";
- return el;
- },
- title: function(req, data) {
- var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1;
- key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
- status = req.status;
- service = Linkify.types[key].title;
- text = "[" + key + "] " + ((function() {
- switch (status) {
- case 200:
- case 304:
- return service.text(req.response);
- case 404:
- return "Not Found";
- case 403:
- return "Forbidden or Private";
- default:
- return "" + status + "'d";
- }
- })());
- link.textContent = text;
- _ref = post.clones;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- post2 = _ref[_i];
- _ref1 = $$('a', post2.nodes.comment);
- for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
- link2 = _ref1[_j];
- if (link2.href === link.href) {
- link2.textContent = text;
- }
- }
- }
- }
- },
- ordered_types: [
- {
- key: 'audio',
- regExp: /(.*\.(mp3|ogg|wav))$/,
- style: '',
- el: function(a) {
- return $.el('audio', {
- controls: true,
- preload: 'auto',
- src: a.dataset.uid
- });
- }
- }, {
- key: 'gist',
- regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/,
- el: function(a) {
- var div;
- return div = $.el('iframe', {
- src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js"
- });
- },
- title: {
- api: function(uid) {
- return "https://api.github.com/gists/" + uid;
- },
- text: function(_arg) {
- var file, files;
- files = _arg.files;
- for (file in files) {
- if (files.hasOwnProperty(file)) {
- return file;
- }
- }
- }
- }
- }, {
- key: 'image',
- regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/,
- style: 'border: 0; width: auto; height: auto;',
- el: function(a) {
- var el;
- el = $.el('div');
- el.innerHTML = '
';
- el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href;
- return el;
- }
- }, {
- key: 'InstallGentoo',
- regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/,
- el: function(a) {
- return $.el('iframe', {
- src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid
- });
- }
- }, {
- key: 'Twitter',
- regExp: /.*twitter.com\/(.+\/status\/\d+)/,
- el: function(a) {
- return $.el('iframe', {
- src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid
- });
- }
- }, {
- key: 'LiveLeak',
- regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/,
- el: function(a) {
- var el;
- el = $.el('iframe', {
- width: "640",
- height: "360",
- src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid,
- frameborder: "0"
- });
- el.setAttribute("allowfullscreen", "true");
- return el;
- }
- }, {
- key: 'MediaCrush',
- regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i,
- style: 'border: 0;',
- el: function(a) {
- var el;
- el = $.el('div');
- $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() {
- var embed, ext, file, files, i, status, type, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _results, _results1;
- status = this.status;
- if (status !== 200 && status !== 304) {
- return el.textContent = "ERROR " + status;
- }
- files = this.response.files;
- _ref = ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg'];
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- type = _ref[_i];
- for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
- file = files[_j];
- if (file.type === type) {
- embed = file;
- break;
- }
- }
- if (embed) {
- break;
- }
- }
- if (!embed) {
- return div.textContent = "ERROR: Not a valid filetype";
- }
- switch (embed.type) {
- case 'video/mp4':
- case 'video/webm':
- case 'video/ogv':
- el.innerHTML = '';
- _ref1 = ['mp4', 'webm', 'ogv'];
- _results = [];
- for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) {
- ext = _ref1[i];
- _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext);
- }
- return _results;
- break;
- case 'image/svg+xml':
- case 'image/png':
- case 'image/gif':
- case 'image/jpeg':
- el.innerHTML = '
';
- el.firstChild.href = a.dataset.href;
- return el.firstChild.firstChild.src = "https://mediacru.sh/" + file.file;
- case 'audio/mpeg':
- case 'audio/ogg':
- el.innerHTML = '';
- _ref2 = ['ogg', 'mp3'];
- _results1 = [];
- for (i = _l = 0, _len3 = _ref2.length; _l < _len3; i = ++_l) {
- ext = _ref2[i];
- _results1.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext);
- }
- return _results1;
- break;
- default:
- return el.textContent = "ERROR: No valid filetype.";
- }
- });
- return el;
- }
- }, {
- key: 'pastebin',
- regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/,
- el: function(a) {
- var div;
- return div = $.el('iframe', {
- src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid
- });
- }
- }, {
- key: 'gfycat',
- regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/,
- el: function(a) {
- var div;
- return div = $.el('iframe', {
- src: "http://gfycat.com/iframe/" + a.dataset.uid
- });
- }
- }, {
- key: 'SoundCloud',
- regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/,
- style: 'border: 0; width: 500px; height: 400px;',
- el: function(a) {
- return $.el('iframe', {
- src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid))
- });
- },
- title: {
- api: function(uid) {
- return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid));
- },
- text: function(_) {
- return _.title;
- }
- }
- }, {
- key: 'StrawPoll',
- regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/,
- style: 'border: 0; width: 600px; height: 406px;',
- el: function(a) {
- return $.el('iframe', {
- src: "http://strawpoll.me/embed_1/" + a.dataset.uid
- });
- }
- }, {
- key: 'TwitchTV',
- regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/,
- style: "border: none; width: 640px; height: 360px;",
- el: function(a) {
- var channel, id, idparam, obj, result, type, _;
- if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) {
- _ = result[0], channel = result[1], type = result[2], id = result[3];
- idparam = {
- 'b': 'archive_id',
- 'c': 'chapter_id'
- };
- obj = $.el('object', {
- data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
- });
- obj.innerHTML = '';
- obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id;
- return obj;
- } else {
- channel = (/(\w+)/.exec(a.dataset.uid))[0];
- obj = $.el('object', {
- data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel
- });
- obj.innerHTML = '';
- obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25";
- return obj;
- }
- }
- }, {
- key: 'Vocaroo',
- regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/,
- style: '',
- el: function(a) {
- return $.el('audio', {
- controls: true,
- preload: 'auto',
- src: "http://vocaroo.com/media_command.php?media=" + (a.dataset.uid.replace(/^i\//, '')) + "&command=download_ogg"
- });
- }
- }, {
- key: 'Vimeo',
- regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/,
- el: function(a) {
- return $.el('iframe', {
- src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque"
- });
- },
- title: {
- api: function(uid) {
- return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid;
- },
- text: function(_) {
- return _.title;
- }
- }
- }, {
- key: 'Vine',
- regExp: /.*(?:vine.co\/)([^#\&\?]*).*/,
- style: 'border: none; width: 500px; height: 500px;',
- el: function(a) {
- return $.el('iframe', {
- src: "https://vine.co/" + a.dataset.uid + "/card"
- });
- }
- }, {
- key: 'YouTube',
- regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/,
- el: function(a) {
- var el;
- el = $.el('iframe', {
- src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque"
- });
- el.setAttribute("allowfullscreen", "true");
- return el;
- },
- title: {
- api: function(uid) {
- return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode";
- },
- text: function(data) {
- return data.entry.title.$t;
- }
- }
- }, {
- key: 'Loopvid',
- regExp: /.*loopvid.appspot.com\/.*/,
- dummy: true
- }, {
- key: 'MediaFire',
- regExp: /.*mediafire.com\/.*/,
- dummy: true
- }, {
- key: 'video',
- regExp: /(.*\.(ogv|webm|mp4))$/,
- style: 'border: 0; width: auto; height: auto;',
- el: function(a) {
- return $.el('video', {
- controls: 'controls',
- preload: 'auto',
- src: a.dataset.uid
- });
- }
- }
- ]
+ }
};
ArchiveLink = {
diff --git a/builds/crx/script.js b/builds/crx/script.js
index a498bf98b..283cab3ff 100644
--- a/builds/crx/script.js
+++ b/builds/crx/script.js
@@ -88,7 +88,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
+ var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__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,
@@ -11654,10 +11654,10 @@
}
};
- Linkify = {
+ Embedding = {
init: function() {
var type, _i, _len, _ref;
- if (!Conf['Linkify']) {
+ if (!(Conf['Embedding'] || Conf['Link Title'])) {
return;
}
this.types = {};
@@ -11666,39 +11666,689 @@
type = _ref[_i];
this.types[type.key] = type;
}
- if (Conf['Comment Expansion']) {
- ExpandComment.callbacks.push(this.node);
+ if (Conf['Floating Embeds']) {
+ this.dialog = UI.dialog('embedding', 'top: 50px; right: 0px;', {
+ innerHTML: "\r\r"
+ });
+ this.media = $('#media-embed', this.dialog);
+ $.one(d, '4chanXInitFinished', this.ready);
}
- if (Conf['Embedding'] || Conf['Link Title']) {
- this.embedProcess = Function('link', 'post', "var data = this.services(link);\nif (data) {" + ((Conf['Embedding'] ? 'this.embed(data); ' : '') + (Conf['Link Title'] ? 'data.push(post); this.title(data);' : '')) + "}");
+ if (Conf['Link Title']) {
+ return $.on(d, '4chanXInitFinished PostsInserted', function() {
+ var key, service, _ref1, _ref2;
+ _ref1 = Embedding.types;
+ for (key in _ref1) {
+ service = _ref1[key];
+ if ((_ref2 = service.title) != null ? _ref2.batchSize : void 0) {
+ Embedding.flushTitles(service.title);
+ }
+ }
+ });
}
- return Post.callbacks.push({
- name: 'Linkify',
- cb: this.node
- });
},
events: function(post) {
var el, i, items;
+ if (!Conf['Embedding']) {
+ return;
+ }
i = 0;
items = $$('.embedder', post.nodes.comment);
while (el = items[i++]) {
- $.on(el, 'click', Linkify.cb.toggle);
+ $.on(el, 'click', Embedding.cb.toggle);
if ($.hasClass(el, 'embedded')) {
- Linkify.cb.toggle.call(el);
+ Embedding.cb.toggle.call(el);
}
}
},
+ process: function(link, post) {
+ var data;
+ if (!(Conf['Embedding'] || Conf['Link Title'])) {
+ return;
+ }
+ if ($.x('ancestor::pre', link)) {
+ return;
+ }
+ if (data = Embedding.services(link)) {
+ data.post = post;
+ if (Conf['Embedding']) {
+ Embedding.embed(data);
+ }
+ if (Conf['Link Title']) {
+ return Embedding.title(data);
+ }
+ }
+ },
+ services: function(link) {
+ var href, match, type, _i, _len, _ref;
+ href = link.href;
+ _ref = Embedding.ordered_types;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ type = _ref[_i];
+ if (!(match = type.regExp.exec(href))) {
+ continue;
+ }
+ if (type.dummy || type.httpOnly && location.protocol !== 'http:') {
+ return;
+ }
+ return {
+ key: type.key,
+ uid: match[1],
+ options: match[2],
+ link: link
+ };
+ }
+ },
+ embed: function(data) {
+ var embed, key, link, name, options, post, uid, value, _ref;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ embed = $.el('a', {
+ className: 'embedder',
+ rel: 'nofollow noreferrer',
+ href: link.href,
+ textContent: '(embed)'
+ });
+ _ref = {
+ key: key,
+ uid: uid,
+ options: options
+ };
+ for (name in _ref) {
+ value = _ref[name];
+ embed.dataset[name] = value;
+ }
+ $.addClass(link, "" + embed.dataset.key);
+ $.on(embed, 'click', Embedding.cb.toggle);
+ $.after(link, [$.tn(' '), embed]);
+ if (Conf['Auto-embed'] && !Conf['Floating Embeds'] && !post.isFetchedQuote) {
+ return $.asap((function() {
+ return doc.contains(embed);
+ }), function() {
+ return Embedding.cb.toggle.call(embed);
+ });
+ }
+ },
+ ready: function() {
+ $.addClass(Embedding.dialog, 'empty');
+ $.on($('.close', Embedding.dialog), 'click', Embedding.closeFloat);
+ $.on($('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed);
+ $.on($('.jump', Embedding.dialog), 'click', function() {
+ if (doc.contains(Embedding.lastEmbed)) {
+ return Header.scrollTo(Embedding.lastEmbed);
+ }
+ });
+ return $.add(d.body, Embedding.dialog);
+ },
+ closeFloat: function() {
+ delete Embedding.lastEmbed;
+ $.addClass(Embedding.dialog, 'empty');
+ return $.replace(Embedding.media.firstChild, $.el('div'));
+ },
+ dragEmbed: function() {
+ var style;
+ style = Embedding.media.style;
+ if (Embedding.dragEmbed.mouseup) {
+ $.off(d, 'mouseup', Embedding.dragEmbed);
+ Embedding.dragEmbed.mouseup = false;
+ style.visibility = '';
+ return;
+ }
+ $.on(d, 'mouseup', Embedding.dragEmbed);
+ Embedding.dragEmbed.mouseup = true;
+ return style.visibility = 'hidden';
+ },
+ title: function(data) {
+ var key, link, options, post, service, uid;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ if (!(service = Embedding.types[key].title)) {
+ return;
+ }
+ if (service.batchSize) {
+ (service.queue || (service.queue = [])).push(data);
+ if (service.queue.length >= service.batchSize) {
+ return Embedding.flushTitles(service);
+ }
+ } else {
+ if (!$.cache(service.api(uid), (function() {
+ return Embedding.cb.title(this, data);
+ }), {
+ responseType: 'json'
+ })) {
+ return $.extend(link, {
+ innerHTML: "[" + E(key) + "] Title Link Blocked (are you using NoScript?)"
+ });
+ }
+ }
+ },
+ flushTitles: function(service) {
+ var cb, data, queue, _i, _len;
+ queue = service.queue;
+ if (!(queue != null ? queue.length : void 0)) {
+ return;
+ }
+ service.queue = [];
+ cb = function() {
+ var data, _i, _len;
+ for (_i = 0, _len = queue.length; _i < _len; _i++) {
+ data = queue[_i];
+ Embedding.cb.title(this, data);
+ }
+ };
+ if (!$.cache(service.api((function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = queue.length; _i < _len; _i++) {
+ data = queue[_i];
+ _results.push(data.uid);
+ }
+ return _results;
+ })()), cb, {
+ responseType: 'json'
+ })) {
+ for (_i = 0, _len = queue.length; _i < _len; _i++) {
+ data = queue[_i];
+ $.extend(data.link, {
+ innerHTML: "[" + E(data.key) + "] Title Link Blocked (are you using NoScript?)"
+ });
+ }
+ }
+ },
+ cb: {
+ toggle: function(e) {
+ var div;
+ if (e != null) {
+ e.preventDefault();
+ }
+ if (Conf['Floating Embeds']) {
+ if (!(div = Embedding.media.firstChild)) {
+ return;
+ }
+ $.replace(div, Embedding.cb.embed(this));
+ Embedding.lastEmbed = Get.postFromNode(this).nodes.root;
+ $.rmClass(Embedding.dialog, 'empty');
+ return;
+ }
+ if ($.hasClass(this, "embedded")) {
+ if (!$.hasClass(this.previousElementSibling, 'linkify')) {
+ $.rm(this.previousElementSibling);
+ }
+ this.previousElementSibling.hidden = false;
+ this.textContent = '(embed)';
+ } else {
+ this.previousElementSibling.hidden = true;
+ $.before(this, Embedding.cb.embed(this));
+ this.textContent = '(unembed)';
+ }
+ return $.toggleClass(this, 'embedded');
+ },
+ embed: function(a) {
+ var el, type;
+ el = (type = Embedding.types[a.dataset.key]).el(a);
+ el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px";
+ return el;
+ },
+ title: function(req, data) {
+ var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1;
+ key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post;
+ status = req.status;
+ service = Embedding.types[key].title;
+ text = "[" + key + "] " + ((function() {
+ switch (status) {
+ case 200:
+ case 304:
+ return service.text(req.response, uid);
+ case 404:
+ return "Not Found";
+ case 403:
+ return "Forbidden or Private";
+ default:
+ return "" + status + "'d";
+ }
+ })());
+ link.dataset.original = link.textContent;
+ link.textContent = text;
+ _ref = post.clones;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ post2 = _ref[_i];
+ _ref1 = $$('a.linkify', post2.nodes.comment);
+ for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
+ link2 = _ref1[_j];
+ if (!(link2.href === link.href)) {
+ continue;
+ }
+ link2.dataset.original = link2.textContent;
+ link2.textContent = text;
+ }
+ }
+ }
+ },
+ ordered_types: [
+ {
+ key: 'audio',
+ regExp: /\.(?:mp3|ogg|wav)(?:\?|$)/i,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: a.href
+ });
+ }
+ }, {
+ key: 'gist',
+ regExp: /^\w+:\/\/gist\.github\.com\/(?:[\w\-]+\/)?(\w+)/,
+ el: function(a) {
+ var content, el;
+ el = $.el('iframe');
+ el.setAttribute('sandbox', 'allow-scripts');
+ content = {
+ innerHTML: "" + E(a.dataset.uid) + ""
+ };
+ el.src = "data:text/html;charset=utf-8," + (encodeURIComponent(content.innerHTML));
+ return el;
+ },
+ title: {
+ api: function(uid) {
+ return "https://api.github.com/gists/" + uid;
+ },
+ text: function(_arg) {
+ var file, files;
+ files = _arg.files;
+ for (file in files) {
+ if (files.hasOwnProperty(file)) {
+ return file;
+ }
+ }
+ }
+ }
+ }, {
+ key: 'image',
+ regExp: /\.(?:gif|png|jpg|jpeg|bmp)(?:\?|$)/i,
+ style: '',
+ el: function(a) {
+ return $.el('div', {
+ innerHTML: "
"
+ });
+ }
+ }, {
+ key: 'InstallGentoo',
+ regExp: /^\w+:\/\/paste\.installgentoo\.com\/view\/(?:raw\/|download\/|embed\/)?(\w+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://paste.installgentoo.com/view/embed/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'Twitter',
+ regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'LiveLeak',
+ regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/,
+ httpOnly: true,
+ el: function(a) {
+ var el;
+ el = $.el('iframe', {
+ width: "640",
+ height: "360",
+ src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid,
+ frameborder: "0"
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ }
+ }, {
+ key: 'MediaCrush',
+ regExp: /^\w+:\/\/(?:www\.)?mediacru\.sh\/([\w\-]+)/,
+ style: '',
+ el: function(a) {
+ var el;
+ el = $.el('div');
+ $.queueTask(function() {
+ return $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() {
+ var embed, ext, file, files, i, status, type, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
+ if (!doc.contains(el)) {
+ return;
+ }
+ status = this.status;
+ if (status !== 200 && status !== 304) {
+ return el.textContent = "ERROR " + status;
+ }
+ files = this.response.files;
+ _ref = ['video/mp4', 'video/webm', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg'];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ type = _ref[_i];
+ for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
+ file = files[_j];
+ if (file.type === type) {
+ embed = file;
+ break;
+ }
+ }
+ if (embed) {
+ break;
+ }
+ }
+ if (!embed) {
+ return el.textContent = "ERROR: Not a valid filetype";
+ }
+ switch (embed.type) {
+ case 'video/mp4':
+ case 'video/webm':
+ case 'video/ogv':
+ $.extend(el, {
+ innerHTML: ""
+ });
+ _ref1 = ['mp4', 'webm'];
+ for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) {
+ ext = _ref1[i];
+ el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext;
+ }
+ break;
+ case 'image/svg+xml':
+ case 'image/png':
+ case 'image/gif':
+ case 'image/jpeg':
+ $.extend(el, {
+ innerHTML: "
"
+ });
+ break;
+ case 'audio/mpeg':
+ case 'audio/ogg':
+ $.extend(el, {
+ innerHTML: ""
+ });
+ break;
+ default:
+ el.textContent = "ERROR: No valid filetype.";
+ }
+ });
+ });
+ return el;
+ }
+ }, {
+ key: 'pastebin',
+ regExp: /^\w+:\/\/(?:\w+\.)?pastebin\.com\/(?!u\/)(?:[\w\.]+\?i\=)?(\w+)/,
+ httpOnly: true,
+ el: function(a) {
+ var div;
+ return div = $.el('iframe', {
+ src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'gfycat',
+ regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/,
+ el: function(a) {
+ var div;
+ return div = $.el('iframe', {
+ src: "//gfycat.com/iframe/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'SoundCloud',
+ regExp: /^\w+:\/\/(?:www\.)?(?:soundcloud\.com\/|snd\.sc\/)([\w\-\/]+)/,
+ style: 'border: 0; width: 500px; height: 400px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid))
+ });
+ },
+ title: {
+ api: function(uid) {
+ return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid));
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'StrawPoll',
+ regExp: /^\w+:\/\/(?:www\.)?strawpoll\.me\/(?:embed_\d+\/)?(\d+(?:\/r)?)/,
+ httpOnly: true,
+ style: 'border: 0; width: 600px; height: 406px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "http://strawpoll.me/embed_1/" + a.dataset.uid
+ });
+ }
+ }, {
+ key: 'TwitchTV',
+ regExp: /^\w+:\/\/(?:www\.)?twitch\.tv\/([^#\&\?]*)/,
+ httpOnly: true,
+ style: "border: none; width: 640px; height: 360px;",
+ el: function(a) {
+ var channel, id, idparam, obj, result, type, _;
+ if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) {
+ _ = result[0], channel = result[1], type = result[2], id = result[3];
+ idparam = {
+ 'b': 'archive_id',
+ 'c': 'chapter_id'
+ };
+ obj = $.el('object', {
+ data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
+ });
+ $.extend(obj, {
+ innerHTML: ""
+ });
+ obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id;
+ return obj;
+ } else {
+ channel = (/(\w+)/.exec(a.dataset.uid))[0];
+ obj = $.el('object', {
+ data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel
+ });
+ $.extend(obj, {
+ innerHTML: ""
+ });
+ obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25";
+ return obj;
+ }
+ }
+ }, {
+ key: 'Vocaroo',
+ regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: "http://vocaroo.com/media_command.php?media=" + a.dataset.uid + "&command=download_ogg"
+ });
+ }
+ }, {
+ key: 'Vimeo',
+ regExp: /^\w+:\/\/(?:www\.)?vimeo\.com\/(\d+)/,
+ el: function(a) {
+ return $.el('iframe', {
+ src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque"
+ });
+ },
+ title: {
+ api: function(uid) {
+ return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid;
+ },
+ text: function(_) {
+ return _.title;
+ }
+ }
+ }, {
+ key: 'Vine',
+ regExp: /^\w+:\/\/(?:www\.)?vine\.co\/v\/(\w+)/,
+ style: 'border: none; width: 500px; height: 500px;',
+ el: function(a) {
+ return $.el('iframe', {
+ src: "https://vine.co/v/" + a.dataset.uid + "/card"
+ });
+ }
+ }, {
+ key: 'YouTube',
+ regExp: /^\w+:\/\/(?:youtu.be\/|[\w\.]*youtube[\w\.]*\/.*(?:v=|\/embed\/|\/v\/|\/videos\/))([\w\-]{11})[^#\&\?]?(.*)/,
+ el: function(a) {
+ var el, start;
+ start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/);
+ if (start) {
+ start = start[1];
+ }
+ if (start && !/^\d+$/.test(start)) {
+ start += ' 0h0m0s';
+ start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1];
+ }
+ el = $.el('iframe', {
+ src: "//www.youtube.com/embed/" + a.dataset.uid + "?wmode=opaque" + (start ? '&start=' + start : '')
+ });
+ el.setAttribute("allowfullscreen", "true");
+ return el;
+ },
+ title: {
+ batchSize: 50,
+ api: function(uids) {
+ var ids, key;
+ ids = encodeURIComponent(uids.join(','));
+ key = '';
+ return "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=" + ids + "&fields=items%28id%2Csnippet%28title%29%29&key=" + key;
+ },
+ text: function(data, uid) {
+ var item, _i, _len, _ref;
+ _ref = data.items;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ item = _ref[_i];
+ if (item.id === uid) {
+ return item.snippet.title;
+ }
+ }
+ return 'Not Found';
+ }
+ }
+ }, {
+ key: 'Loopvid',
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/((?:pf|kd|lv|mc|gd|gh|db|nn)\/[\w\-]+(,[\w\-]+)*|fc\/\w+\/\d+)/,
+ style: 'max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ var base, el, host, name, names, type, types, url, _, _i, _j, _len, _len1, _ref, _ref1;
+ el = $.el('video', {
+ controls: true,
+ preload: 'auto',
+ loop: true
+ });
+ _ref = a.dataset.uid.match(/(\w+)\/(.*)/), _ = _ref[0], host = _ref[1], names = _ref[2];
+ types = host === 'gd' || host === 'fc' ? [''] : ['.webm', '.mp4'];
+ _ref1 = names.split(',');
+ for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
+ name = _ref1[_i];
+ for (_j = 0, _len1 = types.length; _j < _len1; _j++) {
+ type = types[_j];
+ base = "" + name + type;
+ url = (function() {
+ switch (host) {
+ case 'pf':
+ return "http://a.pomf.se/" + base;
+ case 'kd':
+ return "http://kastden.org/loopvid/" + base;
+ case 'lv':
+ return "http://loopvid.mooo.com/videos/" + base;
+ case 'mc':
+ return "https://cdn.mediacru.sh/" + base;
+ case 'gd':
+ return "https://docs.google.com/uc?export=download&id=" + base;
+ case 'gh':
+ return "https://googledrive.com/host/" + base;
+ case 'db':
+ return "https://googledrive.com/host/" + base;
+ case 'fc':
+ return "//i.4cdn.org/" + base + ".webm";
+ case 'nn':
+ return "http://naenara.eu/loopvids/" + base;
+ }
+ })();
+ $.add(el, $.el('source', {
+ src: url
+ }));
+ }
+ }
+ return el;
+ }
+ }, {
+ key: 'Clyp',
+ regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/,
+ style: '',
+ el: function(a) {
+ return $.el('audio', {
+ controls: true,
+ preload: 'auto',
+ src: "http://clyp.it/" + a.dataset.uid + ".ogg"
+ });
+ }
+ }, {
+ key: 'Loopvid-dummy',
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\//,
+ dummy: true
+ }, {
+ key: 'MediaFire-dummy',
+ regExp: /^\w+:\/\/(?:www\.)?mediafire.com\//,
+ dummy: true
+ }, {
+ key: 'video',
+ regExp: /\.(?:ogv|webm|mp4)(?:\?|$)/i,
+ style: 'max-width: 80vw; max-height: 80vh;',
+ el: function(a) {
+ return $.el('video', {
+ controls: true,
+ preload: 'auto',
+ src: a.href
+ });
+ }
+ }
+ ]
+ };
+
+ Linkify = {
+ init: function() {
+ var _ref;
+ if (((_ref = g.VIEW) !== 'index' && _ref !== 'thread') || !Conf['Linkify']) {
+ return;
+ }
+ if (Conf['Comment Expansion']) {
+ ExpandComment.callbacks.push(this.node);
+ }
+ Post.callbacks.push({
+ name: 'Linkify',
+ cb: this.node
+ });
+ CatalogThread.callbacks.push({
+ name: 'Linkify',
+ cb: this.catalogNode
+ });
+ return Embedding.init();
+ },
node: function() {
- var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word;
+ var link, links, _i, _len;
if (this.isClone) {
- return (Conf['Embedding'] ? Linkify.events(this) : null);
+ return Embedding.events(this);
}
if (!Linkify.regString.test(this.info.comment)) {
return;
}
+ links = Linkify.process(this.nodes.comment);
+ for (_i = 0, _len = links.length; _i < _len; _i++) {
+ link = links[_i];
+ Embedding.process(link, this);
+ }
+ },
+ catalogNode: function() {
+ if (!Linkify.regString.test(this.thread.OP.info.comment)) {
+ return;
+ }
+ return Linkify.process(this.nodes.comment);
+ },
+ process: function(node) {
+ var data, end, endNode, i, index, length, links, result, saved, snapshot, space, test, word;
test = /[^\s'"]+/g;
space = /[\s'"]/;
- snapshot = $.X('.//br|.//text()', this.nodes.comment);
+ snapshot = $.X('.//br|.//text()', node);
i = 0;
links = [];
while (node = snapshot.snapshotItem(i++)) {
@@ -11718,16 +12368,18 @@
}
endNode = saved;
data = saved.data;
- word += data;
- length = data.length;
if (end = space.exec(data)) {
+ word += data.slice(0, end.index);
test.lastIndex = length = end.index;
i--;
break;
+ } else {
+ length = data.length;
+ word += data;
}
}
}
- if (Linkify.regString.exec(word)) {
+ if (Linkify.regString.test(word)) {
links.push(Linkify.makeRange(node, endNode, index, length));
}
if (!(test.lastIndex && node === endNode)) {
@@ -11737,12 +12389,11 @@
}
i = links.length;
while (i--) {
- link = links[i];
- Linkify.embedProcess(Linkify.makeLink(link), this);
+ links[i] = Linkify.makeLink(links[i]);
}
+ return links;
},
- embedProcess: function() {},
- regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i,
+ regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/?])|([-a-z\d]+[.])+(aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?![^\s'"]))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i,
makeRange: function(startNode, endNode, startOffset, endOffset) {
var range;
range = document.createRange();
@@ -11753,11 +12404,8 @@
makeLink: function(range) {
var a, i, t, text;
text = range.toString();
- i = 0;
- while (/[(\[{<>]/.test(text.charAt(i))) {
- i++;
- }
- if (i) {
+ i = text.search(Linkify.regString);
+ if (i > 0) {
text = text.slice(i);
while (range.startOffset + i >= range.startContainer.data.length) {
i--;
@@ -11782,7 +12430,7 @@
range.setEnd(range.endContainer, range.endOffset - i);
}
}
- if (!/(mailto:|.+:\/\/)/.test(text)) {
+ if (!/((mailto|magnet):|.+:\/\/)/.test(text)) {
text = (/@/.test(text) ? 'mailto:' : 'http://') + text;
}
a = $.el('a', {
@@ -11795,414 +12443,7 @@
range.insertNode(a);
range.detach();
return a;
- },
- services: function(link) {
- var href, match, type, _i, _len, _ref;
- href = link.href;
- _ref = Linkify.ordered_types;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- type = _ref[_i];
- if (!(match = type.regExp.exec(href))) {
- continue;
- }
- if (type.dummy) {
- return;
- }
- return [type.key, match[1], match[2], link];
- }
- },
- embed: function(data) {
- var embed, href, key, link, name, options, post, uid, value, _ref;
- key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
- href = link.href;
- embed = $.el('a', {
- className: 'embedder',
- href: 'javascript:;',
- textContent: '(embed)'
- });
- _ref = {
- key: key,
- href: href,
- uid: uid,
- options: options
- };
- for (name in _ref) {
- value = _ref[name];
- embed.dataset[name] = value;
- }
- $.addClass(link, "" + embed.dataset.key);
- $.on(embed, 'click', Linkify.cb.toggle);
- $.after(link, [$.tn(' '), embed]);
- if (Conf['Auto-embed']) {
- return Linkify.cb.toggle.call(embed);
- }
- },
- title: function(data) {
- var err, key, link, options, post, service, title, titles, uid;
- key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
- if (!(service = Linkify.types[key].title)) {
- return;
- }
- titles = Conf['CachedTitles'];
- if (title = titles[uid]) {
- return link.textContent = title[0];
- } else {
- try {
- return $.cache(service.api(uid), (function() {
- return Linkify.cb.title(this, data);
- }), {
- responseType: 'json'
- });
- } catch (_error) {
- err = _error;
- link.innerHTML = 'Title Link Blocked (are you using NoScript?)';
- $.prepend(link, $.tn("[" + key + "] "));
- }
- }
- },
- cb: {
- toggle: function() {
- if ($.hasClass(this, "embedded")) {
- $.rm(this.previousElementSibling);
- this.previousElementSibling.hidden = false;
- this.textContent = '(embed)';
- } else {
- this.previousElementSibling.hidden = true;
- $.before(this, Linkify.cb.embed(this));
- this.textContent = '(unembed)';
- }
- return $.toggleClass(this, 'embedded');
- },
- embed: function(a) {
- var el, type;
- el = (type = Linkify.types[a.dataset.key]).el(a);
- el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px";
- return el;
- },
- title: function(req, data) {
- var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1;
- key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4];
- status = req.status;
- service = Linkify.types[key].title;
- text = "[" + key + "] " + ((function() {
- switch (status) {
- case 200:
- case 304:
- return service.text(req.response);
- case 404:
- return "Not Found";
- case 403:
- return "Forbidden or Private";
- default:
- return "" + status + "'d";
- }
- })());
- link.textContent = text;
- _ref = post.clones;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- post2 = _ref[_i];
- _ref1 = $$('a', post2.nodes.comment);
- for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
- link2 = _ref1[_j];
- if (link2.href === link.href) {
- link2.textContent = text;
- }
- }
- }
- }
- },
- ordered_types: [
- {
- key: 'audio',
- regExp: /(.*\.(mp3|ogg|wav))$/,
- style: '',
- el: function(a) {
- return $.el('audio', {
- controls: true,
- preload: 'auto',
- src: a.dataset.uid
- });
- }
- }, {
- key: 'gist',
- regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/,
- el: function(a) {
- var div;
- return div = $.el('iframe', {
- src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js"
- });
- },
- title: {
- api: function(uid) {
- return "https://api.github.com/gists/" + uid;
- },
- text: function(_arg) {
- var file, files;
- files = _arg.files;
- for (file in files) {
- if (files.hasOwnProperty(file)) {
- return file;
- }
- }
- }
- }
- }, {
- key: 'image',
- regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/,
- style: 'border: 0; width: auto; height: auto;',
- el: function(a) {
- var el;
- el = $.el('div');
- el.innerHTML = '
';
- el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href;
- return el;
- }
- }, {
- key: 'InstallGentoo',
- regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/,
- el: function(a) {
- return $.el('iframe', {
- src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid
- });
- }
- }, {
- key: 'Twitter',
- regExp: /.*twitter.com\/(.+\/status\/\d+)/,
- el: function(a) {
- return $.el('iframe', {
- src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid
- });
- }
- }, {
- key: 'LiveLeak',
- regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/,
- el: function(a) {
- var el;
- el = $.el('iframe', {
- width: "640",
- height: "360",
- src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid,
- frameborder: "0"
- });
- el.setAttribute("allowfullscreen", "true");
- return el;
- }
- }, {
- key: 'MediaCrush',
- regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i,
- style: 'border: 0;',
- el: function(a) {
- var el;
- el = $.el('div');
- $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() {
- var embed, ext, file, files, i, status, type, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _results, _results1;
- status = this.status;
- if (status !== 200 && status !== 304) {
- return el.textContent = "ERROR " + status;
- }
- files = this.response.files;
- _ref = ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg'];
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- type = _ref[_i];
- for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
- file = files[_j];
- if (file.type === type) {
- embed = file;
- break;
- }
- }
- if (embed) {
- break;
- }
- }
- if (!embed) {
- return div.textContent = "ERROR: Not a valid filetype";
- }
- switch (embed.type) {
- case 'video/mp4':
- case 'video/webm':
- case 'video/ogv':
- el.innerHTML = '';
- _ref1 = ['mp4', 'webm', 'ogv'];
- _results = [];
- for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) {
- ext = _ref1[i];
- _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext);
- }
- return _results;
- break;
- case 'image/svg+xml':
- case 'image/png':
- case 'image/gif':
- case 'image/jpeg':
- el.innerHTML = '
';
- el.firstChild.href = a.dataset.href;
- return el.firstChild.firstChild.src = "https://mediacru.sh/" + file.file;
- case 'audio/mpeg':
- case 'audio/ogg':
- el.innerHTML = '';
- _ref2 = ['ogg', 'mp3'];
- _results1 = [];
- for (i = _l = 0, _len3 = _ref2.length; _l < _len3; i = ++_l) {
- ext = _ref2[i];
- _results1.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext);
- }
- return _results1;
- break;
- default:
- return el.textContent = "ERROR: No valid filetype.";
- }
- });
- return el;
- }
- }, {
- key: 'pastebin',
- regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/,
- el: function(a) {
- var div;
- return div = $.el('iframe', {
- src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid
- });
- }
- }, {
- key: 'gfycat',
- regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/,
- el: function(a) {
- var div;
- return div = $.el('iframe', {
- src: "http://gfycat.com/iframe/" + a.dataset.uid
- });
- }
- }, {
- key: 'SoundCloud',
- regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/,
- style: 'border: 0; width: 500px; height: 400px;',
- el: function(a) {
- return $.el('iframe', {
- src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid))
- });
- },
- title: {
- api: function(uid) {
- return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid));
- },
- text: function(_) {
- return _.title;
- }
- }
- }, {
- key: 'StrawPoll',
- regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/,
- style: 'border: 0; width: 600px; height: 406px;',
- el: function(a) {
- return $.el('iframe', {
- src: "http://strawpoll.me/embed_1/" + a.dataset.uid
- });
- }
- }, {
- key: 'TwitchTV',
- regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/,
- style: "border: none; width: 640px; height: 360px;",
- el: function(a) {
- var channel, id, idparam, obj, result, type, _;
- if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) {
- _ = result[0], channel = result[1], type = result[2], id = result[3];
- idparam = {
- 'b': 'archive_id',
- 'c': 'chapter_id'
- };
- obj = $.el('object', {
- data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
- });
- obj.innerHTML = '';
- obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id;
- return obj;
- } else {
- channel = (/(\w+)/.exec(a.dataset.uid))[0];
- obj = $.el('object', {
- data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel
- });
- obj.innerHTML = '';
- obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25";
- return obj;
- }
- }
- }, {
- key: 'Vocaroo',
- regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/,
- style: '',
- el: function(a) {
- return $.el('audio', {
- controls: true,
- preload: 'auto',
- src: "http://vocaroo.com/media_command.php?media=" + (a.dataset.uid.replace(/^i\//, '')) + "&command=download_ogg"
- });
- }
- }, {
- key: 'Vimeo',
- regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/,
- el: function(a) {
- return $.el('iframe', {
- src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque"
- });
- },
- title: {
- api: function(uid) {
- return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid;
- },
- text: function(_) {
- return _.title;
- }
- }
- }, {
- key: 'Vine',
- regExp: /.*(?:vine.co\/)([^#\&\?]*).*/,
- style: 'border: none; width: 500px; height: 500px;',
- el: function(a) {
- return $.el('iframe', {
- src: "https://vine.co/" + a.dataset.uid + "/card"
- });
- }
- }, {
- key: 'YouTube',
- regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/,
- el: function(a) {
- var el;
- el = $.el('iframe', {
- src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque"
- });
- el.setAttribute("allowfullscreen", "true");
- return el;
- },
- title: {
- api: function(uid) {
- return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode";
- },
- text: function(data) {
- return data.entry.title.$t;
- }
- }
- }, {
- key: 'Loopvid',
- regExp: /.*loopvid.appspot.com\/.*/,
- dummy: true
- }, {
- key: 'MediaFire',
- regExp: /.*mediafire.com\/.*/,
- dummy: true
- }, {
- key: 'video',
- regExp: /(.*\.(ogv|webm|mp4))$/,
- style: 'border: 0; width: auto; height: auto;',
- el: function(a) {
- return $.el('video', {
- controls: 'controls',
- preload: 'auto',
- src: a.dataset.uid
- });
- }
- }
- ]
+ }
};
ArchiveLink = {
diff --git a/src/General/html/Features/Embed.html b/src/General/html/Features/Embed.html
new file mode 100644
index 000000000..dbf93db1f
--- /dev/null
+++ b/src/General/html/Features/Embed.html
@@ -0,0 +1,6 @@
+
+
diff --git a/src/Linkification/Embedding.coffee b/src/Linkification/Embedding.coffee
new file mode 100644
index 000000000..16c4dca99
--- /dev/null
+++ b/src/Linkification/Embedding.coffee
@@ -0,0 +1,402 @@
+Embedding =
+ init: ->
+ return unless Conf['Embedding'] or Conf['Link Title']
+ @types = {}
+ @types[type.key] = type for type in @ordered_types
+
+ if Conf['Floating Embeds']
+ @dialog = UI.dialog 'embedding', 'top: 50px; right: 0px;',
+ <%= importHTML('Features/Embed') %>
+ @media = $ '#media-embed', @dialog
+ $.one d, '4chanXInitFinished', @ready
+
+ if Conf['Link Title']
+ $.on d, '4chanXInitFinished PostsInserted', ->
+ for key, service of Embedding.types when service.title?.batchSize
+ Embedding.flushTitles service.title
+ return
+
+ events: (post) ->
+ return unless Conf['Embedding']
+ i = 0
+ items = $$ '.embedder', post.nodes.comment
+ while el = items[i++]
+ $.on el, 'click', Embedding.cb.toggle
+ Embedding.cb.toggle.call el if $.hasClass el, 'embedded'
+ return
+
+ process: (link, post) ->
+ return unless Conf['Embedding'] or Conf['Link Title']
+ return if $.x 'ancestor::pre', link
+ if data = Embedding.services link
+ data.post = post
+ Embedding.embed data if Conf['Embedding']
+ Embedding.title data if Conf['Link Title']
+
+ services: (link) ->
+ {href} = link
+ for type in Embedding.ordered_types when match = type.regExp.exec href
+ return if type.dummy or type.httpOnly and location.protocol isnt 'http:'
+ return {key: type.key, uid: match[1], options: match[2], link}
+ return
+
+ embed: (data) ->
+ {key, uid, options, link, post} = data
+ embed = $.el 'a',
+ className: 'embedder'
+ rel: 'nofollow noreferrer'
+ href: link.href
+ textContent: '(embed)'
+
+ embed.dataset[name] = value for name, value of {key, uid, options}
+
+ $.addClass link, "#{embed.dataset.key}"
+
+ $.on embed, 'click', Embedding.cb.toggle
+ $.after link, [$.tn(' '), embed]
+
+ if Conf['Auto-embed'] and !Conf['Floating Embeds'] and !post.isFetchedQuote
+ $.asap (-> doc.contains embed), ->
+ Embedding.cb.toggle.call embed
+
+ ready: ->
+ $.addClass Embedding.dialog, 'empty'
+ $.on $('.close', Embedding.dialog), 'click', Embedding.closeFloat
+ $.on $('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed
+ $.on $('.jump', Embedding.dialog), 'click', ->
+ Header.scrollTo Embedding.lastEmbed if doc.contains Embedding.lastEmbed
+ $.add d.body, Embedding.dialog
+
+ closeFloat: ->
+ delete Embedding.lastEmbed
+ $.addClass Embedding.dialog, 'empty'
+ $.replace Embedding.media.firstChild, $.el 'div'
+
+ dragEmbed: ->
+ # only webkit can handle a blocking div
+ {style} = Embedding.media
+ if Embedding.dragEmbed.mouseup
+ $.off d, 'mouseup', Embedding.dragEmbed
+ Embedding.dragEmbed.mouseup = false
+ style.visibility = ''
+ return
+ $.on d, 'mouseup', Embedding.dragEmbed
+ Embedding.dragEmbed.mouseup = true
+ style.visibility = 'hidden'
+
+ title: (data) ->
+ {key, uid, options, link, post} = data
+ return unless service = Embedding.types[key].title
+ if service.batchSize
+ (service.queue or= []).push data
+ if service.queue.length >= service.batchSize
+ Embedding.flushTitles service
+ else
+ unless $.cache service.api(uid), (-> Embedding.cb.title @, data), {responseType: 'json'}
+ $.extend link, <%= html('[${key}] Title Link Blocked (are you using NoScript?)') %>
+
+ flushTitles: (service) ->
+ {queue} = service
+ return unless queue?.length
+ service.queue = []
+ cb = ->
+ Embedding.cb.title @, data for data in queue
+ return
+ unless $.cache service.api(data.uid for data in queue), cb, {responseType: 'json'}
+ for data in queue
+ $.extend data.link, <%= html('[${data.key}] Title Link Blocked (are you using NoScript?)') %>
+ return
+
+ cb:
+ toggle: (e) ->
+ e?.preventDefault()
+ if Conf['Floating Embeds']
+ return unless div = Embedding.media.firstChild
+ $.replace div, Embedding.cb.embed @
+ Embedding.lastEmbed = Get.postFromNode(@).nodes.root
+ $.rmClass Embedding.dialog, 'empty'
+ return
+ if $.hasClass @, "embedded"
+ $.rm @previousElementSibling unless $.hasClass @previousElementSibling, 'linkify'
+ @previousElementSibling.hidden = false
+ @textContent = '(embed)'
+ else
+ @previousElementSibling.hidden = true
+ $.before @, Embedding.cb.embed @
+ @textContent = '(unembed)'
+ $.toggleClass @, 'embedded'
+
+ embed: (a) ->
+ # We create an element to embed
+ el = (type = Embedding.types[a.dataset.key]).el a
+
+ # Set style values.
+ el.style.cssText = if type.style?
+ type.style
+ else
+ "border: 0; width: 640px; height: 390px"
+
+ return el
+
+ title: (req, data) ->
+ {key, uid, options, link, post} = data
+ {status} = req
+ service = Embedding.types[key].title
+
+ text = "[#{key}] #{switch status
+ when 200, 304
+ service.text req.response, uid
+ when 404
+ "Not Found"
+ when 403
+ "Forbidden or Private"
+ else
+ "#{status}'d"
+ }"
+
+ link.dataset.original = link.textContent
+ link.textContent = text
+ for post2 in post.clones
+ for link2 in $$ 'a.linkify', post2.nodes.comment when link2.href is link.href
+ link2.dataset.original = link2.textContent
+ link2.textContent = text
+ return
+
+ ordered_types: [
+ key: 'audio'
+ regExp: /\.(?:mp3|ogg|wav)(?:\?|$)/i
+ style: ''
+ el: (a) ->
+ $.el 'audio',
+ controls: true
+ preload: 'auto'
+ src: a.href
+ ,
+ key: 'gist'
+ regExp: /^\w+:\/\/gist\.github\.com\/(?:[\w\-]+\/)?(\w+)/
+ el: (a) ->
+ el = $.el 'iframe'
+ el.setAttribute 'sandbox', 'allow-scripts'
+ content = <%= html('${a.dataset.uid}') %>
+ el.src = "data:text/html;charset=utf-8,#{encodeURIComponent content.innerHTML}"
+ el
+ title:
+ api: (uid) -> "https://api.github.com/gists/#{uid}"
+ text: ({files}) ->
+ return file for file of files when files.hasOwnProperty file
+ ,
+ key: 'image'
+ regExp: /\.(?:gif|png|jpg|jpeg|bmp)(?:\?|$)/i
+ style: ''
+ el: (a) ->
+ $.el 'div', <%= html('
') %>
+ ,
+ key: 'InstallGentoo'
+ regExp: /^\w+:\/\/paste\.installgentoo\.com\/view\/(?:raw\/|download\/|embed\/)?(\w+)/
+ el: (a) ->
+ $.el 'iframe',
+ src: "https://paste.installgentoo.com/view/embed/#{a.dataset.uid}"
+ ,
+ key: 'Twitter'
+ regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/
+ el: (a) ->
+ $.el 'iframe',
+ src: "https://twitframe.com/show?url=https://twitter.com/#{a.dataset.uid}"
+ ,
+ key: 'LiveLeak'
+ regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/
+ httpOnly: true
+ el: (a) ->
+ el = $.el 'iframe',
+ width: "640",
+ height: "360",
+ src: "http://www.liveleak.com/ll_embed?i=#{a.dataset.uid}",
+ frameborder: "0"
+ el.setAttribute "allowfullscreen", "true"
+ el
+ ,
+ key: 'MediaCrush'
+ regExp: /^\w+:\/\/(?:www\.)?mediacru\.sh\/([\w\-]+)/
+ style: ''
+ el: (a) ->
+ el = $.el 'div'
+ $.queueTask -> $.cache "https://mediacru.sh/#{a.dataset.uid}.json", ->
+ return unless doc.contains el
+ {status} = @
+ return el.textContent = "ERROR #{status}" unless status in [200, 304]
+ {files} = @response
+ for type in ['video/mp4', 'video/webm', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg']
+ for file in files
+ if file.type is type
+ embed = file
+ break
+ break if embed
+ return el.textContent = "ERROR: Not a valid filetype" unless embed
+ switch embed.type
+ when 'video/mp4', 'video/webm', 'video/ogv'
+ $.extend el, <%= html('') %>
+ for ext, i in ['mp4', 'webm']
+ el.firstChild.children[i].src = "https://mediacru.sh/#{a.dataset.uid}.#{ext}"
+ when 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'
+ $.extend el, <%= html('
') %>
+ when 'audio/mpeg', 'audio/ogg'
+ $.extend el, <%= html('') %>
+ else
+ el.textContent = "ERROR: No valid filetype."
+ return
+ el
+ ,
+ key: 'pastebin'
+ regExp: /^\w+:\/\/(?:\w+\.)?pastebin\.com\/(?!u\/)(?:[\w\.]+\?i\=)?(\w+)/
+ httpOnly: true
+ el: (a) ->
+ div = $.el 'iframe',
+ src: "http://pastebin.com/embed_iframe.php?i=#{a.dataset.uid}"
+ ,
+ key: 'gfycat'
+ regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/
+ el: (a) ->
+ div = $.el 'iframe',
+ src: "//gfycat.com/iframe/#{a.dataset.uid}"
+ ,
+ key: 'SoundCloud'
+ regExp: /^\w+:\/\/(?:www\.)?(?:soundcloud\.com\/|snd\.sc\/)([\w\-\/]+)/
+ style: 'border: 0; width: 500px; height: 400px;'
+ el: (a) ->
+ $.el 'iframe',
+ src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent a.dataset.uid}"
+ title:
+ api: (uid) -> "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent uid}"
+ text: (_) -> _.title
+ ,
+ key: 'StrawPoll'
+ regExp: /^\w+:\/\/(?:www\.)?strawpoll\.me\/(?:embed_\d+\/)?(\d+(?:\/r)?)/
+ httpOnly: true
+ style: 'border: 0; width: 600px; height: 406px;'
+ el: (a) ->
+ $.el 'iframe',
+ src: "http://strawpoll.me/embed_1/#{a.dataset.uid}"
+ ,
+ key: 'TwitchTV'
+ regExp: /^\w+:\/\/(?:www\.)?twitch\.tv\/([^#\&\?]*)/
+ httpOnly: true
+ style: "border: none; width: 640px; height: 360px;"
+ el: (a) ->
+ if result = /(\w+)\/([bc])\/(\d+)/i.exec a.dataset.uid
+ [_, channel, type, id] = result
+ idparam = {'b': 'archive_id', 'c': 'chapter_id'}
+ obj = $.el 'object',
+ data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
+ $.extend obj, <%= html('') %>
+ obj.children[1].value = "channel=#{channel}&start_volume=25&auto_play=false{idparam[type]}=#{id}"
+ obj
+ else
+ channel = (/(\w+)/.exec a.dataset.uid)[0]
+ obj = $.el 'object',
+ data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=#{channel}"
+ $.extend obj, <%= html('') %>
+ obj.children[1].value = "hostname=www.twitch.tv&channel=#{channel}&auto_play=true&start_volume=25"
+ obj
+ ,
+ key: 'Vocaroo'
+ regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/
+ style: ''
+ el: (a) ->
+ $.el 'audio',
+ controls: true
+ preload: 'auto'
+ src: "http://vocaroo.com/media_command.php?media=#{a.dataset.uid}&command=download_ogg"
+ ,
+ key: 'Vimeo'
+ regExp: /^\w+:\/\/(?:www\.)?vimeo\.com\/(\d+)/
+ el: (a) ->
+ $.el 'iframe',
+ src: "//player.vimeo.com/video/#{a.dataset.uid}?wmode=opaque"
+ title:
+ api: (uid) -> "https://vimeo.com/api/oembed.json?url=http://vimeo.com/#{uid}"
+ text: (_) -> _.title
+ ,
+ key: 'Vine'
+ regExp: /^\w+:\/\/(?:www\.)?vine\.co\/v\/(\w+)/
+ style: 'border: none; width: 500px; height: 500px;'
+ el: (a) ->
+ $.el 'iframe',
+ src: "https://vine.co/v/#{a.dataset.uid}/card"
+ ,
+ key: 'YouTube'
+ regExp: /^\w+:\/\/(?:youtu.be\/|[\w\.]*youtube[\w\.]*\/.*(?:v=|\/embed\/|\/v\/|\/videos\/))([\w\-]{11})[^#\&\?]?(.*)/
+ el: (a) ->
+ start = a.dataset.options.match /\b(?:star)?t\=(\w+)/
+ start = start[1] if start
+ if start and !/^\d+$/.test start
+ start += ' 0h0m0s'
+ start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1]
+ el = $.el 'iframe',
+ src: "//www.youtube.com/embed/#{a.dataset.uid}?wmode=opaque#{if start then '&start=' + start else ''}"
+ el.setAttribute "allowfullscreen", "true"
+ el
+ title:
+ batchSize: 50
+ api: (uids) ->
+ ids = encodeURIComponent uids.join(',')
+ key = '<%= meta.youtubeAPIKey %>'
+ "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=#{ids}&fields=items%28id%2Csnippet%28title%29%29&key=#{key}"
+ text: (data, uid) ->
+ for item in data.items when item.id is uid
+ return item.snippet.title
+ 'Not Found'
+ ,
+ key: 'Loopvid'
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/((?:pf|kd|lv|mc|gd|gh|db|nn)\/[\w\-]+(,[\w\-]+)*|fc\/\w+\/\d+)/
+ style: 'max-width: 80vw; max-height: 80vh;'
+ el: (a) ->
+ el = $.el 'video',
+ controls: true
+ preload: 'auto'
+ loop: true
+ [_, host, names] = a.dataset.uid.match /(\w+)\/(.*)/
+ types = if host in ['gd', 'fc'] then [''] else ['.webm', '.mp4']
+ for name in names.split ','
+ for type in types
+ base = "#{name}#{type}"
+ url = switch host
+ # list from src/loopvid.py at http://loopvid.appspot.com/source.html
+ when 'pf' then "http://a.pomf.se/#{base}"
+ when 'kd' then "http://kastden.org/loopvid/#{base}"
+ when 'lv' then "http://loopvid.mooo.com/videos/#{base}"
+ when 'mc' then "https://cdn.mediacru.sh/#{base}"
+ when 'gd' then "https://docs.google.com/uc?export=download&id=#{base}"
+ when 'gh' then "https://googledrive.com/host/#{base}"
+ when 'db' then "https://googledrive.com/host/#{base}"
+ when 'fc' then "//i.4cdn.org/#{base}.webm"
+ when 'nn' then "http://naenara.eu/loopvids/#{base}"
+ $.add el, $.el 'source', src: url
+ el
+ ,
+ key: 'Clyp'
+ regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/
+ style: ''
+ el: (a) ->
+ $.el 'audio',
+ controls: true
+ preload: 'auto'
+ src: "http://clyp.it/#{a.dataset.uid}.ogg"
+ ,
+ # dummy entries: not implemented but included to prevent them being wrongly embedded as a subsequent type
+ key: 'Loopvid-dummy'
+ regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\//
+ dummy: true
+ ,
+ key: 'MediaFire-dummy'
+ regExp: /^\w+:\/\/(?:www\.)?mediafire.com\//
+ dummy: true
+ ,
+ key: 'video'
+ regExp: /\.(?:ogv|webm|mp4)(?:\?|$)/i
+ style: 'max-width: 80vw; max-height: 80vh;'
+ el: (a) ->
+ $.el 'video',
+ controls: true
+ preload: 'auto'
+ src: a.href
+ ]
diff --git a/src/Linkification/Linkify.coffee b/src/Linkification/Linkify.coffee
index df965c0d8..faa9a99f5 100755
--- a/src/Linkification/Linkify.coffee
+++ b/src/Linkification/Linkify.coffee
@@ -1,42 +1,35 @@
Linkify =
init: ->
- return if !Conf['Linkify']
-
- @types = {}
- @types[type.key] = type for type in @ordered_types
+ return if g.VIEW not in ['index', 'thread'] or not Conf['Linkify']
if Conf['Comment Expansion']
ExpandComment.callbacks.push @node
- if Conf['Embedding'] or Conf['Link Title']
- @embedProcess = Function 'link', 'post',
- """
- var data = this.services(link);
- if (data) {#{
- (if Conf['Embedding'] then 'this.embed(data); ' else '') +
- if Conf['Link Title'] then 'data.push(post); this.title(data);' else ''
- }}
- """
-
Post.callbacks.push
name: 'Linkify'
cb: @node
- events: (post) ->
- i = 0
- items = $$ '.embedder', post.nodes.comment
- while el = items[i++]
- $.on el, 'click', Linkify.cb.toggle
- Linkify.cb.toggle.call el if $.hasClass el, 'embedded'
- return
+ CatalogThread.callbacks.push
+ name: 'Linkify'
+ cb: @catalogNode
+
+ Embedding.init()
node: ->
- return (if Conf['Embedding'] then Linkify.events @ else null) if @isClone
+ return Embedding.events @ if @isClone
return unless Linkify.regString.test @info.comment
+ links = Linkify.process @nodes.comment
+ Embedding.process link, @ for link in links
+ return
+ catalogNode: ->
+ return unless Linkify.regString.test @thread.OP.info.comment
+ Linkify.process @nodes.comment
+
+ process: (node) ->
test = /[^\s'"]+/g
space = /[\s'"]/
- snapshot = $.X './/br|.//text()', @nodes.comment
+ snapshot = $.X './/br|.//text()', node
i = 0
links = []
while node = snapshot.snapshotItem i++
@@ -57,36 +50,37 @@ Linkify =
endNode = saved
{data} = saved
- word += data
- {length} = data
if end = space.exec data
# Set our snapshot and regex to start on this node at this position when the loop resumes
+ word += data[...end.index]
test.lastIndex = length = end.index
i--
break
+ else
+ {length} = data
+ word += data
- links.push Linkify.makeRange node, endNode, index, length if Linkify.regString.exec word
+ if Linkify.regString.test word
+ links.push Linkify.makeRange node, endNode, index, length
+ <%= assert('word is links[links.length-1].toString()') %>
break unless test.lastIndex and node is endNode
i = links.length
while i--
- link = links[i]
- Linkify.embedProcess (Linkify.makeLink link), @
- return
-
- embedProcess: -> return
+ links[i] = Linkify.makeLink links[i]
+ links
regString: ///(
# http, magnet, ftp, etc
(https?|mailto|git|magnet|ftp|irc):(
- [a-z\d%/]
+ [a-z\d%/?]
)
| # This should account for virtually all links posted without http:
- [-a-z\d]+[.](
- aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2}
- )([:/]|(?!.))
+ ([-a-z\d]+[.])+(
+ aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2}
+ )([:/]|(?![^\s'"]))
| # IPv4 Addresses
[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}
| # E-mails
@@ -103,10 +97,9 @@ Linkify =
text = range.toString()
# Clean start of range
- i = 0
- i++ while /[(\[{<>]/.test text.charAt i
+ i = text.search Linkify.regString
- if i
+ if i > 0
text = text.slice i
i-- while range.startOffset + i >= range.startContainer.data.length
@@ -126,7 +119,7 @@ Linkify =
range.setEnd range.endContainer, range.endOffset - i
# Make our link 'valid' if it is formatted incorrectly.
- unless /(mailto:|.+:\/\/)/.test text
+ unless /((mailto|magnet):|.+:\/\/)/.test text
text = (
if /@/.test text
'mailto:'
@@ -146,278 +139,3 @@ Linkify =
range.detach()
a
-
- services: (link) ->
- {href} = link
- for type in Linkify.ordered_types when match = type.regExp.exec href
- return if type.dummy
- return [type.key, match[1], match[2], link]
- return
-
- embed: (data) ->
- [key, uid, options, link, post] = data
- href = link.href
- embed = $.el 'a',
- className: 'embedder'
- href: 'javascript:;'
- textContent: '(embed)'
-
- embed.dataset[name] = value for name, value of {key, href, uid, options}
-
- $.addClass link, "#{embed.dataset.key}"
-
- $.on embed, 'click', Linkify.cb.toggle
- $.after link, [$.tn(' '), embed]
-
- Linkify.cb.toggle.call embed if Conf['Auto-embed']
-
- title: (data) ->
- [key, uid, options, link, post] = data
- return unless service = Linkify.types[key].title
- titles = Conf['CachedTitles']
- if title = titles[uid]
- link.textContent = title[0]
- else
- try
- $.cache service.api(uid), (-> Linkify.cb.title @, data), responseType: 'json'
- catch err
- link.innerHTML = 'Title Link Blocked (are you using NoScript?)'
- $.prepend link, $.tn "[#{key}] "
- return
-
- cb:
- toggle: ->
- if $.hasClass @, "embedded"
- $.rm @previousElementSibling
- @previousElementSibling.hidden = false
- @textContent = '(embed)'
- else
- @previousElementSibling.hidden = true
- $.before @, Linkify.cb.embed @
- @textContent = '(unembed)'
- $.toggleClass @, 'embedded'
-
- embed: (a) ->
- # We create an element to embed
- el = (type = Linkify.types[a.dataset.key]).el a
-
- # Set style values.
- el.style.cssText = if type.style?
- type.style
- else
- "border: 0; width: 640px; height: 390px"
-
- return el
-
- title: (req, data) ->
- [key, uid, options, link, post] = data
- {status} = req
- service = Linkify.types[key].title
-
- text = "[#{key}] #{switch status
- when 200, 304
- service.text req.response
- when 404
- "Not Found"
- when 403
- "Forbidden or Private"
- else
- "#{status}'d"
- }"
-
- link.textContent = text
- for post2 in post.clones
- for link2 in $$ 'a', post2.nodes.comment when link2.href is link.href
- link2.textContent = text
- return
-
- ordered_types: [
- key: 'audio'
- regExp: /(.*\.(mp3|ogg|wav))$/
- style: ''
- el: (a) ->
- $.el 'audio',
- controls: true
- preload: 'auto'
- src: a.dataset.uid
- ,
- key: 'gist'
- regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/
- el: (a) ->
- div = $.el 'iframe',
- # Github doesn't allow embedding straight from the site, so we use an external site to bypass that.
- src: "http://www.purplegene.com/script?url=https://gist.github.com/#{a.dataset.uid}.js"
- title:
- api: (uid) -> "https://api.github.com/gists/#{uid}"
- text: ({files}) ->
- return file for file of files when files.hasOwnProperty file
- ,
- key: 'image'
- regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/
- style: 'border: 0; width: auto; height: auto;'
- el: (a) ->
- el = $.el 'div'
- el.innerHTML = '
'
- el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href
- el
- ,
- key: 'InstallGentoo'
- regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/
- el: (a) ->
- $.el 'iframe',
- src: "http://paste.installgentoo.com/view/embed/#{a.dataset.uid}"
- ,
- key: 'Twitter'
- regExp: /.*twitter.com\/(.+\/status\/\d+)/
- el: (a) ->
- $.el 'iframe',
- src: "https://twitframe.com/show?url=https://twitter.com/#{a.dataset.uid}"
- ,
- key: 'LiveLeak'
- regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/
- el: (a) ->
- el = $.el 'iframe',
- width: "640",
- height: "360",
- src: "http://www.liveleak.com/ll_embed?i=#{a.dataset.uid}",
- frameborder: "0"
- el.setAttribute "allowfullscreen", "true"
- el
- ,
- key: 'MediaCrush'
- regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i
- style: 'border: 0;'
- el: (a) ->
- el = $.el 'div'
- $.cache "https://mediacru.sh/#{a.dataset.uid}.json", ->
- {status} = @
- return el.textContent = "ERROR #{status}" unless status in [200, 304]
- {files} = @response
- for type in ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg']
- for file in files
- if file.type is type
- embed = file
- break
- break if embed
- return div.textContent = "ERROR: Not a valid filetype" unless embed
- switch embed.type
- when 'video/mp4', 'video/webm', 'video/ogv'
- el.innerHTML = ''
- for ext, i in ['mp4', 'webm', 'ogv']
- el.firstChild.children[i].src = "https://mediacru.sh/#{a.dataset.uid}.#{ext}"
- when 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'
- el.innerHTML = '
'
- el.firstChild.href = a.dataset.href
- el.firstChild.firstChild.src = "https://mediacru.sh/#{file.file}"
- when 'audio/mpeg', 'audio/ogg'
- el.innerHTML = ''
- for ext, i in ['ogg', 'mp3']
- el.firstChild.children[i].src = "https://mediacru.sh/#{a.dataset.uid}.#{ext}"
- else
- el.textContent = "ERROR: No valid filetype."
- el
- ,
- key: 'pastebin'
- regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/
- el: (a) ->
- div = $.el 'iframe',
- src: "http://pastebin.com/embed_iframe.php?i=#{a.dataset.uid}"
- ,
- key: 'gfycat'
- regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/
- el: (a) ->
- div = $.el 'iframe',
- src: "http://gfycat.com/iframe/#{a.dataset.uid}"
- ,
- key: 'SoundCloud'
- regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/
- style: 'border: 0; width: 500px; height: 400px;'
- el: (a) ->
- $.el 'iframe',
- src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent a.dataset.uid}"
- title:
- api: (uid) -> "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent uid}"
- text: (_) -> _.title
- ,
- key: 'StrawPoll'
- regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/
- style: 'border: 0; width: 600px; height: 406px;'
- el: (a) ->
- $.el 'iframe',
- src: "http://strawpoll.me/embed_1/#{a.dataset.uid}"
- ,
- key: 'TwitchTV'
- regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/
- style: "border: none; width: 640px; height: 360px;"
- el: (a) ->
- if result = /(\w+)\/([bc])\/(\d+)/i.exec a.dataset.uid
- [_, channel, type, id] = result
- idparam = {'b': 'archive_id', 'c': 'chapter_id'}
- obj = $.el 'object',
- data: 'http://www.twitch.tv/widgets/archive_embed_player.swf'
- obj.innerHTML = ''
- obj.children[1].value = "channel=#{channel}&start_volume=25&auto_play=false{idparam[type]}=#{id}"
- obj
- else
- channel = (/(\w+)/.exec a.dataset.uid)[0]
- obj = $.el 'object',
- data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=#{channel}"
- obj.innerHTML = ''
- obj.children[1].value = "hostname=www.twitch.tv&channel=#{channel}&auto_play=true&start_volume=25"
- obj
- ,
- key: 'Vocaroo'
- regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/
- style: ''
- el: (a) ->
- $.el 'audio',
- controls: true
- preload: 'auto'
- src: "http://vocaroo.com/media_command.php?media=#{a.dataset.uid.replace /^i\//, ''}&command=download_ogg"
- ,
- key: 'Vimeo'
- regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/
- el: (a) ->
- $.el 'iframe',
- src: "//player.vimeo.com/video/#{a.dataset.uid}?wmode=opaque"
- title:
- api: (uid) -> "https://vimeo.com/api/oembed.json?url=http://vimeo.com/#{uid}"
- text: (_) -> _.title
- ,
- key: 'Vine'
- regExp: /.*(?:vine.co\/)([^#\&\?]*).*/
- style: 'border: none; width: 500px; height: 500px;'
- el: (a) ->
- $.el 'iframe',
- src: "https://vine.co/#{a.dataset.uid}/card"
- ,
- key: 'YouTube'
- regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/
- el: (a) ->
- el = $.el 'iframe',
- src: "//www.youtube.com/embed/#{a.dataset.uid}#{if a.dataset.option then '#' + a.dataset.option else ''}?wmode=opaque"
- el.setAttribute "allowfullscreen", "true"
- el
- title:
- api: (uid) -> "https://gdata.youtube.com/feeds/api/videos/#{uid}?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"
- text: (data) -> data.entry.title.$t
- ,
- # dummy entries: not implemented yet but included to prevent them being wrongly embedded as a subsequent type
- key: 'Loopvid'
- regExp: /.*loopvid.appspot.com\/.*/
- dummy: true
- ,
- key: 'MediaFire'
- regExp: /.*mediafire.com\/.*/
- dummy: true
- ,
- key: 'video'
- regExp: /(.*\.(ogv|webm|mp4))$/
- style: 'border: 0; width: auto; height: auto;'
- el: (a) ->
- $.el 'video',
- controls: 'controls'
- preload: 'auto'
- src: a.dataset.uid
- ]
-