From 7e4af880e1bcbeb732d570aac031811c489d5ace Mon Sep 17 00:00:00 2001 From: Zixaphir Date: Fri, 9 Jan 2015 19:01:59 -0700 Subject: [PATCH] I'm just replacing my Linkifier with ccd0/4chan-x's Instead of manually merging like I've done every other file that wasn't completely new. --- builds/appchan-x.user.js | 1115 ++++++++++++++++---------- builds/crx/script.js | 1115 ++++++++++++++++---------- src/General/html/Features/Embed.html | 6 + src/Linkification/Embedding.coffee | 402 ++++++++++ src/Linkification/Linkify.coffee | 346 +------- 5 files changed, 1796 insertions(+), 1188 deletions(-) create mode 100644 src/General/html/Features/Embed.html create mode 100644 src/Linkification/Embedding.coffee 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\r×\r
\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\r×\r
\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 - ] -