YouTube Me Again!

ytma! automatically converts YouTube(TM), Vimeo, Vine, Soundcloud, WebM, and MP4 links into real embedded videos.

目前为 2017-07-31 提交的版本。查看 最新版本

  1. /*jslint indent: 4, maxerr: 50, browser: true, devel: true, sub: false, fragment: false, nomen: true, plusplus: true, bitwise: true, regexp: true, newcap: true */
  2. // ==UserScript==
  3. // Do not modify and re-release this script!
  4. // If you would like to add support for other sites, please tell me and I'll put it in the includes.
  5.  
  6. // @id youtube-me-again
  7. // @name YouTube Me Again!
  8. // @namespace hateradio)))
  9. // @author hateradio
  10. // @version 7.2
  11. // @description ytma! automatically converts YouTube(TM), Vimeo, Vine, Soundcloud, WebM, and MP4 links into real embedded videos.
  12. // @homepage https://greasyfork.org/en/scripts/1023-youtube-me-again
  13. // @icon https://www.dropbox.com/s/b85qmq0bsim407s/ytma32.png?dl=1
  14. // @icon64 https://www.dropbox.com/s/5zw3al38yf39wxb/ytma64.png?dl=1
  15. // @screenshot https://www.dropbox.com/s/syy9916b1prygl9/ytmascreen5.png?dl=1
  16.  
  17. // @include https://vine.co/v/*/embed/simple
  18. // @match https://vine.co/v/*/embed/simple
  19.  
  20. // @include http*://*youtube-nocookie.com/embed/*
  21. // @match *://*.youtube-nocookie.com/embed/*
  22.  
  23. // @include http*://*youtube.com/embed/*
  24. // @match *://*.youtube.com/embed/*
  25.  
  26. // @include https://gfycat.com/iframe/*
  27. // @match https://gfycat.com/iframe/*
  28.  
  29. // @include http*://*.neogaf.com/forum/showthread.php*
  30. // @include http*://*.neogaf.com/forum/showpost.php?p*
  31. // @include http*://*.neogaf.com/forum/newreply.php*
  32. // @include http*://*.neogaf.com/forum/editpost.php*
  33. // @include http*://*.neogaf.com/forum/private.php*
  34.  
  35. // @match *://*.neogaf.com/forum/showthread.php*
  36. // @match *://*.neogaf.com/forum/showpost.php?p*
  37. // @match *://*.neogaf.com/forum/newreply.php*
  38. // @match *://*.neogaf.com/forum/editpost.php*
  39. // @match *://*.neogaf.com/forum/private.php*
  40.  
  41. // @include http*://*what.cd/forums.php?*viewthread*
  42. // @include http*://*what.cd/torrents.php?*
  43. // @include http*://*what.cd/user.php?*
  44.  
  45. // @match *://*.what.cd/forums.php?*viewthread*
  46. // @match *://*.what.cd/torrents.php?*
  47. // @match *://*.what.cd/user.php?*
  48.  
  49. // @updated 03 Jul 2017
  50.  
  51. // @grant GM_xmlhttpRequest
  52. // @grant unsafeWindow
  53.  
  54. // @run-at document-end
  55. // ==/UserScript==
  56.  
  57. /*
  58.  
  59. ## Updates
  60.  
  61. #### 7.2
  62.  
  63. * New: CSS rule to make videos fit within smaller windows
  64. * New: GitHub repository and update links
  65. * New: Streamable favicon
  66. * Fix: Vimeo favicon
  67.  
  68. #### 7.1
  69.  
  70. * HTTPS links for Vimeo and Gfycat
  71. * Fix: Safari bug
  72.  
  73. #### 7
  74.  
  75. * New: NeoGAF HTTPS Support
  76. * New: Streamable.com added
  77. * New: Soundcloud playlist support
  78. * Improved time parser
  79. * Upon scrolling, cached descriptions are shown
  80. * Code reorganization makes adding new media sites easier
  81.  
  82. ### 6
  83.  
  84. * New: Imgur GIFV (WEBM/MP4) support
  85. * New: Button to remove cache (descriptions/thumbnail links/etc)
  86. * New: SoundCloud playlist support
  87. * Default video quality is now 720p/HD
  88. * Soundcloud now uses HTML5 player
  89. * Players that open on scroll will no longer trigger the opening of players higher on the page
  90. * Adds HTML5, Gfycat, Imgur icons on links
  91. * Improved Soundcloud and GfyCat URL matchers
  92. * Restructured code base to simplify creation of media controls
  93. * Restructured CSS
  94. * Patched back Gfycat iFrame setting for Safari (it is incompatible with new settings)
  95. * Updates YouTube data API
  96. * Removes:
  97. * Object tag for YouTube for Flash (Deprecated)
  98. * "Batch" loading of descriptions (Only manual and scroll methods are supported)
  99.  
  100. // #Updates
  101.  
  102. Whitelist these sites on NoScript/NotScript/etc.
  103. ------------------------------------------------
  104.  
  105. * neogaf.com
  106. * youtube.com
  107. * youtube-nocookie.com
  108. * googlevideo.com (HTML5 player sends videos from this domain)
  109. * googleapis.com (YT video descriptions)
  110. * vimeo.com
  111. * vimeocdn.com
  112. * soundcloud.com
  113. * sndcdn.com
  114. * vineco.com
  115. * vine.com
  116. * vine.co
  117. * gfycat.com
  118. * github.io
  119.  
  120.  
  121. Whitelist these on Ghostery
  122. ---------------------------
  123.  
  124. * SoundCloud (Widgets, Audio / Music Player)
  125.  
  126. */
  127.  
  128. (function () {
  129. 'use strict';
  130.  
  131. var $$, strg, update;
  132.  
  133. if (!Function.prototype.bind) {
  134. Function.prototype.bind = function (self) {
  135. var args = [].slice.call(arguments, 1), fn = this;
  136. return function () {
  137. return fn.apply(self, args.concat([].slice.call(arguments)));
  138. };
  139. };
  140. }
  141.  
  142. function isNumber(n) {
  143. return !isNaN(parseFloat(n)) && isFinite(n);
  144. }
  145.  
  146. function inject(func) {
  147. var script = document.createElement('script');
  148. script.type = 'text/javascript';
  149. script.textContent = '(' + func + ')();';
  150. document.body.appendChild(script);
  151. document.body.removeChild(script);
  152. }
  153.  
  154. function removeSearch(uri, keepHash) { // removes search query from a uri
  155. var s = uri.indexOf('?'), h = uri.indexOf('#'), hash = '';
  156. if (s > -1) {
  157. if (keepHash && h > -1) {
  158. hash = uri.substr(h);
  159. }
  160. uri = uri.substr(0, s) + hash;
  161. }
  162. return uri;
  163. }
  164.  
  165. // D O M Handle
  166. $$ = {
  167. s: function (selector, cb) { var s = document.querySelectorAll(selector), i = -1; while (++i < s.length) { if (cb(s[i], i, s) === false) { break; } } },
  168. o: function (object, cb) { var i; for (i in object) { if (object.hasOwnProperty(i)) { if (cb(i, object[i], object) === false) { break; } } } },
  169. a: function (e) { var i = 1, j = arguments.length, f = document.createDocumentFragment(); for (i; i < j; i++) { if (arguments[i]) { f.appendChild(arguments[i]); } } e.appendChild(f); return e; },
  170. e: function (t, o, e, p) {
  171. var c = document.createElement(t);
  172. $$.o(o, function (k, v) {
  173. var b = k.charAt(0);
  174. switch (b) {
  175. case '_':
  176. c.dataset[k.substring(1)] = v;
  177. break;
  178. case '$':
  179. c.setAttribute(k.substring(1), v);
  180. break;
  181. default:
  182. c[k] = v;
  183. }
  184. });
  185.  
  186. if (e && p) {
  187. c.appendChild(e);
  188. } else if (e) {
  189. e.appendChild(c);
  190. }
  191. return c;
  192. },
  193. x: function (selector) { return this.ary(document.querySelectorAll(selector)); },
  194. ary: function (ary) { return Array.from ? Array.from(ary) : Array.prototype.slice.call(ary); },
  195. top: document.head || document.body,
  196. css: function (t) {
  197. if (!this.style) {
  198. this.style = document.createElement('style');
  199. this.style.type = 'text/css';
  200. this.top.appendChild(this.style);
  201. }
  202. this.style.appendChild(document.createTextNode(t + '\n'));
  203. },
  204. js: function (t) {
  205. var j = document.createElement('script');
  206. j.type = 'text/javascript';
  207. j[/^https?\:\/\//i.test(t) ? 'src' : 'textContent'] = t;
  208. this.top.appendChild(j);
  209. }
  210. };
  211.  
  212. // S T O R A G E HANDLE
  213. strg = {
  214. MAX: 5012,
  215. on: false,
  216. test: function () { try { var a, b = localStorage, c = Math.random().toString(16).substr(2, 8); b.setItem(c, c); a = b.getItem(c); return a === c ? !b.removeItem(c) : false; } catch (e) { return false; } },
  217. read: function (key) {
  218. try {
  219. return JSON.parse(localStorage.getItem(key));
  220. } catch (e) {
  221. console.error(e.lineNumber + ':' + e.message);
  222. return undefined;
  223. }
  224. },
  225. save: function (key, val) { return this.on ? !localStorage.setItem(key, JSON.stringify(val)) : false; },
  226. wipe: function (key) { return this.on ? !localStorage.removeItem(key) : false; },
  227. zero: function (o) { var k; for (k in o) { if (o.hasOwnProperty(k)) { return false; } } return true; },
  228. grab: function (key, def) { var s = strg.read(key); return strg.zero(s) ? def : s; },
  229. size: function () {
  230. var length = 0, key;
  231. try {
  232. for (key in window.localStorage) {
  233. if (window.localStorage.hasOwnProperty(key)) {
  234. length += window.localStorage[key].length;
  235. }
  236. }
  237. } catch (e) {}
  238. return 3 + ((length * 16) / (8 * 1024));
  239. },
  240. full: function () {
  241. try {
  242. var date = +(new Date());
  243. localStorage.setItem(date, date);
  244. localStorage.removeItem(date);
  245. return false;
  246. } catch (e) {
  247. if (e.name === 'QuotaExceededError' ||
  248. e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
  249. return true;
  250. }
  251. }
  252. },
  253. init: function () { this.on = this.test(); }
  254. };
  255. strg.init();
  256.  
  257. // U P D A T E HANDLE
  258. update = {
  259. name: 'ytma!',
  260. version: 7200,
  261. key: 'ujs_YTMA_UPDT_HR',
  262. callback: 'ytmaupdater',
  263. page: 'https://greasyfork.org/scripts/1023-youtube-me-again',
  264. urij: 'https://hateradio.github.io/ytma/update.json',
  265. interval: 5,
  266. day: (new Date()).getTime(),
  267. time: function () { return new Date(this.day + (1000 * 60 * 60 * 24 * this.interval)).getTime(); },
  268. notification: function (j) {
  269. if (this.version < j.version) {
  270. strg.save(this.key, { date: this.time(), version: j.version, page: j.page });
  271. this.link();
  272. }
  273. },
  274. link: function () {
  275. this.csstxt();
  276.  
  277. var a = document.createElement('a'), b = strg.read(this.key);
  278. a.href = b.page || '#';
  279. a.id = 'userscriptupdater2';
  280. a.title = 'Update now.';
  281. a.target = '_blank';
  282. a.textContent = 'An update for ' + this.name + ' is available.';
  283. a.addEventListener('click', function () { this.style.display = 'none'; }, false);
  284. document.body.appendChild(a);
  285. },
  286. xhr: function () {
  287. var x = new XMLHttpRequest();
  288. x.addEventListener('load', function () { update.notification(JSON.parse(this.responseText)); }, false);
  289. x.open('get', update.urij, true);
  290. x.send();
  291. },
  292. check: function (opt) {
  293. if (!strg.on) { return; }
  294. if (window.chrome && window.chrome.extension) { return; }
  295. var stored = strg.read(this.key), page;
  296.  
  297. if (opt || !stored || stored.date < this.day) {
  298. page = (stored && stored.page) || '#';
  299. strg.save(this.key, {date: this.time(), version: this.version, page: page});
  300. this.xhr();
  301. } else if (this.version < stored.version) {
  302. this.link();
  303. }
  304. },
  305. csstxt: function () {
  306. if (!this.pop) { this.pop = true; $$.css('#userscriptupdater2,#userscriptupdater2:visited{box-shadow:1px 1px 6px #7776;border-bottom:3px solid #d65e55;cursor:pointer;color:#555;font-family:sans-serif;font-size:12px;font-weight:700;text-align:justify;position:fixed;z-index:999999;right:10px;top:10px;background:#ebebeb url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTguODQ4NTMgMTk5LjM4MzA3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC4yNzYgLTE2LjM2NykiPjxjaXJjbGUgY3g9IjEwNC4zMjEiIGN5PSIxMTYuMzI3IiByPSI5OC4yNzQiIGZpbGw9IiNkNjVlNTUiLz48cGF0aCBmaWxsPSIjZTljZTAyIiBzdHJva2U9IiNlOWM4MDIiIHN0cm9rZS13aWR0aD0iMTYuNyIgZD0iTTE2Ni40NSAxNTcuMzEySDQxLjg5bDMxLjE0LTUzLjkzNSAzMS4xNC01My45MzUgMzEuMTM3IDUzLjkzNXoiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48dGV4dCB4PSI4NS42NDMiIHk9IjE1MS44NjYiIGZpbGw9IiNkNjVlNTUiIHN0cm9rZS13aWR0aD0iMS40NzciIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Oy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246J0Jvb2sgQW50aXF1YSciIGZvbnQtd2VpZ2h0PSI0MDAiIGZvbnQtc2l6ZT0iNTkuMDg4IiBmb250LWZhbWlseT0iQm9vayBBbnRpcXVhIiBsZXR0ZXItc3BhY2luZz0iMCIgd29yZC1zcGFjaW5nPSIwIj48dHNwYW4geD0iODUuNjQzIiB5PSIxNTEuODY2IiBzdHlsZT0iLWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjonQm9vayBBbnRpcXVhJyIgZm9udC13ZWlnaHQ9IjcwMCIgZm9udC1zaXplPSIxMjYuMDU0Ij4hPC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg==) no-repeat 10px center;background-size:40px;padding:0 20px 0 60px;height:55px;line-height:55px}#userscriptupdater2:hover,#userscriptupdater2:visited:hover{color:#b33a3a !important;border-color:#ce4b30}'); }
  307. }
  308. };
  309. update.check();
  310.  
  311. /** Y T M A CLASS
  312. * Bare YTMA class, filled through _new() or _reactivate()
  313. */
  314. function YTMA() {}
  315.  
  316. YTMA.events = {
  317. clicks: function (e) { // YTMA global click dispatcher
  318. var t = e.target;
  319.  
  320. if (t) {
  321. // console.log('YTMA.clicks');
  322. if (t.tagName === 'VAR' && t.hasAttribute('data-ytmuid')) { // trigger the ui
  323. console.log('show', t.dataset.ytmuid);
  324. YTMA.UI.createFromTrigger(t).showPlayer();
  325. } else if (t.hasAttribute('data-ytmdescription')) {
  326. console.log('load', t.dataset.ytmid);
  327. YTMA.external.events.manualLoad(e);
  328. }
  329. }
  330. },
  331. thumb: {
  332. start: function (e) {
  333. var el = e.target;
  334. el.dataset.thumb = el.dataset.thumb > 0 ? (el.dataset.thumb % 3) + 1 : 2;
  335. el.style.backgroundImage = ['url(https://i3.ytimg.com/vi/', el.dataset.ytmid, '/', el.dataset.thumb, '.jpg)'].join('');
  336. el.dataset.timeout = window.setTimeout(YTMA.events.thumb.start.bind(this, e), 800);
  337. },
  338. stop: function (e) {
  339. window.clearTimeout(e.target.dataset.timeout);
  340. }
  341. }
  342. };
  343.  
  344. YTMA.num = 0;
  345.  
  346. YTMA.addToSet = function (ytma) {
  347. YTMA.set[ytma.data.uid] = ytma;
  348. };
  349.  
  350. YTMA.create = function (link) {
  351. return YTMA.grabIdAndSite(link, function (data, err) {
  352. if (err) {
  353. console.error(link.href, err);
  354. return {};
  355. }
  356.  
  357. var y = new YTMA()._new(data.id, data.site, link);
  358. YTMA.addToSet(y);
  359. y.setup();
  360.  
  361. return y;
  362. });
  363. };
  364.  
  365. YTMA.grabIdAndSite = function (link, cb) {
  366. var uri = link.href, id, site, match;
  367. try {
  368. site = YTMA.reg.siteByTest[YTMA.reg.siteExpressions.test(uri) ? RegExp.lastMatch : ''];
  369. // console.log(site);
  370.  
  371. if (site === 'html5') { // || site === 'html5-audio'
  372. id = uri.slice(-15);
  373. } else if (site === 'soundcloud') {
  374. if (!YTMA.reg.extra.soundcloud.playlist.test(uri)) {
  375. link.href = uri = YTMA.reg.fix.soundcloud(uri);
  376. }
  377.  
  378. match = YTMA.DB.sites.soundcloud.matcher.exec(uri);
  379. id = YTMA.escapeId(match[1]);
  380.  
  381. if (match && YTMA.reg.extra.soundcloud.tracks.test(uri)) {
  382. id = id.slice(-50);
  383. }
  384. } else {
  385. id = uri.match(YTMA.DB.sites[site].matcher)[1];
  386. }
  387.  
  388. console.log(id, site, match);
  389. if (id && YTMA.DB.sites[site]) {
  390. return cb({id: id, site: site}, null);
  391. }
  392. throw TypeError('Invalid ID/Site: ' + id + ' @ ' + site);
  393. } catch (e) {
  394. return cb(null, e);
  395. }
  396. };
  397.  
  398. YTMA.escapeId = function (id) {
  399. return (id += '').replace(/(?:\W)/g, '_');
  400. };
  401.  
  402. YTMA.route = {
  403. host: document.location.host.replace('www.', ''),
  404. control: {
  405. $: {
  406. patchSafari: function () {
  407. delete YTMA.DB.sites.gfycat.videoTag;
  408. delete YTMA.DB.sources.gfycat;
  409. },
  410. checkStorage: function () {
  411. if (strg.full() === true) {
  412. console.log('YTMA ERROR: Storage is full!');
  413. try {
  414. localStorage.removeItem(YTMA.external.version);
  415. strg.on = strg.test();
  416. } catch (e) {
  417. console.error(e);
  418. }
  419. }
  420. },
  421. runOnce: function () {
  422. if (!document.body.hasAttribute('ytma-enabled')) {
  423. document.body.setAttribute('ytma-enabled', true);
  424.  
  425. this.checkStorage();
  426.  
  427. if (!YTMA.DB.extension) { update.check(); }
  428.  
  429. YTMA.css();
  430. YTMA.user.init();
  431. YTMA.DB.postInit();
  432.  
  433. document.body.addEventListener('click', YTMA.events.clicks, false);
  434. }
  435. }
  436. },
  437. go: function (host) {
  438. if (/(?:googlevideo|youtube-nocookie\.com|youtube\.com\.?)/i.test(host)) {
  439. this.sites.youtube();
  440. } else if (this.sites[host]) {
  441. this.sites[host]();
  442. } else {
  443. this.sites.$generic();
  444. }
  445. },
  446. sites: {
  447. $generic: function () {
  448. YTMA.route.control.$.runOnce();
  449.  
  450. if (YTMA.DB.browser.safari) { // safari patch
  451. YTMA.route.control.$.patchSafari();
  452. }
  453.  
  454. if (YTMA.selector.processor() > 0) {
  455. YTMA.user.fn.loadPreferences();
  456. }
  457. },
  458. 'gfycat.com': function () {
  459. var v = document.querySelector('video');
  460. v.controls = true;
  461. $$.css('body,html {overflow:hidden;height:100%;width:100%} video {display:table;height:100%;margin:0 auto;}');
  462. document.body.appendChild(v);
  463. },
  464. 'vine.co': function () {
  465. // console.log('vine.co');
  466.  
  467. window.addEventListener('resize', function () {
  468. $$.s('[style]', function (e) {
  469. e.removeAttribute('style');
  470. });
  471. });
  472. },
  473. youtube: function () { // lets force some quality parity
  474. console.log('now inside youtube . . .', document.location);
  475.  
  476. if (/(?:vq=(\w+))/.test(document.location.search)) {
  477. document.body.setAttribute('gm-player-quality', RegExp.lastParen);
  478. }
  479.  
  480. if (/(?:volume=(\d+))/.test(document.location.search)) {
  481. document.body.setAttribute('gm-player-volume', RegExp.lastParen);
  482. }
  483.  
  484. inject(function () {
  485. var max = 10, count = 1, intr = window.setInterval(function () {
  486. console.log('inside says: ', count, !!window.yt);
  487. if (window.yt && window.player) {
  488. var p = window.yt.player.getPlayerByElement(window.player);
  489.  
  490. if (document.body.hasAttribute('gm-player-quality')) {
  491. console.log('inside says: setting quality to ', document.body.getAttribute('gm-player-quality'));
  492. p.setPlaybackQuality(document.body.getAttribute('gm-player-quality'));
  493. }
  494.  
  495. if (document.body.hasAttribute('gm-player-volume')) {
  496. console.log('inside says: setting volume to ', document.body.getAttribute('gm-player-volume'));
  497. p.setVolume(document.body.getAttribute('gm-player-volume'));
  498. }
  499.  
  500. window.clearInterval(intr);
  501. } else {
  502. console.log(count);
  503. count += 1;
  504. if (count > max) {
  505. window.clearInterval(intr);
  506. }
  507. }
  508. }, 500);
  509. });
  510. }
  511. }
  512. },
  513. load: function () {
  514. this.control.go(this.host);
  515. }
  516. };
  517.  
  518. YTMA.main = function () {
  519. YTMA.reg.siteExpressions = YTMA.DB.views.getAllSiteRegExps();
  520. // console.log(YTMA.reg.siteExpressions);
  521. YTMA.route.load();
  522. };
  523.  
  524. YTMA.set = {};
  525.  
  526. YTMA.collect = function (id) {
  527. var i, a = [];
  528. for (i in YTMA.set) {
  529. if (YTMA.set.hasOwnProperty(i) && YTMA.set[i].data.id === id) {
  530. a.push(YTMA.set[i]);
  531. }
  532. }
  533. return a;
  534. };
  535.  
  536. YTMA.reg = {
  537. siteExpressions: null,
  538. time: /(?:t\=(?:(\d+)m)?(\d+))/,
  539. ios: /(?:\b(?:ipod|iphone|ipad))\b/i,
  540. extra: {
  541. soundcloud: {
  542. playlist: /(?:soundcloud\.com\/.+\/sets\/)/,
  543. tracks: /(?:soundcloud\.com\/.+\/tracks\/)/
  544. }
  545. },
  546. siteByTest: {
  547. youtu: 'youtube',
  548. vimeo: 'vimeo',
  549. vine: 'vine',
  550. gfycat: 'gfycat',
  551. imgur: 'imgur',
  552. '.webm': 'html5',
  553. '.mp4': 'html5',
  554. // '.mp3': 'html5-audio',
  555. '.gifv': 'html5',
  556. soundcloud: 'soundcloud',
  557. 'streamable.com': 'streamable'
  558. },
  559. fix: {
  560. soundcloud: function (uri) {
  561. var match = YTMA.DB.sites.soundcloud.matcher.exec(uri), id;
  562. if (match) {
  563. id = match[1].split('/', 2).join('/');
  564. uri = removeSearch('https://soundcloud.com/' + id, true);
  565. }
  566.  
  567. return uri;
  568. }
  569. }
  570. };
  571.  
  572. YTMA.selector = { // to build the selector
  573. parentBlacklist: ['.smallfont', '.colhead_dark', '.spoiler', 'pre'],
  574. chrome37Blacklist: 'a[href*="pomf.se/"]',
  575. ignore: function () {
  576. var i, j, ignore = [], all = YTMA.DB.views.getAllSiteSelectors().split(','), blacklist = this.parentBlacklist;
  577. for (i = 0; i < blacklist.length; i++) {
  578. for (j = 0; j < all.length; j++) {
  579. ignore.push(blacklist[i] + ' ' + all[j]);
  580. }
  581. }
  582. //console.log(ignore);
  583. return ignore.join(',');
  584. },
  585. links: function () {
  586. var links;
  587.  
  588. $$.x(YTMA.selector.ignore()).map(function (el) { el.setAttribute('ytmaignore', true); });
  589.  
  590. links = $$.x(YTMA.DB.views.getAllSiteSelectors()).filter(function (el) {
  591. var r = !el.hasAttribute('ytmaprocessed') && !el.hasAttribute('ytmaignore');
  592. el.setAttribute('ytmaprocessed', true);
  593. return r;
  594. });
  595.  
  596. return links;
  597. },
  598. processor: function () {
  599. var links = this.links();
  600.  
  601. if (links.length > 0) {
  602. if (window.chrome && (/(?:Chrome\/(\d+))/.exec(window.navigator.appVersion) && RegExp.lastParen < 38)) {
  603. $$.s(YTMA.selector.chrome37Blacklist, function (a) {
  604. if (/(?:\.webm)/i.test(a.href)) {
  605. a.dataset.ytmscroll = false;
  606. }
  607. });
  608. }
  609.  
  610. links.forEach(YTMA.create);
  611. }
  612.  
  613. return links.length;
  614. }
  615. };
  616.  
  617. /**
  618. * User Preferences
  619. * size: Small (240p), Medium (360p), Large (480p), XL (720p)
  620. * ratio: 1 4:3, 2 16:9
  621. * quality: 240, 360, 480, 720, 1080
  622. * focus: 0/1; Will attempt to set the window's focus near the video
  623. * autoShow: 0/1; Will automatically display HTML5 videos, which currently lack descriptions and thumbnails
  624. * desc: (Descriptions) 0 None; 1 Yes on scroll; 2 Yes all at once
  625. * yt_nocookie: 0/1; Will disable/enable youtube-nocookie.com
  626. * yt_volume: positive number; youtube volume
  627. * yt_annotation: 0/1; youtube annotations
  628. */
  629. YTMA.user = {
  630. KEY: 'ytmasetts',
  631. $form: null,
  632. init: function () {
  633. this.load();
  634.  
  635. if (strg.on) {
  636. this.fn.makeForm();
  637. this.mark();
  638. }
  639. },
  640. valid: {
  641. focus: [0, 1],
  642. desc: [0, 1, 2],
  643. ratio: [1, 2],
  644. size: [240, 360, 480, 720],
  645. quality: [240, 360, 480, 720, 1080],
  646. autoShow: [0, 1],
  647. yt_nocookie: [0, 1],
  648. yt_annotation: [0, 1], // hide | show
  649. yt_volume: 100 // todo? (function () { var a = new Array(101); for (i = 0; i <= 100; i++) { a[i] = i; } return a; }())
  650. },
  651. mapping: { // map values to some other values used by an external API, for example
  652. yt_annotation: [3, 1] // 3 = hide | 1 = show
  653. },
  654. validate: function (property, n) {
  655. n = +n;
  656.  
  657. if (property === 'yt_volume') {
  658. return n >= 0 && n <= 100 ? (+n) : YTMA.user.defaults()[property];
  659. }
  660. return YTMA.user.valid[property].indexOf(n) > -1 ? n : YTMA.user.defaults()[property];
  661. },
  662. defaults: function () {
  663. return {
  664. focus : 0,
  665. desc : 1,
  666. ratio : 2,
  667. size : 360,
  668. quality : 720,
  669. autoShow : 1,
  670. yt_nocookie : 0,
  671. yt_annotation : 1,
  672. yt_volume : 100
  673. };
  674. },
  675. load: function () {
  676. var s = strg.grab(YTMA.user.KEY, {});
  677.  
  678. YTMA.user.preferences = {
  679. size : YTMA.user.validate('size', s.size),
  680. ratio : YTMA.user.validate('ratio', s.ratio),
  681. desc : YTMA.user.validate('desc', s.desc),
  682. focus : YTMA.user.validate('focus', s.focus),
  683. quality : YTMA.user.validate('quality', s.quality),
  684. autoShow : YTMA.user.validate('autoShow', s.autoShow),
  685. yt_nocookie : YTMA.user.validate('yt_nocookie', s.yt_nocookie),
  686. yt_annotation : YTMA.user.validate('yt_annotation', s.yt_annotation),
  687. yt_volume : YTMA.user.validate('yt_volume', s.yt_volume)
  688. };
  689.  
  690. $$.o(YTMA.user.mapping, function (key, val) {
  691. if (!val.hasOwnProperty('indexOf')) {
  692. YTMA.user.preferences[key] = val[YTMA.user.valid[key].indexOf(YTMA.user.preferences[key])];
  693. }
  694. });
  695.  
  696. console.log('loaded: ', YTMA.user.preferences);
  697. },
  698. mark: function () {
  699. var a = {};
  700. a.ytma__focus = !!YTMA.user.preferences.focus;
  701. a.ytma__autoShow = !!YTMA.user.preferences.autoShow;
  702. a.ytma__yt_nocookie = !!YTMA.user.preferences.yt_nocookie;
  703. a.ytma__yt_annotation = !!YTMA.user.preferences.yt_annotation;
  704. a.ytma__yt_volume = YTMA.user.preferences.yt_volume;
  705. a['ytma__ratio' + YTMA.user.preferences.ratio] = true;
  706. a['ytma__size' + YTMA.user.preferences.size] = true;
  707. a['ytma__desc' + YTMA.user.preferences.desc] = true;
  708. a['ytma__quality' + YTMA.user.preferences.quality] = !!YTMA.user.preferences.quality;
  709.  
  710. // console.log('marking', a);
  711. $$.o(a, function (id, val) {
  712. try {
  713. var el = document.getElementById(id);
  714. el.checked = val;
  715. el.value = val;
  716. } catch (e) {
  717. // console.log(id, e);
  718. }
  719. });
  720. },
  721. events: {
  722. save: function (e) {
  723. var o = {};
  724.  
  725. if (e && /(?:INPUT|LABEL)/i.test(e.target.nodeName)) {
  726. // console.log(YTMA.user.$form.querySelectorAll('[data-key]'));
  727. // [data-key]:checked
  728. $$.ary(YTMA.user.$form.querySelectorAll('[data-key]')).forEach(function (e) {
  729. var key;
  730. key = e.dataset.key;
  731.  
  732. if (e.type === 'checkbox') {
  733. o[key] = +e.checked;
  734. } else if (e.type === 'radio') {
  735. if (e.checked) {
  736. if (e.hasAttribute('data-num')) {
  737. o[key] = +e.dataset.num;
  738. }
  739. }
  740. } else {
  741. o[key] = +e.value;
  742. }
  743. });
  744.  
  745. if (strg.save(YTMA.user.KEY, o)) {
  746. YTMA.user.load();
  747. } else {
  748. YTMA.user.error.classList.remove('ytm_none');
  749. }
  750. }
  751.  
  752. },
  753. reset: function () {
  754. YTMA.user.preferences = YTMA.user.defaults();
  755. YTMA.user.mark();
  756. strg.wipe(YTMA.user.KEY);
  757. YTMA.user.error.classList.add('ytm_none');
  758. },
  759. clear: function () {
  760. try {
  761. localStorage.removeItem(YTMA.external.version);
  762. YTMA.user.events.reset();
  763. console.log('removed all YTMA cache');
  764. } catch (e) {
  765. console.error(e);
  766. }
  767. },
  768. formToggle: function (e) {
  769. if (!e || (e && e.target && !/(?:INPUT|LABEL)/i.test(e.target.nodeName))) {
  770. YTMA.user.$form.classList.toggle('ytm_none');
  771. }
  772. },
  773. formToggleKeyboard: function (e) {
  774. // press CTRL+SHIFT+Y (META+SHIFT+Y) to display settings form
  775. if ((e.ctrlKey || e.metaKey) && e.shiftKey && String.fromCharCode(e.which).toLowerCase() === 'y') {
  776. e.preventDefault();
  777. YTMA.user.events.formToggle();
  778. }
  779. }
  780. },
  781. fn: {
  782. $scroller: null,
  783. $once: false,
  784. loadPreferences: function () {
  785. YTMA.user.fn.onScrollLoadDescriptions(YTMA.user.preferences.desc === 1);
  786.  
  787. this.loadPreferencesOnce();
  788. },
  789. loadPreferencesOnce: function () {
  790. if (this.$once) { return; }
  791.  
  792. this.$once = true;
  793.  
  794. if (YTMA.user.preferences.autoShow === 1) {
  795. YTMA.user.fn.onScrollViewMedia();
  796. }
  797. },
  798. showMedia: function () {
  799. console.log('showMedia');
  800. return new YTMA.Scroll('a.ytm_scroll:not([data-ytmscroll="false"])', function (link) {
  801. if (YTMA.Scroll.visibleAll(link, 50)) {
  802. $$.s('var[data-ytmsid="' + link.dataset.ytmsid + '"]:not([data-ytmscroll="false"])', function (trigger) {
  803. var ui = YTMA.UI.createFromTrigger(trigger);
  804. ui.showOnScroll(link);
  805. });
  806. }
  807. });
  808. },
  809. toggleMedia: function () {
  810. return new YTMA.Scroll('div.ytm_panel_switcher', function (div) {
  811. var v = div.querySelector('video'),
  812. paused = v && (v.paused || v.ended),
  813. ui = YTMA.set[div.dataset.ytmuid].getUI();
  814.  
  815. if (paused && !YTMA.Scroll.visibleAll(div, 0)) {
  816. return ui.play.switchStandby();
  817. }
  818.  
  819. if (ui.play.isStandby() && YTMA.Scroll.visibleAll(div, 200)) {
  820. return ui.play.switchOn();
  821. }
  822.  
  823. // todo ascertain embedded player properties
  824. // f = div.querySelector('iframe, object');
  825. // if (f && !YTMA.Scroll.visibleAll(div, 200)) {
  826. // y.hidePlayer();
  827. // }
  828. });
  829. },
  830. onScrollViewMedia: function () {
  831. this.showMedia();
  832. this.toggleMedia();
  833. },
  834. onScrollLoadDescriptions: function (ajax) {
  835. if (YTMA.user.fn.$scroller) { YTMA.user.fn.$scroller.stop(); }
  836.  
  837. YTMA.user.fn.$scroller = new YTMA.Scroll('span.ytm_manual > a.ytm_title:not(.ytm_error)', function (a) {
  838. if (YTMA.Scroll.visibleAll(a, 200)) {
  839. if (ajax) {
  840. YTMA.ajax.loadFromDataset(a.dataset);
  841. } else {
  842. YTMA.ajax.loadFromCacheDataset(a.dataset);
  843. }
  844. // console.log('doc', document.querySelectorAll(YTMA.user.fn.$scroller.selector).length, a.dataset.id);
  845. }
  846.  
  847. if (document.querySelectorAll(YTMA.user.fn.$scroller.selector).length === 0) {
  848. YTMA.user.fn.$scroller.stop();
  849. }
  850. });
  851. },
  852. makeForm: function () {
  853. var e, f = [
  854. '<div id="ytm_settings" class="ytm_sans ytm_block ytm_normalize"><form action="" title="Double click to close"><div id="ytm_settingst">ytma! Site Settings</div><div class="ytm_field_container">',
  855. '<fieldset><legend title="Load descriptions from the content sever.">Load Descriptions</legend><p><span><input id="ytma__desc0" type="radio" data-num="0" name="ytma__desc" data-key="desc"><label for="ytma__desc0" title="Load descriptions on demand">Manually</label></span><span><input id="ytma__desc1" type="radio" data-num="1" name="ytma__desc" data-key="desc"><label for="ytma__desc1" title="Load descriptions as they become visible on the screen.">Automatically, on scrolling</label></span></p></fieldset>',
  856. '<fieldset><legend>HTML5 Players</legend><p><input name="ytma__autoShow" data-key="autoShow" id="ytma__autoShow" type="checkbox"><label for="ytma__autoShow">Automatically show WebM, MP4 and Soundcloud players</label></p></fieldset>',
  857. '<fieldset><legend>Player Size</legend><p><span><input type="radio" name="ytma__size" data-key="size" data-num="240" id="ytma__size240" /><label for="ytma__size240">S <small>240p</small></label></span><span><input name="ytma__size" data-key="size" type="radio" id="ytma__size360" data-num="360" /><label for="ytma__size360">M <small>360p</small></label></span><span><input type="radio" name="ytma__size" data-key="size" data-num="480" id="ytma__size480" /><label for="ytma__size480">L <small>480p</small></label></span><span><input type="radio" name="ytma__size" data-key="size" data-num="720" id="ytma__size720" /><label for="ytma__size720">X <small>720p</small></label></span></p></fieldset>',
  858. '<fieldset><legend>Quality</legend><p><span><input name="ytma__quality" data-key="quality" data-num="240" id="ytma__quality240" type="radio"><label for="ytma__quality240">240p</label></span><span><input name="ytma__quality" data-key="quality" id="ytma__quality360" data-num="360" type="radio"><label for="ytma__quality360">360p</label></span><span><input name="ytma__quality" data-key="quality" data-num="480" id="ytma__quality480" type="radio"><label for="ytma__quality480">480p</label></span><span><input name="ytma__quality" data-key="quality" data-num="720" id="ytma__quality720" type="radio"><label for="ytma__quality720">720p</label></span><span><input name="ytma__quality" data-key="quality" data-num="1080" id="ytma__quality1080" type="radio"><label for="ytma__quality1080">1080p</label></span></p></fieldset>',
  859. '<fieldset><legend>Aspect Ratio</legend><p><span><input name="ytma__ratio" data-key="ratio" type="radio" id="ytma__ratio2" data-num="2" /><label for="ytma__ratio2">16:9</label></span><span><input type="radio" name="ytma__ratio" data-key="ratio" data-num="1" id="ytma__ratio1" /><label for="ytma__ratio1">4:3</label></span></p></fieldset>',
  860. '<fieldset><legend>YouTube</legend>',
  861. '<p><input name="ytma__yt_annotation" data-key="yt_annotation" type="checkbox" id="ytma__yt_annotation" /><label for="ytma__yt_annotation">Enable video annotations</label></p>',
  862. '<p><input name="ytma__yt_nocookie" data-key="yt_nocookie" type="checkbox" id="ytma__yt_nocookie" /><label for="ytma__yt_nocookie">Use https://youtube-nocookie.com to load videos</label></p>',
  863. '<p><input name="ytma__yt_volume" data-key="yt_volume" type="number" style="width:4em" min=0 max=100 id="ytma__yt_volume" /><label for="ytma__yt_volume">Video volume (Experimental)</label></p>',
  864. '</fieldset>',
  865. '<fieldset><legend>Window Focus</legend><p><input name="ytma__focus" data-key="focus" type="checkbox" id="ytma__focus" value="focus" /><label for="ytma__focus">After clicking the thumbnail, set the video at the top of the window.</label></p></fieldset>',
  866. '</div><p><small id="ytm_settings_error" class="ytm_error ytm_none ytm_title">Error! Your settings could not be saved.</small></p><p id="ytm_opts"><button type="button" id="ytmaclose">Close</button> <button type="button" id="ytmareset">Reset</button> <button type="button" id="ytmaclear" title="Remove all video descriptions that have been cached">Reset & Remove Cache</button></p></form></div>'
  867. ].join('');
  868.  
  869. YTMA.user.$form = $$.e('div', {className: 'ytm_fix_center ytm_none ytm_box', innerHTML: f}, document.body);
  870. YTMA.user.error = document.getElementById('ytm_settings_error');
  871.  
  872. e = YTMA.Scroll.debounce(YTMA.user.events.save, 500);
  873. YTMA.user.$form.addEventListener('submit', function (evt) { evt.preventDefault(); }, false);
  874. YTMA.user.$form.addEventListener('keyup', e, false);
  875. YTMA.user.$form.addEventListener('click', e, false);
  876.  
  877. YTMA.user.$form.addEventListener('dblclick', YTMA.user.events.formToggle, false);
  878. document.getElementById('ytmaclose').addEventListener('click', YTMA.user.events.formToggle, false);
  879. document.getElementById('ytmareset').addEventListener('click', YTMA.user.events.reset, false);
  880. document.getElementById('ytmaclear').addEventListener('click', YTMA.user.events.clear, false);
  881. document.body.addEventListener('keydown', YTMA.user.events.formToggleKeyboard, false);
  882. }
  883. }
  884. };
  885.  
  886. YTMA.css = function () {
  887. var playerCss = YTMA.Player.css.generator(),
  888. loadingIcon = 'data:image/gif;base64,R0lGODlhDgAKAJEAAP///+BKV////wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAADgAKAAACHFSOeQYI71p6MtAJz41162yBH+do5Ih1kKG0QgEAIfkEBQoAAgAsAAABAA0ACAAAAhSUYGEoerkgdIzKGlu2ET/9ceJmFAAh+QQFCgACACwAAAEADQAIAAACFJRhcbmiglx78SXKYK6za+NxHyYVACH5BAUKAAIALAAAAQANAAgAAAIWVCSAl+hqEGRTLhtbdvTqnlUf9nhTAQAh+QQFCgACACwAAAEADQAIAAACFZRiYCh6uaCRzNXYsKVT+5eBW3gJBQAh+QQJCgACACwAAAAADgAKAAACGpSPaWGwfZhwQtIK8VTUvuxpm9Yp4XlmpiIUADs=';
  889.  
  890. // Roboto font-o
  891. // $$.e('link', {
  892. // rel: 'stylesheet',
  893. // $type: 'text/css',
  894. // href: 'https://fonts.googleapis.com/css?family=Roboto'
  895. // }, document.body);
  896.  
  897. // console.log(playerCss);
  898. $$.css(playerCss);
  899.  
  900. // images
  901. // todo update(site, size, padding)
  902. $$.css([
  903. '.ytm_loading{background:url(', loadingIcon, ') 0 3px no-repeat;}',
  904. '.ytm_link{background:url(', YTMA.DB.sites.youtube.favicon, ') 0 center no-repeat !important;margin-left:4px;padding-left:20px!important;}',
  905. '.ytm_link.ytm_link_vimeo{background-image:url(', YTMA.DB.sites.vimeo.favicon, ') !important;background-size:12px 12px !important;padding-left:18px!important}',
  906. '.ytm_link.ytm_link_vine{background-image:url(', YTMA.DB.sites.vine.favicon, ') !important;background-size:10px 10px!important;padding-left:16px!important}',
  907. '.ytm_link.ytm_link_soundcloud{background-image:url(', YTMA.DB.sites.soundcloud.favicon, ')!important;padding-left:17px!important}',
  908. '.ytm_link.ytm_link_html5{background-image:url(', YTMA.DB.sites.html5.favicon, ') !important;padding-left:16px!important}',
  909. '.ytm_link.ytm_link_gfycat{background-image:url(', YTMA.DB.sites.gfycat.favicon, ') !important;background-size:12px 12px !important;padding-left:16px!important;}',
  910. '.ytm_link.ytm_link_imgur{background-image:url(', YTMA.DB.sites.imgur.favicon, ') !important;background-size:12px 12px !important;padding-left:16px!important}',
  911. '.ytm_link.ytm_link_streamable{background-image:url(', YTMA.DB.sites.streamable.favicon, ') !important; background-size: 12px 12px !important;padding-left: 14px !important;}'
  912. ].join(''));
  913.  
  914. // todo
  915. // if (window.NO_YTMA_CSS) { return; }
  916.  
  917. $$.css('.ytm_none,.ytm_link br{display:none!important}.ytm_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ytm_block{display:block;position:relative;clear:both;text-align:left;border:0;margin:0;padding:0;overflow:hidden}.ytm_normalize{font-weight:400!important;font-style:normal!important;line-height:1.2!important}.ytm_sans{font-family:Arial,Helvetica,sans-serif!important}.ytm_spacer{overflow:auto;margin:0 0 6px;padding:4px}.ytm_spacer.ytm_site_slim{display:inline}.ytm_clear:after{content:"";display:table;clear:both}.ytm_center{text-align:center}.ytm_link b,.ytm_link strong{font-weight:400!important}.ytm_link u{text-decoration:none!important}.ytm_link i,.ytm_link em{font-style:normal!important}.ytm_trigger{width:118px;height:66px;background-color:#262626!important;cursor:pointer;background-position:-1px -12px;float:left;box-shadow:2px 2px rgba(0,0,0,.3);background-size:auto 90px!important;color:#fff;text-shadow:#333 0 0 2px;font-size:13px}.ytm_trigger:hover{box-shadow:2px 2px #9eae9e;opacity:.95}.ytm_trigger var{z-index:2;height:100%;width:100%;position:absolute;left:0;top:0;text-align:right}.ytm_label{display:block;padding:3px 6px;line-height:1.2;font-style:normal}.ytm_init{height:22px;background:rgba(11,11,11,.62);padding:4px 25px 6px 6px}.ytm_site_vine .ytm_trigger{background-color:#90ee90!important;background-size:120px auto!important}.ytm_site_slim .ytm_trigger{background:#e34c26!important;height:auto;box-shadow:0 0 2px #ffdb9d inset,2px 2px rgba(0,0,0,.3);margin:0 3px 0 0;width:auto;transition:all .3s ease-in-out 0s}.ytm_site_slim .ytm_trigger:hover{opacity:.8}.ytm_site_slim .ytm_label{text-shadow:0 0 1px #f06529}.ytm_site_slim .ytm_init{background:transparent}.ytm_bd{float:left;max-width:500px;margin:2px 10px;font-size:90%}.ytm_title{font-weight:700}.ytm_error{color:#cc2f24;font-style:italic}.ytm_loading{font-style:italic;padding:1px 1.5em}.ytm_descr{word-wrap:break-word;max-height:48px;overflow:auto;padding-right:20px}.ytm_descr[data-full]{cursor:pointer}.ytm_descr_open{resize:both;white-space:pre-line}.ytm_descr_open[style]{max-height:none}.ytm_projector{margin-bottom:4px}ul.ytm_control{overflow:hidden;margin:0!important;padding:3px 0 1px;list-style-position:outside!important}.ytm_control li{display:inline;margin:0!important;padding:0!important}.ytm_control li>ul{display:inline-block;margin:0;padding:0 1px 0 0}.ytm_control li ul li{-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none;list-style-type:none;cursor:pointer;float:left;color:#858585;border:1px solid #1d1d1d;border-bottom:1px solid #000;border-top:1px solid #292929;box-shadow:0 0 1px #555;height:14px;font-size:12px!important;line-height:12px!important;background:#222;background:linear-gradient(#2d2c2c,#222);margin:0!important;padding:5px 9px 3px!important}.ytm_control li ul li:first-child{border-radius:2px 0 0 2px}.ytm_control li ul li:last-child{border-left:0!important;border-radius:0 2px 2px 0;margin:0 2px 0 0!important}.ytm_control li ul li:first-child:last-child,.ytm_li_setting{border-radius:2px}.ytm_control li ul li:hover{color:#ccc;text-shadow:1px 1px 0 #333;background:#181818}.ytm_control li ul li[id]{color:#ddd;text-shadow:0 0 2px #444}.ytm_panel_size{background:#000;max-width:100%;}.ytm_panel_switcher[data-standby="true"]{background:#111}.ytm_panel_switcher[data-standby="true"]:after{cursor:cell;color:#0e0e0e;content:"ytma!";display:block;font-size:85px;font-style:italic;font-weight:700;left:50%;position:absolute;text-shadow:2px 1px #181818,-1px -1px #0a0a0a;top:50%;transform:translate(-50%,-50%)}.ytm_site_soundcloud .ytm_panel_size.ytm_soundcloud-playlist{height:334px!important}.ytm_fix_center{background:rgba(51,51,51,.41);height:100%;left:0;position:fixed;top:0;width:100%;z-index:99998}#ytm_settings{z-index:99999;max-width:500px;max-height:85%;overflow:auto;background:#fbfbfb;border:1px solid #bbb;color:#444;box-shadow:0 0 5px rgba(0,0,0,.2),0 0 3px rgba(239,239,239,.1) inset;margin:4% auto;padding:4px 8px 0}#ytm_settings p{margin:5px 0;padding:0}#ytm_settings fieldset{vertical-align:top;border-radius:3px;border:1px solid #ccc;margin:0 0 5px}#ytm_settings fieldset span{display:inline-block;min-width:5em}#ytm_settings input{vertical-align:baseline!important;margin:3px 5px!important}#ytm_settingst{font-size:110%;border-bottom:1px solid #d00;margin:3px 0 9px;padding:0 3px 3px}#ytm_settings label{cursor:pointer}#ytm_settings small{font-size:90%}#ytm_opts button{cursor:pointer;margin:10px 5px 8px 2px;padding:3px;border:1px solid #adadad;border-radius:2px;background:#eee;font-size:90%}#ytm_opts button:hover{background:#ddd}');
  918. // $$.css('.ytm_site_youtube .ytm_sans { font-family: \'Roboto\'; }');
  919. };
  920.  
  921. YTMA.ajax = {
  922. load: function (site, id, uri) {
  923. console.log('YTMA.ajax.load:', site, id, uri);
  924. uri = YTMA.DB.sites[site].ajax.replace('%key', id).replace('%uri', uri);
  925.  
  926. if (YTMA.DB.sites[site].ajaxExtension) { return this.gmxhr(uri, site, id); }
  927.  
  928. console.log('ajax.site?', YTMA.DB.sites[site].ajax.replace('%key', id).replace('%uri', uri));
  929. if (YTMA.DB.sites[site].ajax) {
  930. console.log('preping uri');
  931. return this.xhr(uri, site, id);
  932. }
  933.  
  934. return null;
  935. },
  936. loadFromDataset: function (dataset) {
  937. if (!this.loadFromCacheDataset(dataset)) {
  938. return this.load(dataset.ytmsite, dataset.ytmid, dataset.ytmuri);
  939. }
  940. },
  941. loadFromCacheDataset: function (dataset) {
  942. var cache = YTMA.external.dataFromStorage(dataset.ytmsite, dataset.ytmid);
  943.  
  944. console.log('YTMA.ajax.cache:', dataset.ytmsite, dataset.ytmid);
  945. console.log('@cache:', cache);
  946.  
  947. if (cache) { YTMA.external.populate(cache); }
  948.  
  949. return cache;
  950. },
  951. gmxhr: function (uri, site, id) {
  952. try {
  953. // console.log('gmxhr starting!');
  954. GM_xmlhttpRequest({
  955. method: 'GET',
  956. url: uri,
  957. onload: function (response) {
  958. // console.log(response);
  959. YTMA.external.parse(response.responseText, site, id);
  960. },
  961. onerror: function () {
  962. console.log('GM Cannot XHR');
  963. YTMA.ajax.failure.call({id: id});
  964. }
  965. });
  966.  
  967. YTMA.ajax.preProcess(id);
  968.  
  969. } catch (e) {
  970. if (YTMA.DB.extension) {
  971. console.log('attempting cs xhr');
  972. this.xhr(uri, site, id);
  973. } else {
  974. console.log('No applicable CORS request available.');
  975. this.failure.call({id: id});
  976. }
  977. }
  978. },
  979. xhr: function (uri, site, id) {
  980. var x = new XMLHttpRequest();
  981. console.log('xhr', uri, id, site);
  982.  
  983. YTMA.ajax.preProcess(id);
  984.  
  985. x.onreadystatechange = function () {
  986. if (this.readyState === this.DONE) {
  987. // console.log(this.readyState, this.status);
  988. if (this.status === 200) {
  989. YTMA.external.parse(this.responseText, site, id);
  990. } else if (this.status === 403) {
  991. YTMA.external.populate({site: site, id: id, title: 'Error 403', desc: ''});
  992. YTMA.external.save({site: site, id: id, title: 'Error 403', desc: ''});
  993. } else { // if (this.status >= 400 || this.status === 0) {
  994. YTMA.ajax.failure.call({id: id});
  995. }
  996. }
  997. };
  998.  
  999. try {
  1000. console.log('sending');
  1001. x.open('get', uri, true);
  1002. x.send();
  1003. } catch (e) {
  1004. console.error('Cannot send xhr', uri);
  1005. YTMA.ajax.failure.call({id: id});
  1006. console.error(e);
  1007. }
  1008. },
  1009. failure: function () {
  1010. $$.s('.ytm_bd._' + YTMA.escapeId(this.id), function (el) {
  1011. var a = el.querySelector('a');
  1012. a.dataset.tries = a.dataset.tries ? parseFloat(a.dataset.tries) + 1 : 1;
  1013. a.textContent = 'Error, unable to load data. ' + (a.dataset.tries > 0 ? ('(' + a.dataset.tries + ')') : '[Retry]');
  1014. a.className = 'ytm_error ytm_title';
  1015. });
  1016. },
  1017. preProcess: function (id) {
  1018. $$.s('.ytm_manual._' + YTMA.escapeId(id) + ' a', function (el) {
  1019. el.classList.add('ytm_loading');
  1020. el.textContent = 'Loading data . . .';
  1021. el.title = 'Retry loading data.';
  1022. });
  1023. }
  1024. };
  1025.  
  1026. /** E X T E R N A L Apparatus
  1027. * Data from external sites
  1028. */
  1029. YTMA.external = {
  1030. version: 'ytma.4.1.dat',
  1031. parse: function (response, site, id) {
  1032. if (this.parsers[site]) {
  1033. response = YTMA.DB.sites[site].rawResponse ? response : JSON.parse(response);
  1034. this.populate(this.helper.cutDescription(this.parsers[site](response, id)));
  1035. }
  1036. },
  1037. parsers: {
  1038. soundcloud: function (j, id) {
  1039. return {
  1040. site: 'soundcloud',
  1041. id: id, //unescape(j.html).match(/tracks\/(\d+)/)[1],
  1042. title: j.title,
  1043. desc: j.description,
  1044. th: removeSearch(j.thumbnail_url)
  1045. };
  1046. },
  1047. vimeo: function (j) {
  1048. j = j[0];
  1049. return {
  1050. site: 'vimeo',
  1051. id: j.id,
  1052. title: j.title + ' ' + YTMA.external.time.fromSeconds(j.duration),
  1053. desc: j.description.replace(/<br.?.?>/g, ''),
  1054. th: decodeURI(j.thumbnail_medium)
  1055. };
  1056. },
  1057. youtube: function (j, id) {
  1058. if (j.pageInfo.totalResults < 1) {
  1059. return { id: id, error: true };
  1060. }
  1061.  
  1062. j = j.items[0];
  1063. var o = {
  1064. site: 'youtube',
  1065. id: id,
  1066. title: j.snippet.title + ' ' + YTMA.external.time.fromIso8601(j.contentDetails.duration),
  1067. desc: j.snippet.description
  1068. // aspectRatio: j.contentDetails.aspectRatio
  1069. };
  1070.  
  1071. return o;
  1072. },
  1073. vine: function (j, id) {
  1074. return {
  1075. site: 'vine',
  1076. id: id,
  1077. title: j.title,
  1078. th: removeSearch(j.thumbnail_url)
  1079. };
  1080. },
  1081. gfycat: function (j, id) {
  1082. j = j.gfyItem;
  1083. if (j) {
  1084. return {
  1085. site: 'gfycat',
  1086. id: id || j.gfyName,
  1087. title: j.title || j.gfyName
  1088. };
  1089. }
  1090. },
  1091. streamable: function (j, id) {
  1092. return {
  1093. site: 'streamable',
  1094. id: id,
  1095. title: j.title || 'Untitled'
  1096. };
  1097. }
  1098. },
  1099. set: function (data) {
  1100. if (!this.db[data.site]) {
  1101. this.db[data.site] = {};
  1102. }
  1103. this.db[data.site][data.id] = data;
  1104. return this.save();
  1105. },
  1106. unset: function (data) {
  1107. // console.log('unset', data.id);
  1108. if (data.site) {
  1109. delete this.db[data.site][data.id];
  1110. return this.save();
  1111. }
  1112. },
  1113. limitDB: function (max, db) {
  1114. // limits an object's items by half of the max
  1115. // removes the older items at the start of the object
  1116. var keys = Object.keys(db),
  1117. half = Math.floor(max / 2),
  1118. start,
  1119. ndb,
  1120. i;
  1121.  
  1122. if (keys.length > max) {
  1123. ndb = {};
  1124. start = keys.length - half;
  1125.  
  1126. for (i = start; i < keys.length; i++) {
  1127. ndb[keys[i]] = db[keys[i]];
  1128. }
  1129. }
  1130.  
  1131. return ndb || db;
  1132. },
  1133. save: function () {
  1134. this.db = this.limitDB(1000, this.db);
  1135. return strg.save(this.version, this.db);
  1136. },
  1137. helper: {
  1138. cutDescription: function (data) {
  1139. if (data.desc && data.desc.length > 140) {
  1140. data.full = data.desc;
  1141. data.desc = data.desc.substr(0, 130) + ' . . .';
  1142. }
  1143. return data;
  1144. },
  1145. thumbnail: function (data) {
  1146. $$.s('[data-ytmid="%id"].ytm_trigger'.replace('%id', data.id), function (el) {
  1147. el.setAttribute('style', 'background: url(' + data.th + ')');
  1148. });
  1149. },
  1150. titleToggle: function () {
  1151. this.classList.toggle('ytm_descr_open');
  1152. this.textContent = this.textContent.length < 140 ? this.dataset.full : this.dataset.full.substr(0, 130) + ' . . .';
  1153. this.removeAttribute('style');
  1154. }
  1155. },
  1156. time: {
  1157. keepMinutesAndSeconds: function (v, i) {
  1158. return i > 1 || v > 0;
  1159. },
  1160. leadingZero: function (v, i) {
  1161. return i > 0 ? ('00' + v).slice(-2) : v;
  1162. },
  1163. fromArray: function (a) { // [days, hours, mins, secs]
  1164. var b, p = '';
  1165.  
  1166. try {
  1167. // Remove empty values, but keep lower indexes (m:s); a[i] > 0 || i > 1
  1168. // Add leading 0's, ignoring the first index
  1169. // a.slice(0, 1).concat(a.slice(1))
  1170. b = a.filter(this.keepMinutesAndSeconds).map(this.leadingZero);
  1171. p = '(' + b.join(':') + ')';
  1172. } catch (e) {
  1173. console.error('Could not parse this time.');
  1174. }
  1175.  
  1176. console.log({a: a, b: b, p: p });
  1177. return p;
  1178. },
  1179. fromIso8601: function (iso8601) { // eg PT3M, T29S
  1180. var a,
  1181. parseDigits = function (reg) {
  1182. if (reg.test(iso8601)) {
  1183. return RegExp.lastParen;
  1184. }
  1185. return 0;
  1186. };
  1187.  
  1188. // P#DT#H#M#S || PT#H#M#S
  1189. a = [/(\d+)D/, /(\d+)H/, /(\d+)M/, /(\d+)S/].map(parseDigits);
  1190.  
  1191. return this.fromArray(a);
  1192. },
  1193. fromSeconds: function (seconds) {
  1194. var a = [
  1195. Math.floor(seconds / 86400) % 24,
  1196. Math.floor(seconds / 3600) % 60,
  1197. Math.floor(seconds / 60) % 60,
  1198. seconds % 60
  1199. ];
  1200. return this.fromArray(a);
  1201. }
  1202. },
  1203. validate: function (data) {
  1204. if (!data || !data.id || data.error) {
  1205. return YTMA.ajax.failure.call(data);
  1206. }
  1207.  
  1208. // todo? empty titles and descriptions should be okay
  1209. // if (data.id && !data.title && !data.desc) {
  1210. // this.unset(data.id);
  1211. // return YTMA.ajax.failure.call(data);
  1212. // }
  1213.  
  1214. return true;
  1215. },
  1216. populate: function (data, ignoreValidation) {
  1217. if (!ignoreValidation && !this.validate(data)) { return; }
  1218.  
  1219. this.set(data);
  1220.  
  1221. if (data.th) { this.helper.thumbnail(data); }
  1222.  
  1223. $$.s('.ytm_bd._' + YTMA.escapeId(data.id), function (el) {
  1224. var q;
  1225. el.innerHTML = '<span class="ytm_title">' + data.title + '</span>';
  1226. if (data.desc) {
  1227. q = $$.e('q', { className: 'ytm_descr ytm_block', textContent: data.desc }, el);
  1228. if (data.full) {
  1229. q.dataset.full = data.full;
  1230. q.title = 'Click to toggle the length of the description.';
  1231. q.addEventListener('dblclick', YTMA.external.helper.titleToggle, false);
  1232. }
  1233. }
  1234. });
  1235. },
  1236. dataFromStorage: function (site, id) {
  1237. if (this.db && this.db[site]) {
  1238. return this.db[site][id];
  1239. }
  1240. },
  1241. events: {
  1242. manualLoad: function (e) {
  1243. // console.log(this);
  1244. e.preventDefault();
  1245. YTMA.ajax.loadFromDataset(e.target.dataset);
  1246. }
  1247. }
  1248. };
  1249. YTMA.external.db = strg.grab(YTMA.external.version, {});
  1250.  
  1251. /** Database */
  1252. YTMA.DB = {
  1253. postInit: function () {
  1254. if (YTMA.user.preferences.yt_nocookie) {
  1255. YTMA.DB.sites.youtube.home = 'https://www.youtube-nocookie.com/';
  1256. YTMA.DB.sites.youtube.embed = 'https://www.youtube-nocookie.com/embed/%key';
  1257. }
  1258. },
  1259. extension: window.chrome && window.chrome.extension,
  1260. browser: {
  1261. pod: YTMA.reg.ios.test(navigator.userAgent),
  1262. ie: !!document.documentMode, // IE, basically | window.navigator.cpuClass
  1263. safari: Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0
  1264. },
  1265. views: {
  1266. getAllSiteRegExps: function () {
  1267. var regs = [];
  1268.  
  1269. $$.o(YTMA.DB.sites, function (k, site) {
  1270. if (site.reg) {
  1271. regs.push(site.reg);
  1272. }
  1273. });
  1274.  
  1275. return new RegExp('\\b' + regs.join('|'));
  1276. },
  1277. getAllSiteSelectors: function () {
  1278. var sels = [];
  1279.  
  1280. $$.o(YTMA.DB.sites, function (k, site) {
  1281. if (site.selector) {
  1282. sels.push(site.selector);
  1283. }
  1284. });
  1285.  
  1286. return sels.join();
  1287. },
  1288. getPlayerSources: function (siteName) {
  1289. return YTMA.DB.sources[siteName] || YTMA.DB.sources.iframe;
  1290. },
  1291. getToolbar: function (site) {
  1292. var bar = YTMA.DB.customToolbars[site] || {};
  1293.  
  1294. return {
  1295. ratio: bar.ratio === undefined ? true : bar.ratio,
  1296. size: bar.size === undefined ? true : bar.size
  1297. };
  1298. },
  1299. getPlayerDimmensions: function (ratio, size) {
  1300. return 'ytm_panel ytm_block ytm_panel-' + YTMA.DB.playerSize.ratios[ratio]
  1301. + ' ytm_panel-' + YTMA.DB.playerSize.sizes[size];
  1302. },
  1303. getPlayerQuality: function (quality) {
  1304. return YTMA.DB.qualities[quality] || YTMA.DB.qualities[360];
  1305. }
  1306. },
  1307. sites: { // supported sites - to add more also make a parser (if api is available) and add an item to sources (if necessary)
  1308. youtube: {
  1309. title: 'ytma!',
  1310. home: 'https://www.youtube.com/',
  1311. embed: 'https://www.youtube.com/embed/%key',
  1312. ajax: 'https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%key' + window.atob('JmtleT1BSXphU3lEVG5INkxzRERyVElYaFZTZWRQQjlyRHo1czBSczQzZnM='),
  1313. thumb: 'url(https://i3.ytimg.com/vi/%key/1.jpg)',
  1314. selector: 'a[href*="youtube."], a[href*="youtu.be/"]',
  1315. favicon: 'https://www.youtube.com/favicon.ico',
  1316. key: 'id',
  1317. reg: '(youtu)',
  1318. matcher: /(?:(?:(?:v\=|#p\/u\/\d*?\/)|(?:v\=|#p\/c\/[a-zA-Z0-9]+\/\d*?\/)|(?:embed\/)|(?:v\/)|(?:\.be\/))([A-Za-z0-9-_]{11}))/i,
  1319. https: true
  1320. },
  1321. vimeo: {
  1322. title: 'vimeo too!',
  1323. home: 'https://vimeo.com/',
  1324. embed: 'https://player.vimeo.com/video/%key?badge=0',
  1325. ajax: 'https://vimeo.com/api/v2/video/%key.json',
  1326. selector: 'a[href*="vimeo.com/"]',
  1327. favicon: 'https://f.vimeocdn.com/images_v6/favicon.ico',
  1328. key: 'id',
  1329. reg: '(vimeo)',
  1330. matcher: /(?:vimeo\.com\/(\d+))/i,
  1331. https: true
  1332. },
  1333. vine: {
  1334. title: 'vine me!',
  1335. home: 'https://vine.co/',
  1336. embed: 'https://vine.co/v/%key/embed/simple?audio=1',
  1337. ajaxExtension: true,
  1338. ajax: 'https://vine.co/oembed.json?url=https%3A%2F%2Fvine.co%2Fv%2F%key',
  1339. selector: 'a[href*="vine.co/"]',
  1340. favicon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABcklEQVQ4jX2SvyvFYRTGP+9NdzAYbpIkysA/ICnKoEwvA4vlUkoyGQzvoOiK4R0YbDLhDhbi9maWIjEqcScWkyRhMNzXcM/3On39OMt5nvOe85wfvSbGyH9mgs8Cq8AUkAMegU1gJVpXqfu3umpbwITiLUBB8HJGuryb4KMJ/izVvUMVnwNjwJPwGYCMkLL4zlT3XoUL0boD4FBNUhO4Fd9ogm9SRVmFn8Una79qgWuV2K3wvcLJdG3iL7TaiUocAI4Fn0vnHDBrgt+L1g2q3NoEl8CH4KHkMVr3CSwI7QeOTPA9WsAk/8AEvw+MSrwrWleuJQVfABZJWbTOZBTfVXg6lbgEHKjQTcL1BFngAWimeuH2aN2LvHUDV1JcjNbl0zdI9l0T2pDsboKvB7Yl/ga4X2+gku+AVqAC9AFzwLik5KN1xT8FRGQEOBL6yfdn2onWTZKyTDoQrSsB60KT4lNSh/1TQETmgQ1ZowQMy41+2BeLRXeRaKuHSAAAAABJRU5ErkJggg==',
  1341. key: 'id',
  1342. reg: '(vine)',
  1343. matcher: /(?:vine\.co\/v\/([A-Za-z0-9-_]{11}))/i
  1344. },
  1345. soundcloud: {
  1346. title: 'sound off!',
  1347. home: 'https://soundcloud.com/',
  1348. embed: 'https://w.soundcloud.com/player/?show_comments=false&url=%key',
  1349. ajax: 'https://soundcloud.com/oembed?format=json&url=%uri',
  1350. favicon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNp0UjFOw0AQnD3bCYaEoIgiUIQKikiAKKGgSAkFL0BQ0FEgUfECShpewB+QEE+gokDiBxGRiCCEBBzHd8ucLYGQ4Kw7+9azc7OzJ6oKnFYUxgD+W/DHYNA54GIooifTitYqg1okaPFff6BcTLF5fECYb5IJNJ3kMfUP2dxHPycwcQ0SliARocSGsA46YkKWQW0GS6BDAGlu5Myu84AgSmHKM/DY0NegwwR2+AJrSjBre5BaA1H7GFKpI729QHp9jqiaeTBn5mB7fWirjWB9B9JYBrwEMnop5d0zfD53YO9vYMY8SQcpZGkTZvsIstiivDfoaxfIxt8exYeXCLYOSOwlJZQ6twSpL0DfX4EoZtkCqc7/Njalbxnfdt+oSwhurkBnCWI/1FlgqgKNZ3JnZDSA3FFSpYdQAoF56tKNDmhOYTzJ3OSHXZkflJhYEyawcnH02HpmxZ+DrRJlgmacvrsHRmHln2tRnIiAy5WTLwEGAK4QoBQmtGHkAAAAAElFTkSuQmCC',
  1351. selector: 'a[href*="soundcloud.com/"]',
  1352. key: 'uri',
  1353. reg: '(soundcloud)',
  1354. matcher: /(?:\/\/(?:\bwww|m\.\b)?soundcloud\.com\/(.+?\/.+))/i,
  1355. https: true,
  1356. scroll: true
  1357. },
  1358. gfycat: {
  1359. title: 'gfycat meow!',
  1360. home: 'https://gfycat.com/',
  1361. embed: 'https://gfycat.com/iframe/%key',
  1362. ajax: 'https://gfycat.com/cajax/get/%key',
  1363. thumb: 'url(https://thumbs.gfycat.com/%key-poster.jpg)',
  1364. selector: 'a[href*="gfycat.com/"]',
  1365. favicon: 'https://gfycat.com/favicon.ico',
  1366. key: 'id',
  1367. reg: '(gfycat)',
  1368. matcher: /(?:gfycat\.com\/(?:(\b(?:[A-Z][a-z]*){3,}\b)))/i,
  1369. https: true,
  1370. scroll: true,
  1371. videoTag: true
  1372. },
  1373. streamable: {
  1374. title: 'streamable!',
  1375. home: 'https://streamable.com/',
  1376. embed: 'https://streamable.com/e/%key',
  1377. ajax: 'https://api.streamable.com/oembed.json?url=%uri',
  1378. thumb: 'url(https://cdn.streamable.com/image/%key.jpg)',
  1379. selector: 'a[href*="streamable.com/"]',
  1380. favicon: 'https://streamable.com/favicon.ico',
  1381. key: 'id',
  1382. reg: '(streamable\\.com)',
  1383. matcher: /(?:streamable\.com\/([A-Za-z0-9-_]+))/i,
  1384. https: true
  1385. },
  1386. imgur: {
  1387. title: 'imgur it!',
  1388. home: 'https://i.imgur.com/',
  1389. embed: 'https://i.imgur.com/%key',
  1390. thumb: 'url(https://i.imgur.com/%keyh.jpg)',
  1391. selector: 'a[href*=".gifv"]',
  1392. favicon: 'https://imgur.com/favicon.ico',
  1393. reg: '(\\.gifv$)|(imgur)',
  1394. matcher: /(?:imgur\.com\/(\w+)\.(?:gifv|mp4|webm))/i,
  1395. https: true,
  1396. scroll: true,
  1397. videoTag: true
  1398. },
  1399. html5: {
  1400. home: true,
  1401. title: 'html5 go!',
  1402. selector: 'a[href*=".webm"], a[href*=".mp4"]',
  1403. favicon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdlJREFUeNpcks9rE0EUx7+b3TRktxvbhUhrNm6SrUWtxxbxJ9iLl0JFKWg8WLD05EWhIuRieogXwYPX4tGb/gPSq/oP6KH+uG2MFaRJJXaTbp7fWbsl+uDDzLx533lv3ozmeyUM2VlSJxeISULygTwhr5Og1JDgOXlPrpLRMAxTIpLlfJa8Ipv/i56Re4lT07SYKIqQSh2eO0/eqYnujI35HF8O19jr9XBneRmt1je0d3ag63qy5ZIAvNObE+WKTJXKktENoVPsrCntdlsWFxbiNXNJyS3KdMUXxv8w6LzY7XZhmiYer9fBYOTzR5HL5XCzWsWxYhFnTs/gxcYGgiCAZVmOyhR5BVcm8nlZvbsi29+3ZdgGg4Gs1+viua4UJiZVpkiJeqo0VaIqxbGPSKfTORRduXQ59peLx5PyItWaj9yDQtmt21XYto1GoxGvbywtxaNhGHE3ab+V6L6aKYczPo7ZuTk8WnuIWq2G64vX4JU8zJw8BXXv5D21gx+xydrn7VEb/f0+tj5twa/4+Pz1C6Y4Oo6DZrOJdDrdYuxkIlKP+ZbZzoV7e8iyk8kD/9rdRXpkBJlMpsmDC/98I97pPF//AQUBl/2/jRvsm5b1kxmeJgJlfwQYAKZQxgzeI6/EAAAAAElFTkSuQmCC',
  1404. reg: '(\\.webm$)|(\\.mp4$)',
  1405. slim: true,
  1406. scroll: true,
  1407. videoTag: true
  1408. },
  1409. 'html5-audio': {
  1410. home: true,
  1411. title: 'hey, listen!',
  1412. selector: 'a[href*=".mp3"]',
  1413. reg: '(\\.mp3$)',
  1414. slim: true,
  1415. scroll: true
  1416. }
  1417. },
  1418. sources: {
  1419. iframe: function (data) {
  1420. var key = YTMA.DB.sites[data.site].key;
  1421.  
  1422. return [
  1423. {type: 'text/html', src: YTMA.DB.sites[data.site].embed.replace('%key', data[key]) }
  1424. ];
  1425. },
  1426. 'html5-audio': function (data) {
  1427. return [
  1428. {type: 'audio/mp3', src: data.uri}
  1429. ];
  1430. },
  1431. html5: function (data) {
  1432. // attaching the type as either mp4 or webm
  1433.  
  1434. if (/(?:webm)/.test(data.uri)) {
  1435. return [
  1436. {type: 'video/webm', src: data.uri}
  1437. ];
  1438. }
  1439.  
  1440. return [
  1441. {type: 'video/mp4', src: data.uri},
  1442. {type: 'video/webm', src: data.uri},
  1443. {type: 'video/ogg; codecs="theora, vorbis"', src: data.uri}
  1444. ];
  1445. },
  1446. imgur: function (data) {
  1447. var src = YTMA.DB.sites.imgur.embed.replace('%key', data.id);
  1448.  
  1449. return [
  1450. {type: 'video/webm', src: src + '.webm'},
  1451. {type: 'video/mp4', src: src + '.mp4'}
  1452. ];
  1453. },
  1454. gfycat: function (data) {
  1455. return [
  1456. {type: 'video/mp4', src: 'https://zippy.gfycat.com/' + data.id + '.mp4'},
  1457. {type: 'video/mp4', src: 'https://fat.gfycat.com/' + data.id + '.mp4'},
  1458. {type: 'video/mp4', src: 'https://giant.gfycat.com/' + data.id + '.mp4'},
  1459. {type: 'video/webm', src: 'https://zippy.gfycat.com/' + data.id + '.webm'},
  1460. {type: 'video/webm', src: 'https://fat.gfycat.com/' + data.id + '.webm'},
  1461. {type: 'video/webm', src: 'https://giant.gfycat.com/' + data.id + '.webm'}
  1462. ];
  1463. },
  1464. youtube: function (data, attrs) {
  1465. var params = '?html5=1&version=3&modestbranding=1&theme=dark&color=white&showinfo=1&vq=' + attrs.quality
  1466. + '&iv_load_policy=' + YTMA.user.preferences.yt_annotation
  1467. + '&start=' + attrs.start
  1468. + '&volume=' + YTMA.user.preferences.yt_volume;
  1469.  
  1470. return [
  1471. {type: 'text/html', src: YTMA.DB.sites.youtube.embed.replace('%key', data.id) + params}
  1472. ];
  1473. }
  1474. },
  1475. customToolbars: {
  1476. vine: {
  1477. ratio: false,
  1478. size: true
  1479. },
  1480. soundcloud: {
  1481. ratio: false,
  1482. size: false
  1483. }
  1484. },
  1485. playerSize: {
  1486. ratios: {
  1487. 1: 'sd',
  1488. 2: 'hd',
  1489. 3: 'pr'
  1490. },
  1491. sizes: {
  1492. 0 : 'h',
  1493. 240 : 's',
  1494. 360 : 'm',
  1495. 480 : 'l',
  1496. 720 : 'xl'
  1497. },
  1498. aspects: {
  1499. 1: 4 / 3,
  1500. 2: 16 / 9,
  1501. 3: 16 / 9
  1502. }
  1503. },
  1504. qualities: {
  1505. 240 : 'small',
  1506. 360 : 'medium',
  1507. 480 : 'large',
  1508. 720 : 'hd720',
  1509. 1080 : 'hd1080',
  1510. 1081 : 'highres'
  1511. }
  1512. // videoTypes: (function () {
  1513. // var v = document.createElement('video');
  1514.  
  1515. // return {
  1516. // ogg: !!v.canPlayType('video/ogg; codecs="theora, vorbis"'),
  1517. // webm: !!v.canPlayType('video/webm'),
  1518. // mp4: !!v.canPlayType('video/mp4')
  1519. // };
  1520. // }()),
  1521. };
  1522.  
  1523. /** U I CLASS
  1524. * Class for the player controls
  1525. */
  1526. YTMA.UI = function (ytma) {
  1527. this.ytmx = ytma;
  1528.  
  1529. this.play = new YTMA.Player(this.ytmx);
  1530. this.open = false;
  1531. this.selected = { size: null, ratio: null };
  1532.  
  1533. this.trigger = ytma.spn;
  1534. this.projector = $$.e('div', {className: 'ytm_projector ytm_none ytm_block ytm_normalize ytm_sans'});
  1535. this.control = $$.e('ul', {className: 'ytm_control ytm_sans'});
  1536. this.customBar = YTMA.DB.views.getToolbar(this.ytmx.data.site);
  1537. this.controlBar();
  1538. };
  1539.  
  1540. YTMA.UI.ratios = {
  1541. SD: 1,
  1542. HD: 2,
  1543. PORTRAIT: 3
  1544. };
  1545.  
  1546. YTMA.UI.sizes = {
  1547. HIDDEN: 0,
  1548. S: 240,
  1549. M: 360,
  1550. L: 480,
  1551. X: 720
  1552. };
  1553.  
  1554. /** Trigger is the VAR element */
  1555. YTMA.UI.createFromTrigger = function (t) {
  1556. console.log('createFromTrigger');
  1557. if (t.hasAttribute('data-ytmuid') && !YTMA.set[t.dataset.ytmuid]) {
  1558. console.log('createFromTrigger-new');
  1559. YTMA.addToSet(new YTMA()._reactivate(t));
  1560. }
  1561. console.log('createFromTrigger-ui');
  1562. return YTMA.set[t.dataset.ytmuid].getUI();
  1563. };
  1564.  
  1565. YTMA.UI.events = {
  1566. $fire: {
  1567. settings: function () {
  1568. YTMA.user.events.formToggle();
  1569. },
  1570. close: function () {
  1571. if (YTMA.DB.sites[this.ytmx.data.site].scroll) {
  1572. console.log('events.close-1');
  1573. this.hideAllPlayers();
  1574. } else {
  1575. console.log('events.close-2');
  1576. this.ytmx.disableOpenOnScroll();
  1577. this.hidePlayer();
  1578. }
  1579. },
  1580. ratio: function (li) {
  1581. var n = parseInt(li.dataset.value, 10);
  1582. this.play.dimmensions(n);
  1583. this.markSelected(li, 'ratio');
  1584. },
  1585. size: function (li) {
  1586. var n = parseInt(li.dataset.value, 10);
  1587. this.play.dimmensions(null, n);
  1588. this.markSelected(li, 'size');
  1589. }
  1590. },
  1591. videoBar: function (e) {
  1592. var el = e.target, t;
  1593.  
  1594. if (el.tagName.toLowerCase() === 'li' && el.dataset && el.dataset.type) {
  1595. t = el.dataset.type;
  1596. if (YTMA.UI.events.$fire[t]) {
  1597. YTMA.UI.events.$fire[t].call(this, el);
  1598. }
  1599. }
  1600. }
  1601. };
  1602.  
  1603. YTMA.UI.prototype = {
  1604. constructor: YTMA.UI,
  1605. resetViewSize: function () {
  1606. this.play.dimmensions();
  1607. this.setControlBarSize(this.play.attrs.size);
  1608. },
  1609. showOnScroll: function (el) {
  1610. if (!this.open && this.ytmx.canScroll() && this.ytmx.isBelow(el)) {
  1611. this.showPlayer();
  1612. }
  1613. },
  1614. showPlayer: function () {
  1615. this.open = true;
  1616.  
  1617. this.trigger.classList.add('ytm_none');
  1618. this.projector.classList.remove('ytm_none');
  1619.  
  1620. this.attachPlayPanel();
  1621. this.play.switchOn();
  1622.  
  1623. if (YTMA.user.preferences.focus) {
  1624. document.location.hash = '#' + this.ytmx.container.id;
  1625. }
  1626. },
  1627. hidePlayer: function () {
  1628. this.open = false;
  1629.  
  1630. this.play.switchOff();
  1631. this.trigger.classList.remove('ytm_none');
  1632. this.projector.classList.add('ytm_none');
  1633. },
  1634. attachPlayPanel: function () {
  1635. if (!this.play.panel.parentNode) {
  1636. // console.log('attaching display panel');
  1637. this.projector.appendChild(this.play.panel);
  1638. }
  1639. },
  1640. hideAllPlayers: function () {
  1641. var group = YTMA.collect(this.ytmx.data.id);
  1642. console.log('closing all', this.ytmx.data.id, group.length);
  1643. group.forEach(function (y) {
  1644. y.disableOpenOnScroll();
  1645. y.getUI().hidePlayer();
  1646. });
  1647. },
  1648. setControlBarSize: function (size) {
  1649. this.markSelected(this.control.querySelector('li[data-value="' + size + '"]'), 'size');
  1650. },
  1651. controlBar: function () {
  1652. var f = document.createDocumentFragment();
  1653.  
  1654. $$.a(f,
  1655. this.customBar.ratio ? this.buildList('ytm_ratios', [
  1656. {type: 'ratio', text: '4:3', value: YTMA.UI.ratios.SD, title: 'SD'},
  1657. {type: 'ratio', text: '16:9', value: YTMA.UI.ratios.HD, title: 'Landscape'},
  1658. {type: 'ratio', text: '9:16', value: YTMA.UI.ratios.PORTRAIT, title: 'Portrait'}]) : null,
  1659. this.customBar.size ? this.buildList('ytm_sizes', [
  1660. {type: 'size', text: '\u00D8', value: YTMA.UI.sizes.HIDDEN, title: 'Hide the video.'},
  1661. {type: 'size', text: 'S', value: YTMA.UI.sizes.S, title: '240p'},
  1662. {type: 'size', text: 'M', value: YTMA.UI.sizes.M, title: '360p'},
  1663. {type: 'size', text: 'L', value: YTMA.UI.sizes.L, title: '480p'},
  1664. {type: 'size', text: 'X', value: YTMA.UI.sizes.X, title: '720p'}]) : null,
  1665. this.buildList('ytm_options', [
  1666. strg.on ? {type: 'settings', text: '!', title: 'YTMA Settings'} : null,
  1667. {type: 'close', text: '\u00D7', title: 'Close the video.'}])
  1668. );
  1669.  
  1670. this.control.appendChild(f);
  1671. this.control.addEventListener('click', YTMA.UI.events.videoBar.bind(this), false);
  1672. this.projector.appendChild(this.control);
  1673. this.ytmx.container.insertBefore(this.projector, this.trigger.nextSibling);
  1674. },
  1675. markSelected: function (el, type) {
  1676. el.id = type + this.ytmx.data.uid;
  1677. try {
  1678. this.selected[type].removeAttribute('id');
  1679. } catch (e) {}
  1680. this.selected[type] = el;
  1681. },
  1682. buildList: function (className, elements) {
  1683. var li = $$.e('li'),
  1684. ul = $$.e('ul', {className: className}, li),
  1685. f = document.createDocumentFragment(),
  1686. i,
  1687. e;
  1688.  
  1689. for (i = 0; i < elements.length; i++) {
  1690. e = elements[i];
  1691. if (e) {
  1692. f.appendChild(this.li(e.type, e.text, e.value, e.title));
  1693. }
  1694. }
  1695. ul.appendChild(f);
  1696. return li;
  1697. },
  1698. li: function (type, txt, value, title) {
  1699. var l = $$.e('li', {_type: type, textContent: txt, _value: value, title: title});
  1700. if ((type === 'size' && this.play.attrs.size === value) || (type === 'ratio' && this.play.attrs.ratio === value)) {
  1701. this.markSelected(l, type);
  1702. }
  1703. return l;
  1704. }
  1705. };
  1706.  
  1707. /** P L A Y E R CLASS
  1708. * @param parent YTMA instance
  1709. */
  1710. YTMA.Player = function (parent) {
  1711. this.parent = parent;
  1712.  
  1713. this.mode = 'off';
  1714.  
  1715. this.attrs = {
  1716. sources: null,
  1717. quality: YTMA.DB.views.getPlayerQuality(YTMA.user.preferences.quality),
  1718. size: null,
  1719. ratio: null,
  1720. start: this.time(),
  1721. type: null
  1722. };
  1723.  
  1724. this.attrs.sources = YTMA.DB.views.getPlayerSources(parent.data.site)(parent.data, this.attrs);
  1725.  
  1726. // todo improve type/media
  1727. this.attrs.type = this.findType();
  1728. this.media = YTMA.Player.makeMedia[this.attrs.type](this);
  1729.  
  1730. this.channel = $$.e('div', {className: 'ytm_panel_channel ytm_block'}, this.media, true);
  1731. this.switcher = $$.e('div', {className: 'ytm_panel_switcher ytm_panel_size ytm_block ytm_' + this.attrs.type, _ytmuid: this.parent.data.uid, _standby: true});
  1732. this.panel = $$.e('div', {className: 'ytm_panel ytm_block'}, this.switcher, true);
  1733.  
  1734. if (parent.data.site === 'soundcloud' && YTMA.reg.extra.soundcloud.playlist.test(parent.anchor.href)) {
  1735. this.media.classList.add('ytm_soundcloud-playlist');
  1736. this.switcher.classList.add('ytm_soundcloud-playlist');
  1737. }
  1738.  
  1739. this.dimmensions(YTMA.user.preferences.ratio, YTMA.user.preferences.size);
  1740. };
  1741.  
  1742. YTMA.Player.css = {
  1743. item: function (key, value) {
  1744. if (isNumber(value)) {
  1745. value += 'px';
  1746. }
  1747.  
  1748. return '\t' + key + ': ' + value + ';\n';
  1749. },
  1750. iter: function (css, cssEntries) {
  1751. $$.o(cssEntries, function (key, value) {
  1752. css.push(YTMA.Player.css.item(key, value));
  1753. });
  1754. css.push('}');
  1755. },
  1756. generator: function () {
  1757. var css = [];
  1758.  
  1759. $$.o(this.sizes, function (size, sizes) {
  1760. $$.o(sizes, function (dimm, keys) {
  1761. css.push('\n.ytm_panel-' + size + '.ytm_panel-' + dimm + ' .ytm_panel_size {\n');
  1762. YTMA.Player.css.iter(css, keys);
  1763. });
  1764. });
  1765.  
  1766. // add site overrides
  1767. $$.o(this.sites, function (site, data) {
  1768. $$.o(data, function (setting, keys) {
  1769. if (setting === 'all') {
  1770. css.push('\n.ytm_site_' + site + ' .ytm_panel_size {\n');
  1771. } else {
  1772. css.push('\n.ytm_site_' + site + ' .ytm_panel-' + setting + ' .ytm_panel_size {\n');
  1773. }
  1774. YTMA.Player.css.iter(css, keys);
  1775. });
  1776. });
  1777.  
  1778. return css.join('');
  1779. },
  1780. sizes: (function () {
  1781. var merge = {};
  1782.  
  1783. $$.o(YTMA.DB.playerSize.sizes, function (num, size) {
  1784. if (num >= 0) {
  1785. merge[size] = {};
  1786.  
  1787. $$.o(YTMA.DB.playerSize.ratios, function (k, ratio) {
  1788. if (ratio === 'pr') {
  1789. var w = Math.floor(num * 0.95); // smaller than the normal sizes
  1790. merge[size][ratio] = {
  1791. width: w,
  1792. height: Math.floor(w * YTMA.DB.playerSize.aspects[k])
  1793. };
  1794. } else {
  1795. merge[size][ratio] = {
  1796. width: Math.floor(num * YTMA.DB.playerSize.aspects[k]),
  1797. height: num
  1798. };
  1799. }
  1800. });
  1801. }
  1802. });
  1803.  
  1804. return merge;
  1805. }()),
  1806. sites: { // custom sizes per site
  1807. soundcloud: {
  1808. all: {
  1809. height: '118px !important'
  1810. }
  1811. },
  1812. vine: {
  1813. s: {
  1814. width: 240,
  1815. height: 240
  1816. },
  1817. m: {
  1818. width: 360,
  1819. height: 360
  1820. },
  1821. l: {
  1822. width: 480,
  1823. height: 480
  1824. },
  1825. xl: {
  1826. width: 720,
  1827. height: 720
  1828. }
  1829. }
  1830. }
  1831. };
  1832.  
  1833. YTMA.Player.makeMedia = {
  1834. $css: function (type) {
  1835. return 'ytm_panel_media ytm_panel_size ytm_block ytm_' + type;
  1836. },
  1837. video: function (player) {
  1838. var video = $$.e('video', {
  1839. controls: true,
  1840. autoplay: false,
  1841. loop: true,
  1842. className: this.$css('video'),
  1843. $allowscriptaccess: true,
  1844. preload: 'metadata'
  1845. }), links = [];
  1846.  
  1847. player.attrs.sources.forEach(function (source) {
  1848. $$.e('source', {src: source.src, $type: source.type}, video);
  1849.  
  1850. links.push('<a href="' + source.src + '">' + source.src + '</a>');
  1851. });
  1852.  
  1853. $$.e('p', {innerHTML: 'Could not load source(s): ' + links.join('<br />')}, video);
  1854.  
  1855. return video;
  1856. },
  1857. iframe: function (player) {
  1858. return $$.e('iframe', {
  1859. $allowfullscreen: true,
  1860. // $sandbox: 'allow-same-origin allow-scripts allow-popups',
  1861. $type: player.attrs.sources[0].type,
  1862. src: player.attrs.sources[0].src,
  1863. className: this.$css('iframe')
  1864. });
  1865. },
  1866. audio: function (player) {
  1867. return $$.e('audio', {
  1868. src: player.attrs.sources[0].src,
  1869. $type: player.attrs.sources[0].type
  1870. });
  1871. }
  1872. };
  1873.  
  1874. YTMA.Player.prototype = {
  1875. constructor: YTMA.Player,
  1876. dimmensions: function (ratio, size) {
  1877. this.attrs.ratio = isNumber(ratio) ? ratio : this.attrs.ratio;
  1878. this.attrs.size = isNumber(size) ? size : this.attrs.size;
  1879. this.panel.className = YTMA.DB.views.getPlayerDimmensions(this.attrs.ratio, this.attrs.size);
  1880. },
  1881. time: function () {
  1882. try {
  1883. var m = this.parent.data.uri.match(YTMA.reg.time).slice(1, 3);
  1884. return ((+m[0] || 0) * 60) + (+m[1] || 0);
  1885. } catch (e) { return 0; }
  1886. },
  1887. findType: function () {
  1888. if (this.parent.data.site === 'html5-audio') { return 'audio'; }
  1889. if (YTMA.DB.sites[this.parent.data.site].videoTag) { return 'video'; }
  1890. return 'iframe';
  1891. },
  1892. switchOff: function () {
  1893. // console.log('removed media');
  1894.  
  1895. if (this.media.pause) {
  1896. console.log('pausing');
  1897. this.media.pause();
  1898. }
  1899.  
  1900. try {
  1901. this.switcher.removeChild(this.channel);
  1902. } catch (e) {
  1903. // console.error(e);
  1904. }
  1905. this.mode = 'off';
  1906. },
  1907. switchOn: function () {
  1908. if (this.attrs.size === 0) {
  1909. this.attrs.size = YTMA.user.preferences.size;
  1910. this.parent.ui.resetViewSize();
  1911. }
  1912. // console.log('switch to media');
  1913. this.switcher.appendChild(this.channel);
  1914. this.switcher.dataset.standby = false;
  1915. this.mode = 'on';
  1916. },
  1917. switchStandby: function () {
  1918. // console.log('switch to standby');
  1919. this.switchOff();
  1920. this.switcher.dataset.standby = true;
  1921. this.mode = 'standby';
  1922. },
  1923. isStandby: function () {
  1924. return this.mode === 'standby';
  1925. }
  1926. };
  1927.  
  1928. YTMA.prototype = {
  1929. constructor: YTMA,
  1930. getUI: function () {
  1931. if (!this.ui) {
  1932. this.ui = new YTMA.UI(this);
  1933. }
  1934.  
  1935. return this.ui;
  1936. },
  1937. setup: function () {
  1938. var site = YTMA.DB.sites[this.data.site];
  1939.  
  1940. if (site) {
  1941. this.spn.title = site.title || 'ytma!';
  1942.  
  1943. if (site.thumb) {
  1944. this.spn.style.backgroundImage = site.thumb.replace('%key', this.data.id);
  1945. }
  1946.  
  1947. if (site.https) {
  1948. this.anchor.href = this.anchor.href.replace('http:', 'https:');
  1949. }
  1950. }
  1951.  
  1952. try {
  1953. this.dom.custom[this.data.site].call(this);
  1954. } catch (e) {}
  1955.  
  1956. this.dom.link.call(this);
  1957. this.dom.span.call(this);
  1958. },
  1959. disableOpenOnScroll: function () {
  1960. this.anchor.dataset.ytmscroll = false;
  1961. },
  1962. canScroll: function () {
  1963. return this.anchor.dataset.ytmscroll === 'true';
  1964. },
  1965. isBelow: function (link) {
  1966. return YTMA.Scroll.compare(this.anchor, link) < 1;
  1967. },
  1968. dom: {
  1969. custom: { // modifies interface according to site
  1970. youtube: function () {
  1971. this.spn.addEventListener('mouseenter', YTMA.events.thumb.start, false);
  1972. this.spn.addEventListener('mouseleave', YTMA.events.thumb.stop, false);
  1973. this.anchor.href = this.anchor.href.replace('youtu.be/', 'youtube.com/watch?v=');
  1974. }
  1975. },
  1976. link: function () {
  1977. if (this.anchor.getElementsByTagName('img').length === 0) {
  1978. this.anchor.className += ' ytm_link ytm_link_' + this.data.site + ' ';
  1979. }
  1980. this.anchor.dataset.ytmid = this.data.id;
  1981. this.anchor.dataset.ytmuid = this.data.uid;
  1982. this.anchor.dataset.ytmsid = this.data.sid;
  1983. this.anchor.title = 'Visit the video page.';
  1984. this.anchor.parentNode.insertBefore(this.container, this.anchor.nextSibling);
  1985. },
  1986. span: function () {
  1987. var f = document.createDocumentFragment(),
  1988. site = YTMA.DB.sites[this.data.site];
  1989.  
  1990. $$.e('span', {className: 'ytm_init ytm_label ytm_sans ytm_box', textContent: this.spn.title}, this.spn);
  1991. $$.e('var', {className: 'ytm_label ytm_box', _ytmid: this.data.id, _ytmuid: this.data.uid, _ytmsid: this.data.sid, _ytmsite: this.data.site, textContent: '\u25B6'}, this.spn);
  1992.  
  1993. this.spn.title = 'Watch now!';
  1994. f.appendChild(this.spn);
  1995.  
  1996. if (site.ajax) { f.appendChild(this.dom.dataLoadLink.call(this)); }
  1997. if (site.slim) { this.container.classList.add('ytm_site_slim'); }
  1998. if (site.scroll) { this.anchor.classList.add('ytm_scroll'); }
  1999.  
  2000. this.container.appendChild(f);
  2001. },
  2002. dataLoadLink: function () {
  2003. var a, s;
  2004. s = $$.e('span', {className: 'ytm_bd ytm_normalize ytm_manual _' + this.data.sid});
  2005. a = $$.e('a', {
  2006. className: 'ytm_title',
  2007. textContent: 'Load description.',
  2008. href: '#',
  2009. title: 'Load this video\'s description.',
  2010. _ytmid: this.data.id,
  2011. _ytmsite: this.data.site,
  2012. _ytmuri: this.data.uri,
  2013. _ytmdescription: 'true'
  2014. });
  2015. return $$.a(s, a);
  2016. }
  2017. }
  2018. };
  2019.  
  2020. /**
  2021. * Creates a new YTMA from the given attributes
  2022. * @String|Number id Unique ID
  2023. * @String site Website eg: youtube, vimeo
  2024. * @HTMLAnchorElement a Anchor element
  2025. */
  2026. YTMA.prototype._new = function (id, site, a) {
  2027. var uid = YTMA.escapeId(id + '_' + (YTMA.num += 1));
  2028.  
  2029. this.data = {
  2030. id: id,
  2031. uid: YTMA.escapeId(uid), // unique id
  2032. sid: YTMA.escapeId(id), // shared id
  2033. site: site,
  2034. uri: a.href
  2035. };
  2036.  
  2037. this.ui = null;
  2038.  
  2039. if (!a.hasAttribute('data-ytmscroll')) { a.dataset.ytmscroll = true; }
  2040.  
  2041. this.anchor = a;
  2042.  
  2043. this.spn = $$.e('span', {className: 'ytm_trigger ytm_block ytm_normalize ytm_sans', _ytmid: this.data.id, _ytmsite: this.data.site});
  2044. this.container = $$.e('div', {id: 'w' + this.data.uid, className: 'ytm_spacer ytm_block ytm_site_' + this.data.site});
  2045.  
  2046. return this;
  2047. };
  2048.  
  2049. /**
  2050. * Recreates a YTMA object from a trigger element
  2051. * @HTMLElement
  2052. */
  2053. YTMA.prototype._reactivate = function (trigger) {
  2054. var id = trigger.dataset.ytmid,
  2055. a = document.querySelector('a[data-ytmuid="' + trigger.dataset.ytmuid + '"]');
  2056.  
  2057. this.data = {
  2058. id: id,
  2059. uid: trigger.dataset.ytmuid,
  2060. sid: trigger.dataset.ytmsid,
  2061. site: trigger.dataset.ytmsite,
  2062. uri: a.href
  2063. };
  2064.  
  2065. this.ui = null;
  2066. this.anchor = a;
  2067. this.spn = trigger.parentElement;
  2068. this.container = this.spn.parentElement;
  2069.  
  2070. return this;
  2071. };
  2072.  
  2073. /** S C R O L L CLASS
  2074. * Window-Scroll Event Helper
  2075. */
  2076. YTMA.Scroll = (function () {
  2077.  
  2078. function Scroll(selector, cb, delay) {
  2079. this.selector = selector;
  2080. this.cb = cb;
  2081.  
  2082. // console.log('YTMA.Scroll Monitor: ', selector);
  2083. this.bound = Scroll.debounce(this.monitor.bind(this), delay || 500);
  2084.  
  2085. this.bound();
  2086. window.addEventListener('scroll', this.bound, false);
  2087. }
  2088.  
  2089. Scroll.debounce = function (fn, delay) {
  2090. var timeout;
  2091. delay = delay || 250;
  2092.  
  2093. return function () {
  2094. var self = this, args = arguments, timed;
  2095.  
  2096. timed = function () {
  2097. timeout = null;
  2098. fn.apply(self, args);
  2099. };
  2100.  
  2101. window.clearTimeout(timeout);
  2102. timeout = window.setTimeout(timed, delay);
  2103. };
  2104. };
  2105.  
  2106. Scroll.visible = function (el) {
  2107. var bound = el.getBoundingClientRect();
  2108. return (bound.top >= 0 && bound.top <= document.documentElement.clientHeight);
  2109. };
  2110.  
  2111. Scroll.visibleAll = function (el, offset) {
  2112. var bound = el.getBoundingClientRect(),
  2113. height = document.documentElement.clientHeight;
  2114. offset = isNumber(offset) ? +offset : 0;
  2115. return ((bound.bottom + offset >= 0)
  2116. && (bound.top <= height + offset || bound.bottom <= height - offset));
  2117. };
  2118.  
  2119. /** Returns 1, 0, -1 when el1 is above, exactly the same, or below el2 */
  2120. Scroll.compare = function (el1, el2) {
  2121. var a = el1.getBoundingClientRect().y,
  2122. b = el2.getBoundingClientRect().y;
  2123.  
  2124. if (a < b) { return 1; }
  2125. if (a === b) { return 0; }
  2126. return -1;
  2127. };
  2128.  
  2129. Scroll.prototype = {
  2130. stop: function () {
  2131. // console.log('clear scroll: ', this.selector);
  2132. window.removeEventListener('scroll', this.bound);
  2133. },
  2134. monitor: function () {
  2135. $$.s(this.selector, this.cb);
  2136. }
  2137. };
  2138.  
  2139. return Scroll;
  2140.  
  2141. }());
  2142.  
  2143. YTMA.main();
  2144.  
  2145. }());