Anime Lighter

Filter for anime trackers [planning for many]

  1. // ==UserScript==
  2. // @name Anime Lighter
  3. // @namespace horc.net
  4. // @version 3.7.1
  5. // @description Filter for anime trackers [planning for many]
  6. // @author RandomClown @ HoRC
  7. // @copyright © 2015
  8. // @homepage http://git.horc.net
  9. // @icon https://bitbucket.org/horc/anime-lighter/raw/master/img/Logo.png
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_log
  12. // @require http://code.jquery.com/jquery-2.1.3.min.js
  13. // @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
  14. // @match http://www.nyaa.se/*
  15. // @match http://horriblesubs.info
  16. // ==/UserScript==
  17.  
  18. var greasyfork = true;
  19.  
  20. //
  21. //
  22. // Source code available: https://bitbucket.org/horc/anime-lighter/
  23. //
  24. //
  25.  
  26. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  27. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  28. /// <reference path="Class AnimeFilter.js" />
  29. /// <reference path="Class LighterUI.js" />
  30. /// <reference path="Class Run.js" />
  31. /// <reference path="Class StoreUpdater.js" />
  32. /// <reference path="Standard.js" />
  33.  
  34. var HORC_NAME = 'Anime Lighter Beta';
  35.  
  36.  
  37.  
  38. // Wait for ready
  39. new function () {
  40. var runonce = 0;
  41.  
  42. function complete() {
  43. if (document.readyState === 'complete') {
  44. document.removeEventListener('DOMContentLoaded', complete, false);
  45. window.removeEventListener('load', complete, false);
  46. if (runonce++) return;
  47. setTimeout(ready);
  48. } else {
  49. document.addEventListener('DOMContentLoaded', complete, false);
  50. window.addEventListener('load', complete, false);
  51. }
  52. }
  53. complete();
  54. }
  55.  
  56. // Now ready
  57. function ready() {
  58. new StoreUpdater('horc-animes', '1.0'); // <-- refrain from updating this one
  59. new StoreUpdater('horc-position', '1.1');
  60. new StoreUpdater('horc-style', '1.0');
  61. if (document.horc_updated) {
  62. location.reload(true);
  63. return;
  64. }
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80. run_animelighter();
  81. }
  82.  
  83. // Run main module
  84. function run_animelighter() {
  85. // Load site mod
  86. var mod = document.mod;
  87.  
  88. delete document.readymods;
  89. console.log(' Host: ' + window.location.host);
  90.  
  91. mod.createpositions();
  92.  
  93. new MainUI(mod.style);
  94.  
  95. new AnimeFilter(mod.filteroptions);
  96.  
  97. mod.run();
  98. }
  99. /// Dependencies:
  100. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  101. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  102.  
  103.  
  104. // Get Anime - Generic
  105. //
  106. // Parse an episode text for the anime name
  107. // Works for most things
  108. function default_getanime(jqe) {
  109.  
  110. var str = jqe.text();
  111.  
  112. return smartget(str);
  113. }
  114.  
  115.  
  116. ////////////////////////////////////////
  117. //// Useful Functions
  118.  
  119. function smartget(str) {
  120.  
  121.  
  122. // Make it sane
  123. var str = trimws(str.replace(/_/g, ' ').replace(/\s\s+/g, ' '));
  124.  
  125. if (!str.length) throw 'Expected a string';
  126.  
  127. // Get rid of leading subber
  128. if (-1 < str.charAt(0).search(/[\[\(\{]/)) str = trimws(consumetag(str));
  129.  
  130. // Get rid of next tag & beyond
  131. str = trimws(str.substr(0, str.search(/[\[\(\{]/)));
  132.  
  133. // Get rid of episode number[s] of format
  134. str = trimws(consumenumber(str));
  135.  
  136. if (!str.length) throw 'Couldn\'t get the name';
  137.  
  138. return str;
  139. }
  140.  
  141. // Consume Tag
  142. //
  143. // Gets rid of 1 tag: [subber], [1080p], [etc]
  144. function consumetag(str) {
  145.  
  146.  
  147. var mode = false, done = false;
  148. var count = 0;
  149. var s = 0, e = 0;
  150.  
  151. for (var i = 0; i < str.length; ++i) {
  152. if (done) break;
  153. switch (str.charAt(i)) {
  154. case '[':
  155. case '(':
  156. case '{':
  157. mode = true;
  158. count++;
  159. break;
  160. case ']':
  161. case ')':
  162. case '}':
  163. count--;
  164. if (mode && !count) done = true;
  165. break;
  166. default:
  167. break;
  168. }
  169. if (mode) e++;
  170. else s++, e++;
  171. }
  172.  
  173. return str.substr(0, s) + str.substr(e);
  174. }
  175.  
  176. // Consume Number
  177. //
  178. // Gets rid of the episode number
  179. function consumenumber(str) {
  180.  
  181.  
  182. // Consumes numbers with a tack before
  183. function cnum_tack(str) {
  184. for (var s = 0; s < str.length ; ++s) {
  185. var hitnum = false;
  186.  
  187. if (str.charAt(s) === '-' || str.charAt(s) === '‒') {
  188. var n = s + 1;
  189.  
  190. // Consume ws
  191. for (; n < str.length; ++n) if (!isws(str.charAt(n))) break;
  192.  
  193. // Consume number
  194. for (; n < str.length; ++n) {
  195. if (-1 < str.charAt(n).search(/\d/)) hitnum = true;
  196. break;
  197. }
  198.  
  199. if (hitnum) {
  200. return str.substr(0, s);
  201. }
  202. }
  203. }
  204. return str;
  205. }
  206.  
  207. // Consumes numbers without a tack before
  208. function cnum_none(str) {
  209. var digits = 0;
  210. var i = 0;
  211. for (i = str.length - 1; 0 <= i; --i) {
  212. if (-1 < str.charAt(i).search(/\d/)) digits++;
  213. if (str.charAt(i).search(/\d/) === -1) break;
  214. }
  215.  
  216. if (i === 0) return str; // reached the end
  217.  
  218. if (1 < digits && isws(str.charAt(i))) return str.substr(0, i); // lone number & at least 2 digits
  219.  
  220. return str; // some mixed number like: S2
  221. }
  222.  
  223. str = cnum_tack(str);
  224. str = cnum_none(str);
  225.  
  226. return str;
  227. }
  228.  
  229. // Trim Whitespace
  230. //
  231. // Gets rid of beginning & ending whitespace
  232. function trimws(str) {
  233.  
  234.  
  235. var mode = false, done = false;
  236. var count = 0;
  237. var s = 0, e = 0;
  238.  
  239. for (s = 0; s < str.length; ++s) {
  240. if (str.charAt(s) === ' ') continue;
  241. if (str.charAt(s) === '\t') continue;
  242. if (str.charAt(s) === '\r') continue;
  243. if (str.charAt(s) === '\n') continue;
  244. break;
  245. }
  246. for (e = str.length - 1; 0 <= e; --e) {
  247. if (str.charAt(e) === ' ') continue;
  248. if (str.charAt(e) === '\t') continue;
  249. if (str.charAt(e) === '\r') continue;
  250. if (str.charAt(e) === '\n') continue;
  251. break;
  252. }
  253.  
  254. return str.substr(s, e - s + 1);
  255. }
  256.  
  257. function isws(str) {
  258. for (var i = 0; i < str.length; ++i) if (!(str.charAt(i) === ' ' || str.charAt(i) === '\t' || str.charAt(i) === '\n' || str.charAt(i) === '\r' || str.charAt(i) === ' ')) return false;
  259. return true;
  260. }
  261. /// Dependencies:
  262. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  263. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  264. /// <reference path="_ Host All.js" />
  265.  
  266.  
  267. if ('horriblesubs.info' === window.location.host) {
  268. var options = {};
  269.  
  270. options.style = "/* Configuration for horriblesubs.info */\n\n.horc-sidebar {\n\tfont-size: 0.75em;\n\twidth: 10em;\n}\n\n\t.horc-sidebar:hover {\n\t\twidth: 44em;\n\t}\n\n\t.horc-sidebar #horc-mainui {\n\t\twidth: 40em;\n\t}\n\n.horc-ep-droppanel {\n\tfont-size: 1.5em;\n}\n\n\n\n/* Original episode list */\n\n.horc-episode-orig.watch {\n\tbackground-color: rgba(0, 255, 0, 0.25);\n}\n\n.horc-episode-orig.drop {\n\tcolor: rgba(0, 0, 0, 0.15);\n}\n\n.horc-episode-orig.hide {\n\tdisplay: none;\n\tvisibility: hidden;\n}\n\n.horc-episode-orig.dragging {\n\tfont-weight: bolder;\n}\n\n\n\n/* Main UI */\n\n#horc-statusbar {\n\tfont-size: 1.2em;\n\ttext-align: center;\n\tbackground-color: rgba(200,255,200, 0.9);\n\twidth: 100%;\n\tposition: fixed;\n\tpadding: 1em 0 1em 0;\n\tmargin: 0;\n\tleft: 0;\n\tbottom: 0;\n\tborder-radius: 1em;\n}\n\n\t#horc-statusbar > div {\n\t\tmargin: 0 1em 0 1em;\n\t}\n\n.horc-slot {\n\ttext-align: center;\n\twidth: 100%;\n}\n\n#horc-mainui {\n\ttext-align: center;\n\tbackground-color: rgba(230, 230, 230, 0.96);\n\tuser-select: none;\n}\n\n\t#horc-mainui > div {\n\t\tmargin: 1em 0;\n\t\tcursor: initial;\n\t\tuser-select: initial;\n\t}\n\n\t#horc-mainui h2 {\n\t\tcolor: #000000;\n\t\tpadding: 0.2em;\n\t\tmargin: 0;\n\t}\n\n.horc-sidebar {\n\tposition: absolute;\n\tright: 0;\n\toverflow: hidden;\n\tz-index: 1;\n\ttransition: all 0.4s;\n\topacity: 0.25;\n\t-webkit-filter: blur(2px);\n\t-moz-filter: blur(2px);\n\tfilter: blur(2px);\n}\n\n\t.horc-sidebar:hover {\n\t\topacity: 1;\n\t\t-webkit-filter: blur(0px);\n\t\t-moz-filter: blur(0px);\n\t\tfilter: blur(0px);\n\t}\n\n\t.horc-sidebar #horc-mainui {\n\t\tpadding: 2em;\n\t\tpadding-bottom: 0.5em;\n\t\tborder-radius: 2em;\n\t}\n\n\n\n/* Anime Filter */\n\n.horc-listhead {\n\tcursor: default;\n\tuser-select: none;\n}\n\n\t.horc-listhead.horc-listhead-hide {\n\t\tcolor: #888888 !important;\n\t}\n\n.horc-listcounter {\n\tfont-family: Arial;\n\tfont-weight: initial;\n\tmargin-right: -100%;\n\tfloat: left;\n}\n\n#horc-watchlist {\n\tbackground-color: rgba(0, 255, 0, 0.05);\n}\n\n\t#horc-watchlist > .horc-content {\n\t\tbackground-color: rgba(0, 255, 0, 0.1);\n\t}\n\n#horc-droplist {\n\tbackground-color: rgba(0, 0, 0, 0.05);\n}\n\n\t#horc-droplist > .horc-content {\n\t\tbackground-color: rgba(0, 0, 0, 0.1);\n\t}\n\n#horc-hidelist {\n\tbackground-color: rgba(255, 0, 0, 0.05);\n}\n\n\t#horc-hidelist > .horc-content {\n\t\tbackground-color: rgba(255, 0, 0, 0.1);\n\t}\n\n#horc-mainui .horc-episode-filter {\n}\n\n\t#horc-mainui .horc-episode-filter:not(:last-child) {\n\t\tborder-bottom: 0.2em solid rgba(255, 255, 255, 0.8);\n\t}\n\n\n\n/* Position Drag UI */\n\n.horc-posdrag-icon {\n\twidth: 4em;\n\theight: 6em;\n\tmargin-left: -1em;\n\tmargin-top: -1em;\n\tbackground-color: rgb(230, 230, 230);\n\tborder: 0.25em solid #ffffff;\n\tposition: fixed;\n\tz-index: 10;\n\tpointer-events: none;\n}\n\n.horc-slot .horc-ui-droppanel {\n}\n\n\t.horc-slot .horc-ui-droppanel .horc-circle {\n\t\tposition: absolute;\n\t\tdisplay: inline-block;\n\t\tz-index: 8;\n\t\twidth: 8em;\n\t\theight: 8em;\n\t\tmargin: -4em;\n\t\tpadding: 0;\n\t\tbackground: radial-gradient( rgba(100,100,255, 1.0), rgba(100,100,255, 0.8), rgba(100,100,255, 0.8), rgba(100,100,255, 0.8) );\n\t\tborder-radius: 50%;\n\t}\n\n\n\n/* Episode Drag UI */\n\n.horc-episode-icon {\n\tfont-family: Arial, Helvetica, sans-serif;\n\tcolor: #000000;\n\tfont-size: 1.2em;\n\tfont-weight: bold;\n\ttext-align: center;\n\tmax-width: 24em;\n\tmargin-left: -1em;\n\tmargin-top: -3em;\n\tpadding: 1em;\n\tbackground-color: rgb(230, 230, 255);\n\tborder: 0.25em solid #ffffff;\n\tborder-radius: 1em;\n\tposition: fixed;\n\tz-index: 10;\n\tpointer-events: none;\n}\n\n.horc-ep-droppanel {\n\tposition: absolute;\n\tmargin: -8em;\n\twidth: calc(16em);\n\theight: calc(16em);\n\tbackground: radial-gradient( rgba(200,200,255, 0.8), rgba(200,200,255, 0.4), rgba(200,200,255, 0.4), rgba(200,200,255, 0.8) );\n\tborder-radius: 50%;\n\tz-index: 3;\n}\n\n\t.horc-ep-droppanel .horc-circle {\n\t\tcolor: #000000;\n\t\ttext-align: center;\n\t\tpointer-events: initial;\n\t\tposition: absolute;\n\t\tdisplay: table;\n\t\tmargin: calc(4.5em * -0.5);\n\t\twidth: calc(4.5em);\n\t\theight: calc(4.5em);\n\t\tborder-radius: 50%;\n\t}\n\n\t\t.horc-ep-droppanel .horc-circle div {\n\t\t\tdisplay: table-cell;\n\t\t\tvertical-align: middle;\n\t\t\tuser-select: none;\n\t\t}\n\n#watchcircle {\n\tleft: 25%;\n\ttop: 25%;\n\tbackground-color: rgba(140,255,120, 0.75);\n}\n\n\t#watchcircle:hover {\n\t\tbackground-color: rgba(140,255,140, 1.0);\n\t}\n\n#dropcircle {\n\tright: 25%;\n\ttop: 25%;\n\tbackground-color: rgba(140,140,120, 0.75);\n}\n\n\t#dropcircle:hover {\n\t\tbackground-color: rgba(140,140,140, 1.0);\n\t}\n\n#hidecircle {\n\tleft: 25%;\n\tbottom: 25%;\n\tbackground-color: rgba(255,140,120, 0.75);\n}\n\n\t#hidecircle:hover {\n\t\tbackground-color: rgba(255,140,140, 1.0);\n\t}\n\n#clearcircle {\n\tright: 25%;\n\tbottom: 25%;\n\tbackground-color: rgba(255,255,255, 0.75);\n}\n\n\t#clearcircle:hover {\n\t\tbackground-color: rgba(255,255,255, 1.0);\n\t}\n\n\n\n/* Both Drag UI */\n\n.openhand {\n\tcursor: url(http://www.google.com/intl/en_ALL/mapfiles/openhand.cur) 8 4, move;\n}\n\n.closedhand {\n\tcursor: url(http://www.google.com/intl/en_ALL/mapfiles/closedhand.cur) 8 4, move;\n}\n\n.draggable a, .draggable button {\n\tcursor: default;\n}\n\n.noselect {\n\t-webkit-touch-callout: none;\n\t-webkit-user-select: none;\n\t-khtml-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n";
  271.  
  272. options.createpositions = function () { // Create possible UI positions
  273. MainUI.create_sidebar($('<div>').insertBefore($('h2:contains("Releases")')), undefined, ['position', 'absolute', 'right', '4em', ]);
  274. MainUI.create_embed($('<div>').insertBefore($('h2:contains("Releases")')));
  275. MainUI.create_embed($('<div>').insertAfter($('.episodecontainer')));
  276. MainUI.create_embed($('<div>').prependTo($('#sidebar')));
  277. MainUI.create_embed($('<div class="horc-default">').insertAfter($('#text-16')));
  278. MainUI.create_embed($('<div>').insertAfter($('#text-8')));
  279. MainUI.create_embed($('<div>').appendTo($('#sidebar')));
  280. };
  281.  
  282. options.filteroptions = {
  283. // What contains the episode listing?
  284. epcontainer: '.episodecontainer',
  285.  
  286. // How to get an episode element?
  287. epselector: '.episodecontainer .episode',
  288.  
  289. // How to get anime name from an episode element?
  290. getanimename: default_getanime,
  291.  
  292. // How to grep episode container for an anime?
  293. epsearch: function (animename) {
  294. return $('.episodecontainer .episode').filter(':contains("' + animename + '")');
  295. },
  296. };
  297.  
  298. options.run = function () {
  299. // Find every way to update content
  300. function refresh(e) {
  301.  
  302.  
  303. var lastcount = 0;
  304. var timeout = 8000;
  305. var _check = setInterval(function () {
  306. var episodes = $('.episodecontainer .episode:not(.draggable)');
  307.  
  308. if (lastcount != episodes.length) {
  309. lastcount = episodes.length;
  310. document.refreshfilters();
  311. timeout = 1000;
  312. return;
  313. } else if (0 < timeout) {
  314. timeout -= 50;
  315. return;
  316. }
  317.  
  318. clearInterval(_check);
  319.  
  320.  
  321. }, 50);
  322. }
  323.  
  324. function refresh_enter(e) { if (e.which == 13) refresh(); }
  325. function refresh_click(e) { refresh(); }
  326. $('.searchbar').on('keyup', refresh_enter);
  327. $('.refreshbutton').on('keyup', refresh_enter);
  328. $('.refreshbutton').on('mouseup', refresh_click);
  329. $('.morebox').on('keyup', '.morebutton, .searchmorebutton', refresh_enter);
  330. $('.morebox').on('mouseup', '.morebutton, .searchmorebutton', refresh_click);
  331. };
  332.  
  333. document.mod = options;
  334. }
  335. /// Dependencies:
  336. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  337. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  338. /// <reference path="_ Host All.js" />
  339.  
  340.  
  341. if ('www.nyaa.se' === window.location.host) {
  342. var options = {};
  343.  
  344. options.style = "/* Configuration for www.nyaa.se */\n\n.horc-sidebar {\n\twidth: 100px;\n\ttop: 280px;\n}\n\n\t.horc-sidebar:hover {\n\t\twidth: 44em;\n\t}\n\n\t.horc-sidebar #horc-mainui {\n\t\twidth: 40em;\n\t}\n\n#ddpanel {\n\tfont-size: 1.5em;\n}\n\n.horc-episode-orig:not(.watch):not(.drop):not(.hide) * {\n\tfont-weight: normal !important;\n}\n\n.horc-episode-orig.watch .tlistname {\n\tfont-weight: 800 !important;\n}\n\n.horc-episode-orig.drop {\n\topacity: 0.30;\n}\n\n\n\n/* Original episode list */\n\n.horc-episode-orig.watch {\n\tbackground-color: rgba(0, 255, 0, 0.25);\n}\n\n.horc-episode-orig.drop {\n\tcolor: rgba(0, 0, 0, 0.15);\n}\n\n.horc-episode-orig.hide {\n\tdisplay: none;\n\tvisibility: hidden;\n}\n\n.horc-episode-orig.dragging {\n\tfont-weight: bolder;\n}\n\n\n\n/* Main UI */\n\n#horc-statusbar {\n\tfont-size: 1.2em;\n\ttext-align: center;\n\tbackground-color: rgba(200,255,200, 0.9);\n\twidth: 100%;\n\tposition: fixed;\n\tpadding: 1em 0 1em 0;\n\tmargin: 0;\n\tleft: 0;\n\tbottom: 0;\n\tborder-radius: 1em;\n}\n\n\t#horc-statusbar > div {\n\t\tmargin: 0 1em 0 1em;\n\t}\n\n.horc-slot {\n\ttext-align: center;\n\twidth: 100%;\n}\n\n#horc-mainui {\n\ttext-align: center;\n\tbackground-color: rgba(230, 230, 230, 0.96);\n\tuser-select: none;\n}\n\n\t#horc-mainui > div {\n\t\tmargin: 1em 0;\n\t\tcursor: initial;\n\t\tuser-select: initial;\n\t}\n\n\t#horc-mainui h2 {\n\t\tcolor: #000000;\n\t\tpadding: 0.2em;\n\t\tmargin: 0;\n\t}\n\n.horc-sidebar {\n\tposition: absolute;\n\tright: 0;\n\toverflow: hidden;\n\tz-index: 1;\n\ttransition: all 0.4s;\n\topacity: 0.25;\n\t-webkit-filter: blur(2px);\n\t-moz-filter: blur(2px);\n\tfilter: blur(2px);\n}\n\n\t.horc-sidebar:hover {\n\t\topacity: 1;\n\t\t-webkit-filter: blur(0px);\n\t\t-moz-filter: blur(0px);\n\t\tfilter: blur(0px);\n\t}\n\n\t.horc-sidebar #horc-mainui {\n\t\tpadding: 2em;\n\t\tpadding-bottom: 0.5em;\n\t\tborder-radius: 2em;\n\t}\n\n\n\n/* Anime Filter */\n\n.horc-listhead {\n\tcursor: default;\n\tuser-select: none;\n}\n\n\t.horc-listhead.horc-listhead-hide {\n\t\tcolor: #888888 !important;\n\t}\n\n.horc-listcounter {\n\tfont-family: Arial;\n\tfont-weight: initial;\n\tmargin-right: -100%;\n\tfloat: left;\n}\n\n#horc-watchlist {\n\tbackground-color: rgba(0, 255, 0, 0.05);\n}\n\n\t#horc-watchlist > .horc-content {\n\t\tbackground-color: rgba(0, 255, 0, 0.1);\n\t}\n\n#horc-droplist {\n\tbackground-color: rgba(0, 0, 0, 0.05);\n}\n\n\t#horc-droplist > .horc-content {\n\t\tbackground-color: rgba(0, 0, 0, 0.1);\n\t}\n\n#horc-hidelist {\n\tbackground-color: rgba(255, 0, 0, 0.05);\n}\n\n\t#horc-hidelist > .horc-content {\n\t\tbackground-color: rgba(255, 0, 0, 0.1);\n\t}\n\n#horc-mainui .horc-episode-filter {\n}\n\n\t#horc-mainui .horc-episode-filter:not(:last-child) {\n\t\tborder-bottom: 0.2em solid rgba(255, 255, 255, 0.8);\n\t}\n\n\n\n/* Position Drag UI */\n\n.horc-posdrag-icon {\n\twidth: 4em;\n\theight: 6em;\n\tmargin-left: -1em;\n\tmargin-top: -1em;\n\tbackground-color: rgb(230, 230, 230);\n\tborder: 0.25em solid #ffffff;\n\tposition: fixed;\n\tz-index: 10;\n\tpointer-events: none;\n}\n\n.horc-slot .horc-ui-droppanel {\n}\n\n\t.horc-slot .horc-ui-droppanel .horc-circle {\n\t\tposition: absolute;\n\t\tdisplay: inline-block;\n\t\tz-index: 8;\n\t\twidth: 8em;\n\t\theight: 8em;\n\t\tmargin: -4em;\n\t\tpadding: 0;\n\t\tbackground: radial-gradient( rgba(100,100,255, 1.0), rgba(100,100,255, 0.8), rgba(100,100,255, 0.8), rgba(100,100,255, 0.8) );\n\t\tborder-radius: 50%;\n\t}\n\n\n\n/* Episode Drag UI */\n\n.horc-episode-icon {\n\tfont-family: Arial, Helvetica, sans-serif;\n\tcolor: #000000;\n\tfont-size: 1.2em;\n\tfont-weight: bold;\n\ttext-align: center;\n\tmax-width: 24em;\n\tmargin-left: -1em;\n\tmargin-top: -3em;\n\tpadding: 1em;\n\tbackground-color: rgb(230, 230, 255);\n\tborder: 0.25em solid #ffffff;\n\tborder-radius: 1em;\n\tposition: fixed;\n\tz-index: 10;\n\tpointer-events: none;\n}\n\n.horc-ep-droppanel {\n\tposition: absolute;\n\tmargin: -8em;\n\twidth: calc(16em);\n\theight: calc(16em);\n\tbackground: radial-gradient( rgba(200,200,255, 0.8), rgba(200,200,255, 0.4), rgba(200,200,255, 0.4), rgba(200,200,255, 0.8) );\n\tborder-radius: 50%;\n\tz-index: 3;\n}\n\n\t.horc-ep-droppanel .horc-circle {\n\t\tcolor: #000000;\n\t\ttext-align: center;\n\t\tpointer-events: initial;\n\t\tposition: absolute;\n\t\tdisplay: table;\n\t\tmargin: calc(4.5em * -0.5);\n\t\twidth: calc(4.5em);\n\t\theight: calc(4.5em);\n\t\tborder-radius: 50%;\n\t}\n\n\t\t.horc-ep-droppanel .horc-circle div {\n\t\t\tdisplay: table-cell;\n\t\t\tvertical-align: middle;\n\t\t\tuser-select: none;\n\t\t}\n\n#watchcircle {\n\tleft: 25%;\n\ttop: 25%;\n\tbackground-color: rgba(140,255,120, 0.75);\n}\n\n\t#watchcircle:hover {\n\t\tbackground-color: rgba(140,255,140, 1.0);\n\t}\n\n#dropcircle {\n\tright: 25%;\n\ttop: 25%;\n\tbackground-color: rgba(140,140,120, 0.75);\n}\n\n\t#dropcircle:hover {\n\t\tbackground-color: rgba(140,140,140, 1.0);\n\t}\n\n#hidecircle {\n\tleft: 25%;\n\tbottom: 25%;\n\tbackground-color: rgba(255,140,120, 0.75);\n}\n\n\t#hidecircle:hover {\n\t\tbackground-color: rgba(255,140,140, 1.0);\n\t}\n\n#clearcircle {\n\tright: 25%;\n\tbottom: 25%;\n\tbackground-color: rgba(255,255,255, 0.75);\n}\n\n\t#clearcircle:hover {\n\t\tbackground-color: rgba(255,255,255, 1.0);\n\t}\n\n\n\n/* Both Drag UI */\n\n.openhand {\n\tcursor: url(http://www.google.com/intl/en_ALL/mapfiles/openhand.cur) 8 4, move;\n}\n\n.closedhand {\n\tcursor: url(http://www.google.com/intl/en_ALL/mapfiles/closedhand.cur) 8 4, move;\n}\n\n.draggable a, .draggable button {\n\tcursor: default;\n}\n\n.noselect {\n\t-webkit-touch-callout: none;\n\t-webkit-user-select: none;\n\t-khtml-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n";
  345.  
  346. options.createpositions = function () { // Create possible UI positions
  347. MainUI.create_sidebar($('<div>').insertBefore($('#main')), undefined, ['position', 'absolute', 'right', '4em', 'top', '20em']);
  348. MainUI.create_embed($('<div>').insertBefore($('#main .tlistsortorder')), ['width', '100%', 'text-align', 'center']);
  349. MainUI.create_embed($('<div class="horc-default">').insertAfter($('#main .torrentsubcatlist')), ['width', '100%', 'text-align', 'center']);
  350. };
  351.  
  352. options.filteroptions = {
  353. // What contains the episode listing?
  354. epcontainer: '.tlist',
  355.  
  356. // How to get an episode element?
  357. epselector: '.tlist .tlistrow',
  358.  
  359. // How to get anime name from an episode element?
  360. getanimename: default_getanime,
  361.  
  362. // How to grep episode container for an anime?
  363. epsearch: function (animename) {
  364. return $('.tlist .tlistrow').has('.tlistname:contains("' + animename + '")');
  365. },
  366. };
  367.  
  368. options.run = function () {
  369. };
  370.  
  371. document.mod = options;
  372. }
  373. // String Compare Case-Sensitive
  374. function strcmp(lhs, rhs) {
  375. for (var i = 0; i < lhs.length && i < rhs.length; ++i) {
  376. if (lhs[i] === rhs[i]) continue;
  377. return lhs[i] < rhs[i] ? -1 : 1;
  378. }
  379. if (lhs.length === rhs.length) return 0;
  380. return lhs.length < rhs.length ? -1 : 1;
  381. }
  382.  
  383. // String Compare Case-Insensitive
  384. function strcmpi(lhs, rhs) {
  385. for (var i = 0; i < lhs.length && i < rhs.length; ++i) {
  386. if (lhs[i].toLowerCase() === rhs[i].toLowerCase()) continue;
  387. return lhs[i].toLowerCase() < rhs[i].toLowerCase() ? -1 : 1;
  388. }
  389. if (lhs.length === rhs.length) return 0;
  390. return lhs.length < rhs.length ? -1 : 1;
  391. }
  392.  
  393.  
  394. // Binary Search Template
  395. //
  396. // In the options, method_options below, you can override the methods:
  397. //
  398. // int compare(element_to_find, element_in_container)
  399. // Expected return: -1, 0, 1
  400. // Determines if the element is less or greater than
  401. // Behavior should match this: http://www.cplusplus.com/reference/cstring/strcmp/
  402. //
  403. // int length(container)
  404. // Expected return: int
  405. // Redefines how to check the size of the container [default container.length]
  406. //
  407. // ElementType get(i, container)
  408. // Expected return: a type matching the parameters of compare(e0, e1)
  409. // Redefines how to access the container by some index
  410. //
  411. // * found(i, container)
  412. // Expected return: anything you require
  413. // Redefines what to return
  414. //
  415. // * notfound(i, container)
  416. // Expected return: anything you require
  417. // Redefines what to return when not found [default null]
  418. // This function is useful to ask "where to insert", since
  419. // "i" represent the position to insert the new element, using splice:
  420. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
  421. //
  422. // Usage [2]: int, null binsearch(string_to_find, array_of_strings)
  423. // Usage [3]: custom binsearch(element_to_find, container_object, method_options)
  424. function binsearch(element, container, options) {
  425. if (options === undefined) options = {
  426. compare: undefined,
  427. length: undefined,
  428. get: undefined,
  429. found: undefined,
  430. notfound: undefined,
  431. };
  432.  
  433. var compare = options.compare;
  434. var length = options.length;
  435. var get = options.get;
  436. var found = options.found;
  437. var notfound = options.notfound;
  438.  
  439. if (compare === undefined) compare = strcmpi;
  440.  
  441.  
  442. function default_len(container) { return container.length; }
  443. if (length === undefined) length = default_len;
  444.  
  445.  
  446. function default_arrget(i, container) { return container[i]; }
  447. if (get === undefined) get = default_arrget;
  448.  
  449.  
  450. function default_arrret(i, container) { return i; }
  451. if (found === undefined) found = default_arrret;
  452.  
  453.  
  454. function default_arrnotret(i, container) { return null; }
  455. if (notfound === undefined) notfound = default_arrnotret;
  456.  
  457.  
  458.  
  459. if (!length(container)) return notfound(0, container);
  460.  
  461.  
  462.  
  463.  
  464. var begin = 0;
  465. var end = length(container) - 1;
  466. var mid = begin + (end - begin) / 2 | 0;
  467. while (true) {
  468.  
  469.  
  470.  
  471. if (begin === end) {
  472. switch (compare(element, get(mid, container))) {
  473. case -1:
  474. return notfound(mid, container);
  475. case 0:
  476. return found(mid, container);
  477. case 1:
  478. return notfound(mid + 1, container);
  479. }
  480. }
  481.  
  482. switch (compare(element, get(mid, container))) {
  483. case -1:
  484. end = mid;
  485. mid = begin + (end - begin) / 2 | 0;
  486. break;
  487. case 0:
  488. return found(mid, container);
  489. case 1:
  490. begin = mid + 1;
  491. mid = begin + (end - begin) / 2 | 0;
  492. break;
  493. }
  494. }
  495. }
  496. /// Dependencies:
  497. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  498. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  499. /// <reference path="Algorithms.js" />
  500. /// <reference path="Class Store.js" />
  501.  
  502.  
  503. var FLAG_WATCH = 0;
  504. var FLAG_DROP = 1;
  505. var FLAG_HIDE = 2;
  506. var ANIME_NAME = 0;
  507. var ANIME_FLAG = 1;
  508. var ANIME_DATE = 2;
  509. var ANIME_EXPIRE = 3;
  510.  
  511.  
  512. // Anime Structure
  513. //
  514. // Holds information on an anime
  515. //
  516. // Member Data:
  517. // - name | Name of the anime according to the tracker
  518. // - flag | View flag
  519. // - date_y | Date added
  520. // - date_m | ^
  521. // - date_d | ^
  522. // - expire_y | Date this will expire on [-1 for never]
  523. // - expire_m | ^
  524. // - expire_d | ^
  525. //
  526. // Member Functions:
  527. // - isgood | Test if expired
  528. // - setexpirecours | Set the expiration date ## "anime network season" from today
  529. // - setexpireweeks | Set the expiration date ## weeks from today
  530. // - setexpiredays | Set the expiration date ## days from today
  531. // - compress | Compress the data for storage
  532. // - decompress | Copies data from a compressed data array
  533. //
  534. // Usage [0]: new Anime()
  535. // Usage [1]: new Anime(anime_dataset)
  536. var Anime = function (anime_dataset) {
  537. var thisanime = this;
  538.  
  539. this.Anime = function (anime_dataset) {
  540.  
  541.  
  542. if (typeof anime_dataset === 'object') {
  543. thisanime.decompress(anime_dataset);
  544. } else {
  545. var d = new Date();
  546. thisanime.date_y = d.getFullYear();
  547. thisanime.date_m = d.getMonth() + 1;
  548. thisanime.date_d = d.getDate();
  549. delete d;
  550. }
  551. };
  552.  
  553. this.isgood = function () {
  554. if (thisanime.expire_y === -1) return true;
  555.  
  556. var diff = (new Date(thisanime.expire_y, thisanime.expire_m - 1, thisanime.expire_d - 1)) - (new Date(thisanime.date_y, thisanime.date_m - 1, thisanime.date_d));
  557. if (-1 < diff) return true;
  558.  
  559. return false;
  560. };
  561.  
  562. this.setexpiredays = function (amount) {
  563. var d = new Date();
  564. var e = new Date(d.getFullYear(), d.getMonth(), d.getDate() + amount);
  565. thisanime.expire_y = e.getFullYear();
  566. thisanime.expire_m = e.getMonth() + 1;
  567. thisanime.expire_d = e.getDate();
  568. };
  569. this.setexpireweeks = function (amount) {
  570. thisanime.setexpiredays(amount * 7);
  571. };
  572. this.setexpirecours = function (amount) {
  573. thisanime.setexpiredays(amount * 7 * 13);
  574. };
  575.  
  576.  
  577. this.compress = function () {
  578. var anime = [];
  579.  
  580. anime[ANIME_NAME] = thisanime.name; // Name of anime
  581. anime[ANIME_FLAG] = thisanime.flag; // w d h
  582.  
  583. anime[ANIME_DATE] = thisanime.date_y;
  584. anime[ANIME_DATE] = anime[ANIME_DATE] * 100 + thisanime.date_m;
  585. anime[ANIME_DATE] = anime[ANIME_DATE] * 100 + thisanime.date_d;
  586.  
  587. if (thisanime.expire_y === -1) {
  588. anime[ANIME_EXPIRE] = -1;
  589. } else {
  590. anime[ANIME_EXPIRE] = thisanime.expire_y;
  591. anime[ANIME_EXPIRE] = anime[ANIME_EXPIRE] * 100 + thisanime.expire_m;
  592. anime[ANIME_EXPIRE] = anime[ANIME_EXPIRE] * 100 + thisanime.expire_d;
  593. }
  594.  
  595. return anime;
  596. };
  597.  
  598. this.decompress = function (anime_dataset) {
  599. thisanime.name = anime_dataset[ANIME_NAME]; // Name of anime_dataset
  600. thisanime.flag = anime_dataset[ANIME_FLAG]; // w d h
  601.  
  602. var value = anime_dataset[ANIME_DATE];
  603. thisanime.date_d = Math.floor(value % 100);
  604. value /= 100;
  605. thisanime.date_m = Math.floor(value % 100);
  606. value /= 100;
  607. thisanime.date_y = Math.floor(value);
  608.  
  609. var value = anime_dataset[ANIME_EXPIRE];
  610. if (value === -1) {
  611. thisanime.expire_y = thisanime.expire_m = thisanime.expire_d = -1;
  612. } else {
  613. thisanime.expire_d = Math.floor(value % 100);
  614. value /= 100;
  615. thisanime.expire_m = Math.floor(value % 100);
  616. value /= 100;
  617. thisanime.expire_y = Math.floor(value);
  618. }
  619.  
  620. return thisanime;
  621. };
  622.  
  623.  
  624. this.name = ''; // Name of anime
  625. this.flag = -1; // w d h
  626. this.date_y = -1;
  627. this.date_m = -1;
  628. this.date_d = -1;
  629. this.expire_y = -1;
  630. this.expire_m = -1;
  631. this.expire_d = -1;
  632.  
  633.  
  634. this.Anime(anime_dataset);
  635. };
  636.  
  637.  
  638.  
  639. // Anime List Manager
  640. //
  641. // Holds information on an anime
  642. //
  643. // Public Methods
  644. // - get | Get an anime entry; If not found, return the insertion index
  645. // - add | Add an anime by Anime struct
  646. // - rm | Set the expiration date ## weeks from today
  647. // - save | Save data to storage
  648. // - load | Reload data from storage
  649. //
  650. // Usage [0]: AnimeList()
  651. var AnimeList = function () {
  652. var thisanimelist = this;
  653.  
  654. var store = new function () {
  655. this.anime = new Store('horc-animes', [], 'object');
  656. };
  657. store.anime.list = [];
  658.  
  659.  
  660. this.AnimeList = function () {
  661.  
  662.  
  663.  
  664. thisanimelist.load();
  665. };
  666.  
  667.  
  668.  
  669. // Add Anime
  670. //
  671. // Inserts the anime to the list & storage
  672. //
  673. // Usage [1]: add(Anime_structure)
  674. this.add = function (anime) {
  675.  
  676.  
  677.  
  678.  
  679. var find = thisanimelist.get(anime.name);
  680. if (typeof find === 'number') { // Not found
  681. store.anime.list.splice(find, 0, anime); // insert
  682. } else { // Found
  683. store.anime.list.splice(find[0], 1, anime); // replace
  684. }
  685.  
  686. thisanimelist.save();
  687. };
  688.  
  689. // Remove Anime
  690. //
  691. // Remove the anime from the list & storage
  692. //
  693. // Usage [1]: rm(string_name)
  694. this.rm = function (name) {
  695.  
  696.  
  697. var find = thisanimelist.get(name);
  698. if (typeof find === 'number') { // Not found
  699. } else { // Found
  700. store.anime.list.splice(find[0], 1); // replace
  701. }
  702.  
  703. thisanimelist.save();
  704. };
  705.  
  706. // Get Anime
  707. //
  708. // Return:
  709. // If found, the array [ index, Anime structure ]
  710. // If not found, the index at which to insert the anime
  711. //
  712. // Usage [1]: get(string_name)
  713. this.get = function (name) {
  714.  
  715.  
  716. return binsearch(name, store.anime.list, {
  717. get: function (i, container) { return container[i].name; },
  718. found: function (i, container) { return [i, container[i]]; },
  719. notfound: function (i, container) { return i; },
  720. });
  721. };
  722.  
  723. // Save Anime List
  724. //
  725. // Save the anime list to storage
  726. //
  727. // Usage [0]: save()
  728. this.save = function () {
  729. var compressed = $(store.anime.list).map(function (i, e) {
  730. return [e.compress()];
  731. });
  732.  
  733. store.anime.set(compressed);
  734. };
  735.  
  736. // Load Anime List
  737. //
  738. // Reload the anime list from storage
  739. //
  740. // Note:
  741. // This will also delete expired entries from storage.
  742. //
  743. // Usage [0]: load()
  744. this.load = function () {
  745. var deletions = false;
  746.  
  747. store.anime.list = $(store.anime.get()).map(function (i, e) {
  748. var anime = new Anime(e);
  749. if (anime.isgood()) return anime;
  750. deletions = true;
  751.  
  752.  
  753.  
  754.  
  755. });
  756.  
  757. if (deletions) thisanimelist.save();
  758. };
  759.  
  760.  
  761.  
  762. this.AnimeList();
  763. };
  764. /// Dependencies:
  765. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  766. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  767. /// <reference path="Class Anime.js" />
  768. /// <reference path="Class Store.js" />
  769. /// <reference path="Class Touch.js" />
  770.  
  771.  
  772. // AnimeFilter module
  773. //
  774. // Constructor takes the string selector for slots the UI may position itself in.
  775. // Events are listened to & fired in $(document)
  776. //
  777. // Events this will listen for:
  778. // - set-active | Make the program interactable
  779. // - clear-active | Make the program non-interactable
  780. // - set-ui-droppanel | Open the MainUI position drop panel
  781. // - clear-ui-droppanel | Close the MainUI position drop panel
  782. // - set-ep-droppanel | Open the episode list drop panel
  783. // - clear-ep-droppanel | Close the episode list drop panel
  784. // - update-animelist | Update the anime list count
  785. //
  786. // Events this will trigger:
  787. // -
  788. //
  789. // Options:
  790. // -
  791. //
  792. // Note:
  793. // You are required to allocate at least 1 position as empty elements before constructing this.
  794. var AnimeFilter = function (options) {
  795. var thisanimefilter = this;
  796.  
  797. var animelist = null;
  798.  
  799.  
  800. this.AnimeFilter = function () {
  801.  
  802. document.animelist = animelist = new AnimeList();
  803.  
  804.  
  805.  
  806.  
  807.  
  808.  
  809.  
  810.  
  811. // Bind episodes now & add to document
  812. thisanimefilter.refreshfilters();
  813. document.refreshfilters = thisanimefilter.refreshfilters;
  814.  
  815. firstbindtouch();
  816. $(document).on('update-animelist', updatelist);
  817. $(document).trigger('update-animelist');
  818. };
  819.  
  820.  
  821.  
  822. function firstbindtouch() {
  823. // Search an element, then it's parents for a selector
  824. function treehas(element, selector) {
  825.  
  826.  
  827.  
  828. // Check this
  829. var jqe = $(element).filter(selector);
  830. if (jqe.length) return $(element);
  831.  
  832. // Check parents
  833. var jqe = $(element).parents(selector);
  834. if (jqe.length) return jqe;
  835.  
  836. return null;
  837. }
  838. function typeofelement(jqe) {
  839. if (jqe.hasClass('horc-episode-filter')) return 'fi'; // Episode Filter
  840. if (jqe.hasClass('horc-episode-orig')) return 'ep'; // Episode Original
  841. if (jqe.hasClass('horc-circle')) return 'ep'; // Episode Original
  842. if (jqe.hasClass('horc-slot')) return 'ui'; // UI
  843. if (jqe.hasClass('horc-ui')) return 'ui'; // UI
  844. return '';
  845. }
  846. function shouldreject(jqe) {
  847. return !!treehas(jqe, 'a,button,datalist,input,keygen,output,select,textarea');
  848. }
  849.  
  850. var touch = new Touch();
  851.  
  852. var container = $('body');
  853. container.on('mousedown', touch.mousedown);
  854. container.on('mousemove', touch.mousemove);
  855. container.on('mouseup', touch.mouseup);
  856. container.on('touchstart', touch.touchstart);
  857. container.on('touchmove', touch.touchmove);
  858. container.on('touchend', touch.touchend);
  859.  
  860. touch.onstart = function (ids, changes, e) {
  861.  
  862.  
  863.  
  864. };
  865. touch.onend = function (ids, changes, e) {
  866.  
  867.  
  868.  
  869. };
  870. touch.ondragstart = function (ids, changes, e) {
  871.  
  872.  
  873.  
  874. // If requires control & pressing control don't match, cancel
  875. if (e.ctrlKey ^ document.store.requirectrl.get()) return;
  876.  
  877. $(changes).map(function (i, changed) {
  878. if (id < -1) return; // ignore middle & right click
  879. var changed = e.originalEvent.changedTouches[i];
  880. var id = changed.identifier;
  881.  
  882. if (shouldreject(changed.target)) return;
  883.  
  884. // Check if drag exists
  885. var jqedrag = treehas(changed.target, '.draggable');
  886. if (!jqedrag) return;
  887.  
  888. // Check type of drag & drop
  889. var typeofdrag = typeofelement(jqedrag);
  890.  
  891. switch (typeofdrag) {
  892. case 'ep':
  893. var panel = $('.horc-ep-droppanel');
  894. if (!panel.is(':visible')) {
  895. panel.css('left', changed.pageX);
  896. panel.css('top', changed.pageY);
  897. }
  898. $(document).trigger('set-ep-droppanel')
  899. try {
  900. addepicon(id, options.getanimename(jqedrag));
  901. } catch (err) {
  902. setstatus('<br>Could\'t get the anime\'s name, please report this:<br>\n<span style="color: #800000;">' + jqedrag.text() + '</span>');
  903. }
  904. break;
  905. case 'fi':
  906. var panel = $('.horc-ep-droppanel');
  907. if (!panel.is(':visible')) {
  908. panel.css('left', changed.pageX);
  909. panel.css('top', changed.pageY);
  910. }
  911. $(document).trigger('set-ep-droppanel')
  912. addepicon(id, jqedrag.text());
  913. break;
  914. case 'ui':
  915. $(document).trigger('set-ui-droppanel')
  916. $('#horc-mainui').hide();
  917. adduiicon(id);
  918. break;
  919.  
  920.  
  921. }
  922.  
  923. e.preventDefault();
  924. });
  925. };
  926. touch.ondragend = function (ids, changes, e) {
  927.  
  928.  
  929.  
  930. $(changes).map(function (i, changed) {
  931. if (id < -1) return; // ignore middle & right click
  932. var changed = e.originalEvent.changedTouches[i];
  933. var id = changed.identifier;
  934.  
  935. if (shouldreject(changed.target)) return;
  936.  
  937. rmepicon(id);
  938. rmuiicon(id);
  939.  
  940. // Check if drag & drop both exists
  941. var jqedrag = treehas(changed.target, '.draggable');
  942. if (!jqedrag) return;
  943. var jqedrop = treehas(changed.targetnow, '.droppable');
  944. if (!jqedrop) $('#horc-mainui').show();
  945. if (!jqedrop) return;
  946.  
  947. // Check type of drag & drop
  948. var typeofdrag = typeofelement(jqedrag);
  949. var typeofdrop = typeofelement(jqedrop);
  950.  
  951. switch (typeofdrag) {
  952. case 'ep':
  953. switch (typeofdrop) {
  954. case 'ep':
  955. try {
  956. filteranime(options.getanimename(jqedrag), jqedrop);
  957. } catch (err) {
  958. setstatus('<br>Could\'t get the anime\'s name, please report this:<br>\n<span style="color: #800000;">' + jqedrag.text() + '</span>');
  959. }
  960. break;
  961. case 'ui':
  962.  
  963. break;
  964.  
  965.  
  966. }
  967. break;
  968. case 'fi':
  969. switch (typeofdrop) {
  970. case 'ep':
  971. filteranime(jqedrag.text(), jqedrop);
  972. break;
  973. case 'ui':
  974.  
  975. break;
  976.  
  977.  
  978. }
  979. break;
  980. case 'ui':
  981. switch (typeofdrop) {
  982. case 'ep':
  983.  
  984. break;
  985. case 'ui':
  986. $('.horc-slot').map(function (i, element) {
  987. if (jqedrop[0] === element) document.store.position.set(i);
  988. });
  989.  
  990. $('#horc-mainui').appendTo(jqedrop.children('.horc-content')).show();
  991.  
  992.  
  993. break;
  994.  
  995.  
  996. }
  997. break;
  998.  
  999.  
  1000. }
  1001.  
  1002. e.preventDefault();
  1003. });
  1004. };
  1005. touch.onmove = function (ids, changes, e) {
  1006.  
  1007.  
  1008.  
  1009. $(changes).map(function (i, changed) {
  1010. if (id < -1) return; // ignore middle & right click
  1011. var id = changed.identifier;
  1012.  
  1013. var icon = $('#horc-ep' + id + ',#horc-pos' + id);
  1014. if (icon.length) {
  1015. icon.css('left', changed.clientX);
  1016. icon.css('top', changed.clientY);
  1017. e.preventDefault();
  1018. }
  1019. });
  1020. };
  1021. touch.onfirst = function (ids, changes, e) {
  1022.  
  1023.  
  1024.  
  1025.  
  1026. if (e.ctrlKey ^ document.store.requirectrl.get()) return;
  1027.  
  1028. $('#horc-mainui,' + options.epcontainer).addClass('noselect');
  1029. };
  1030. touch.onlast = function (ids, changes, e) {
  1031.  
  1032.  
  1033.  
  1034. $(document).trigger('clear-ep-droppanel');
  1035. $(document).trigger('clear-ui-droppanel');
  1036.  
  1037. $('#horc-mainui,' + options.epcontainer).removeClass('noselect');
  1038. };
  1039. }
  1040.  
  1041. function updatelist() {
  1042. $('#horc-mainui .horc-episode-filter').remove();
  1043.  
  1044. var eps = $('.horc-episode-orig');
  1045. eps.map(function (i, element) {
  1046. var ep = $(element);
  1047. var animename = '';
  1048. try {
  1049. animename = options.getanimename(ep);
  1050. } catch (err) {
  1051. return;
  1052. }
  1053.  
  1054. // Check which filter this episode falls under
  1055. var flag = -1;
  1056. if (ep.hasClass('watch')) flag = FLAG_WATCH;
  1057. else if (ep.hasClass('drop')) flag = FLAG_DROP;
  1058. else if (ep.hasClass('hide')) flag = FLAG_HIDE;
  1059. if (flag == -1) return;
  1060.  
  1061. // Construct the list selector
  1062. var whichselector = '';
  1063. switch (flag) {
  1064. case FLAG_WATCH:
  1065. whichselector = 'watch';
  1066. break;
  1067. case FLAG_DROP:
  1068. whichselector = 'drop';
  1069. break;
  1070. case FLAG_HIDE:
  1071. whichselector = 'hide';
  1072. break;
  1073.  
  1074.  
  1075. }
  1076.  
  1077. // Populate the filter lists, since they were cleared before this map
  1078.  
  1079. // Check if the list has the anime
  1080. var list = $('#horc-' + whichselector + 'list .horc-content');
  1081. var inlist = !!list.find('.horc-episode-filter').map(function (i, element) {
  1082. var filter = $(element);
  1083. var filtername = filter.text();
  1084. if (filtername === animename) return filtername;
  1085. }).length;
  1086.  
  1087. // Add it maybe
  1088. if (!inlist) {
  1089. var filter = $('<div class="horc-episode-filter draggable">');
  1090. filter.text(animename);
  1091. list.append(filter);
  1092. }
  1093. });
  1094. }
  1095.  
  1096.  
  1097. // Rebind Episodes
  1098. //
  1099. // Rebinds the drag event to new episode elements
  1100. //
  1101. // Note:
  1102. // You will need to call this anytime episode lists are populated dynamically.
  1103. this.refreshfilters = function () {
  1104. $(options.epselector).filter(':not(.horc-episode-orig)').map(function (i, element) {
  1105. var jqe = $(element);
  1106. jqe.addClass('horc-episode-orig draggable');
  1107.  
  1108. var animename = '';
  1109. try {
  1110. animename = options.getanimename(jqe);
  1111. } catch (err) {
  1112. return;
  1113. }
  1114.  
  1115.  
  1116. var search = animelist.get(animename);
  1117. if (typeof search === 'number') { // Not yet marked
  1118. return; // Dont highlight
  1119. }
  1120.  
  1121. var flag = search[1].flag;
  1122.  
  1123. var whichselector = '';
  1124. switch (flag) {
  1125. case FLAG_WATCH:
  1126. whichselector = 'watch';
  1127.  
  1128. break;
  1129. case FLAG_DROP:
  1130. whichselector = 'drop';
  1131.  
  1132. break;
  1133. case FLAG_HIDE:
  1134. whichselector = 'hide';
  1135.  
  1136. break;
  1137.  
  1138.  
  1139. }
  1140.  
  1141. jqe.removeClass('watch drop hide');
  1142. jqe.addClass(whichselector);
  1143. });
  1144.  
  1145. $(document).trigger('update-animelist');
  1146. };
  1147.  
  1148.  
  1149. function filteranime(animename, jqedrop) {
  1150. // Check which filter this episode falls under
  1151. var flag = 0;
  1152. if (jqedrop.filter('#clearcircle').length) flag = -1;
  1153. else if (jqedrop.filter('#watchcircle').length) flag = FLAG_WATCH;
  1154. else if (jqedrop.filter('#dropcircle').length) flag = FLAG_DROP;
  1155. else if (jqedrop.filter('#hidecircle').length) flag = FLAG_HIDE;
  1156.  
  1157. // Construct the list selector
  1158. var whichselector = '';
  1159. switch (flag) {
  1160. case FLAG_WATCH:
  1161. whichselector = 'watch';
  1162.  
  1163. break;
  1164. case FLAG_DROP:
  1165. whichselector = 'drop';
  1166.  
  1167. break;
  1168. case FLAG_HIDE:
  1169. whichselector = 'hide';
  1170.  
  1171. break;
  1172. case -1:
  1173.  
  1174. break;
  1175.  
  1176.  
  1177. }
  1178.  
  1179.  
  1180. if (flag === -1) { // Clear
  1181. // Remove highlight
  1182. var eps = options.epsearch(animename);
  1183.  
  1184. eps.removeClass('watch drop hide');
  1185.  
  1186. // Remove from list
  1187. animelist.rm(animename);
  1188.  
  1189. $(document).trigger('update-animelist');
  1190. return;
  1191. }
  1192.  
  1193. ///////////////////////
  1194. // Watch/Drop/Hide //
  1195.  
  1196. // Re-highlight
  1197. var eps = options.epsearch(animename);
  1198. eps.removeClass('watch drop hide');
  1199. eps.addClass(whichselector);
  1200.  
  1201. var search = animelist.get(animename);
  1202. if (typeof search === 'number') { // Not yet added
  1203. var newanime = new Anime();
  1204.  
  1205. // Update
  1206. newanime.name = animename;
  1207. newanime.flag = flag;
  1208. newanime.setexpirecours(2);
  1209.  
  1210. // Save
  1211. animelist.add(newanime);
  1212. animelist.save();
  1213. } else { // Already there; update it
  1214. if (search[1].flag !== flag) { // update the flag
  1215. // Update
  1216. search[1].flag = flag;
  1217.  
  1218. animelist.save();
  1219. } // Else do nothing
  1220. }
  1221.  
  1222.  
  1223. $(document).trigger('update-animelist');
  1224. }
  1225.  
  1226.  
  1227. function addepicon(id, name) {
  1228.  
  1229.  
  1230.  
  1231. rmepicon(id);
  1232.  
  1233. // Drag icon
  1234. var icon = $('<div id="horc-ep' + id + '" class="horc-episode-icon">' + name + '</div>');
  1235. $('body').append(icon);
  1236. }
  1237. function adduiicon(id) {
  1238.  
  1239.  
  1240. rmuiicon(id);
  1241.  
  1242. // Drag icon
  1243. var icon = $('<div id="horc-pos' + id + '" class="horc-posdrag-icon">');
  1244. $('body').append(icon);
  1245. }
  1246. function rmepicon(id) {
  1247.  
  1248.  
  1249. $('#horc-ep' + id).remove();
  1250. }
  1251. function rmuiicon(id) {
  1252.  
  1253.  
  1254. $('#horc-pos' + id).remove();
  1255. }
  1256.  
  1257.  
  1258. var timer_statusbar = null;
  1259. function setstatus(msg) {
  1260. var statusbar = $('#horc-statusbar');
  1261. var msgbox = statusbar.children();
  1262.  
  1263. if (timer_statusbar === null) {
  1264. msgbox.empty().append(msg);
  1265.  
  1266. statusbar.show({ direction: 'down' }, 250);
  1267. timer_statusbar = setTimeout(function () {
  1268. statusbar.hide({ direction: 'down' }, 250);
  1269. }, 10000);
  1270. } else {
  1271. clearTimeout(timer_statusbar);
  1272. timer_statusbar = null;
  1273. setstatus(msg);
  1274. }
  1275. }
  1276.  
  1277.  
  1278.  
  1279. this.AnimeFilter();
  1280. };
  1281. /// Dependencies:
  1282. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  1283. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  1284. /// <reference path="Class Store.js" />
  1285.  
  1286.  
  1287. // MainUI module
  1288. //
  1289. // Constructor takes the string selector for slots the UI may position itself in.
  1290. // Events are listened to & fired in $(document)
  1291. //
  1292. // Events this will listen for:
  1293. // - set-active | Make the program interactable
  1294. // - clear-active | Make the program non-interactable
  1295. // - set-ui-droppanel | Open the MainUI position drop panel
  1296. // - clear-ui-droppanel | Close the MainUI position drop panel
  1297. // - set-ep-droppanel | Open the episode list drop panel
  1298. // - clear-ep-droppanel | Close the episode list drop panel
  1299. // - update-animelist | Update the anime list count
  1300. //
  1301. // Static methods to know of:
  1302. // - MainUI.create_embed | Creates an embedded position
  1303. // - MainUI.create_sidebar | Creates a position for a sidebar
  1304. //
  1305. // Note:
  1306. // You are required to allocate at least 1 position as empty elements before constructing this.
  1307. // Pass the string selector to your allocated positions.
  1308. var MainUI = function (default_css) {
  1309. var thismainui = this;
  1310.  
  1311.  
  1312. if (document.store == undefined) document.store = {};
  1313. document.store.css = new Store('horc-style', default_css, 'string');
  1314. document.store.handedness = new Store('horc-handedness', 1, 'number');
  1315. document.store.position = new Store('horc-position', -1, 'number');
  1316. document.store.requirectrl = new Store('horc-requirectrl', false, 'boolean');
  1317. document.store.settings = new Store('horc-settings', false, 'boolean');
  1318. document.store.updater = new Store('horc-updater', true, 'boolean');
  1319. document.store.showwatch = new Store('horc-show-watch', true, 'boolean');
  1320. document.store.showdrop = new Store('horc-show-drop', false, 'boolean');
  1321. document.store.showhide = new Store('horc-show-hide', false, 'boolean');
  1322.  
  1323.  
  1324. this.MainUI = function () {
  1325.  
  1326.  
  1327. var slots = $('.horc-slot');
  1328. if (slots.length === 0) throw 'No positions for the UI to take';
  1329.  
  1330.  
  1331.  
  1332. // Load CSS
  1333. $('<style id="horc-css">').text(document.store.css.get()).appendTo($('head'));
  1334.  
  1335. create_mainui();
  1336. create_statusbar();
  1337. create_epdragui();
  1338.  
  1339. create_events();
  1340. };
  1341.  
  1342.  
  1343.  
  1344. // Get Position Value [+1 overloads]
  1345. //
  1346. // Return [0]:
  1347. // Returns the position of the current selection
  1348. // The range is from including -1 to number_of_slots
  1349. //
  1350. // Return [1]:
  1351. // Returns the position value passed in, but corrected for out of bounds
  1352. // The range is from including -1 to number_of_slots
  1353. //
  1354. // Note:
  1355. // Use if you need to calculate a new position that might be out of bounds.
  1356. //
  1357. // Usage [0]: getpositionvalue()
  1358. // Usage [1]: getpositionvalue(position_value_to_test)
  1359. function getpositionvalue(pos) {
  1360.  
  1361.  
  1362. var len = $('.horc-slot').length;
  1363.  
  1364. if (pos === undefined) pos = document.store.position.get();
  1365.  
  1366. if (pos < -1) pos = -1;
  1367. if (len <= pos) pos = len - 1;
  1368.  
  1369. return pos;
  1370. };
  1371.  
  1372. // Get Currently Selected Position
  1373. //
  1374. // Return:
  1375. // Returns the elements that is chosen to hold the UI
  1376. //
  1377. // Usage [0]: getposition()
  1378. function getposition() {
  1379. var slots = $('.horc-slot > .horc-content');
  1380. var pos = getpositionvalue();
  1381. if (pos === -1) { // Find default slot
  1382. var defaultslot = slots.parent('.horc-default').children('.horc-content');
  1383. if (defaultslot.length) {
  1384. return $(defaultslot[0]);
  1385. } else {
  1386. return $(slots[0]);
  1387. }
  1388. }
  1389. return $(slots[pos]);
  1390. };
  1391.  
  1392.  
  1393. // Create Main UI
  1394. function create_mainui() {
  1395. if (0 < $('#horc-mainui').length) throw 'Stopped UserScript from loading again'; // when UserScript loads twice, cancel load
  1396.  
  1397. // Create main ui area
  1398. var mainui = $('<div id="horc-mainui" class="horc-ui draggable">');
  1399. getposition().append(mainui);
  1400.  
  1401.  
  1402. // Add title & logo
  1403. var title = $('<h1>' + HORC_NAME + '</h1>');
  1404. var logo = $('<img>');
  1405. title.prepend(logo);
  1406. logo.css('width', '2em');
  1407. logo.css('height', '2em');
  1408. logo.css('margin-right', '0.5em');
  1409. logo.css('vertical-align', 'middle');
  1410. logo.on('load', function () {
  1411. setTimeout(function () {
  1412. title.slideUp(250, function () {
  1413. title.remove();
  1414. });
  1415. }, 5000);
  1416. });
  1417. logo.prop('src', 'https://bitbucket.org/horc/anime-lighter/raw/master/img/Logo.png');
  1418. mainui.append(title);
  1419.  
  1420.  
  1421. // Updater
  1422. var updater = $('<div>');
  1423. updater.hide().appendTo(mainui);
  1424. var url = $('<span>[<a href="https://openuserjs.org/scripts/RandomClown/Anime_Lighter">OpenUserJS</a>]</span>');
  1425.  
  1426. function nothing(newversion) { console.log('New version available: v' + newversion); }
  1427. function versionchecker(newversion) {
  1428. updater.slideDown(250);
  1429. setTimeout(function () {
  1430. updater.slideUp(250, function () {
  1431. updater.remove();
  1432. });
  1433. }, 10000);
  1434. }
  1435.  
  1436. if (greasyfork) {
  1437. updater.text('May be out-dated. ');
  1438. updater.append(url);
  1439.  
  1440. document.versionchecker = nothing;
  1441.  
  1442. versionchecker('0.0');
  1443. } else {
  1444. updater.text('New version available! ');
  1445. updater.append(url);
  1446.  
  1447. document.versionchecker = document.store.updater.get() ? versionchecker : nothing;
  1448. }
  1449.  
  1450.  
  1451. // Create individual filter lists
  1452. var watchlist = $('<div id="horc-watchlist">');
  1453. var droplist = $('<div id="horc-droplist">');
  1454. var hidelist = $('<div id="horc-hidelist">');
  1455. mainui.append(watchlist);
  1456. mainui.append(droplist);
  1457. mainui.append(hidelist);
  1458.  
  1459.  
  1460. // List headers
  1461. var watchhead = $('<h2 class="horc-listhead">Watch List</h2>');
  1462. var drophead = $('<h2 class="horc-listhead">Drop List</h2>');
  1463. var hidehead = $('<h2 class="horc-listhead">Hide List</h2>');
  1464. watchlist.append(watchhead);
  1465. droplist.append(drophead);
  1466. hidelist.append(hidehead);
  1467. if (!document.store.showwatch.get()) watchhead.addClass('horc-listhead-hide');
  1468. if (!document.store.showdrop.get()) drophead.addClass('horc-listhead-hide');
  1469. if (!document.store.showhide.get()) hidehead.addClass('horc-listhead-hide');
  1470.  
  1471. var watchcontent = $('<div class="horc-content">');
  1472. var dropcontent = $('<div class="horc-content">');
  1473. var hidecontent = $('<div class="horc-content">');
  1474. watchlist.append(watchcontent);
  1475. droplist.append(dropcontent);
  1476. hidelist.append(hidecontent);
  1477. if (!document.store.showwatch.get()) watchcontent.hide();
  1478. if (!document.store.showdrop.get()) dropcontent.hide();
  1479. if (!document.store.showhide.get()) hidecontent.hide();
  1480.  
  1481. watchhead.click(function () {
  1482. if ($(this).prop('disabled')) return;
  1483.  
  1484. watchcontent.slideToggle(250)
  1485. watchhead.toggleClass('horc-listhead-hide');
  1486. document.store.showwatch.set(!document.store.showwatch.get());
  1487. });
  1488. drophead.click(function () {
  1489. if ($(this).prop('disabled')) return;
  1490.  
  1491. dropcontent.slideToggle(250)
  1492. drophead.toggleClass('horc-listhead-hide');
  1493. document.store.showdrop.set(!document.store.showdrop.get());
  1494. });
  1495. hidehead.click(function () {
  1496. if ($(this).prop('disabled')) return;
  1497.  
  1498. hidecontent.slideToggle(250)
  1499. hidehead.toggleClass('horc-listhead-hide');
  1500. document.store.showhide.set(!document.store.showhide.get());
  1501. });
  1502.  
  1503.  
  1504. var watchcount = $('<span class="horc-listcounter">0</span>')
  1505. var dropcount = $('<span class="horc-listcounter">0</span>')
  1506. var hidecount = $('<span class="horc-listcounter">0</span>')
  1507. watchhead.append(watchcount);
  1508. drophead.append(dropcount);
  1509. hidehead.append(hidecount);
  1510. var delay = null;
  1511. $('#horc-mainui').on('anime-update', function () {
  1512. if (!delay) delay = setTimeout(function () {
  1513. delay = null;
  1514.  
  1515. watchcount.text(watchlist.find('.episode').length);
  1516. dropcount.text(droplist.find('.episode').length);
  1517. hidecount.text(hidelist.find('.episode').length);
  1518. }, 100);
  1519. });
  1520.  
  1521. create_settings();
  1522. }
  1523.  
  1524. // Create Settings
  1525. //
  1526. // This will only show if the "settings" flag in localStorage is set
  1527. function create_settings() {
  1528. var mainui = $('#horc-mainui');
  1529.  
  1530. // Create Settings button
  1531. var settingsbutton = $('<button>Settings</button>');
  1532. settingsbutton.click(function () {
  1533. var settings = document.store.settings.get();
  1534. document.store.settings.set(!settings);
  1535.  
  1536. $('#horc-settings').slideToggle(200);
  1537. });
  1538. mainui.append(settingsbutton);
  1539.  
  1540.  
  1541. // Create settings area
  1542. var settingui = $('<div id="horc-settings">');
  1543. mainui.append(settingui);
  1544. settingui.css('text-align', 'center');
  1545. if (!document.store.settings.get()) $('#horc-settings').hide();
  1546.  
  1547.  
  1548. // Create ctrl mode button
  1549. var ctrlmod = $('<input id="horc-ctrlmod" type="checkbox">')
  1550. settingui.append(ctrlmod);
  1551. settingui.append('Require &lt;ctrl&gt; modifier');
  1552. ctrlmod.prop('checked', document.store.requirectrl.get())
  1553. ctrlmod.on('change', function () {
  1554. document.store.requirectrl.set($(ctrlmod).prop('checked'));
  1555. });
  1556.  
  1557.  
  1558. // Create CSS Update button
  1559. var cssupdate = $('<button>Update CSS</button>');
  1560. cssupdate.css('float', 'left');
  1561. cssupdate.click(function () {
  1562.  
  1563.  
  1564. var css = $('#horc-cssbox').val();
  1565. var stringified = JSON.stringify(css);
  1566.  
  1567. // update current CSS style
  1568. document.store.css.set(css);
  1569. $('#horc-css').text(css);
  1570.  
  1571. try {
  1572. StyleFix.styleElement($('#horc-css')[0]);
  1573. } catch (err) {
  1574. // Prefix Free doesnt exist
  1575. }
  1576.  
  1577. // update css dev box
  1578. $('#stringify').val(stringified);
  1579. });
  1580.  
  1581.  
  1582. // Create Clear Storage button
  1583. var clearstorage = $('<button>Clear Storage</button>');
  1584. clearstorage.css('float', 'right');
  1585. clearstorage.css('background-color', 'rgba(255, 0, 0, 0.4)');
  1586. clearstorage.click(function () {
  1587. localStorage.clear();
  1588. window.location.reload(true);
  1589. });
  1590.  
  1591.  
  1592. // Create CSS box
  1593. var cssbox = $('<textarea id="horc-cssbox">');
  1594. cssbox.css('width', '100%');
  1595. cssbox.css('height', '10em');
  1596. cssbox.val(document.store.css.get());
  1597. $(document).on('keydown', '#horc-cssbox', function (e) {
  1598. // Detect save
  1599. if ((e.which == '115' || e.which == '83') && (e.ctrlKey || e.metaKey)) {
  1600. e.preventDefault();
  1601. cssupdate.trigger('click');
  1602. }
  1603. });
  1604.  
  1605.  
  1606. // Create Stringified box
  1607. var stringbox = $('<textarea id="stringify">');
  1608. stringbox.attr('disabled', 'true');
  1609. stringbox.css('width', '100%');
  1610. stringbox.css('height', '8em');
  1611. stringbox.val(JSON.stringify(document.store.css.get()));
  1612.  
  1613.  
  1614. // Append CSS text editors
  1615. settingui.append($('<br>'));
  1616. settingui.append($('<br>'));
  1617.  
  1618. settingui.append(clearstorage);
  1619. settingui.append(cssupdate);
  1620. settingui.append($('<div>Current CSS</div>'));
  1621. settingui.append(cssbox);
  1622.  
  1623. settingui.append($('<br>'));
  1624. settingui.append($('<br>'));
  1625.  
  1626. settingui.append($('<div>Stringified CSS</div>'));
  1627. settingui.append(stringbox);
  1628. }
  1629.  
  1630. // Create status bar
  1631. function create_statusbar() {
  1632. var statusbar = $('<div id="horc-statusbar"><div></div></div>').hide();
  1633. $('body').append(statusbar);
  1634. }
  1635.  
  1636. // Create Episode Drag & Drop UI
  1637. function create_epdragui() {
  1638. // Find panel
  1639. var ep_droppanel = $('<div class="horc-ep-droppanel">').hide();
  1640. $('body').append(ep_droppanel);
  1641. ep_droppanel.css('left', '10em');
  1642. ep_droppanel.css('top', '30em');
  1643.  
  1644.  
  1645. // Create circles
  1646. var watchcircle = $('<div id="watchcircle" class="horc-circle droppable"><div>Watch</div></div>');
  1647. var dropcircle = $('<div id="dropcircle" class="horc-circle droppable"><div>Drop</div></div>');
  1648. var hidecircle = $('<div id="hidecircle" class="horc-circle droppable"><div>Hide</div></div>');
  1649. var clearcircle = $('<div id="clearcircle" class="horc-circle droppable"><div>Clear</div></div>');
  1650. ep_droppanel.append(watchcircle);
  1651. ep_droppanel.append(dropcircle);
  1652. ep_droppanel.append(hidecircle);
  1653. ep_droppanel.append(clearcircle);
  1654. }
  1655.  
  1656.  
  1657. // Create Events
  1658. function create_events() {
  1659. // Make the program interactable
  1660. $(document).on('set-active', function (e) {
  1661. $('#horc-mainui').find('.horc-listhead, .horc-filter-episode, button, input').prop('disabled', false);
  1662. });
  1663. $(document).on('clear-active', function (e) {
  1664. $('#horc-mainui').find('.horc-listhead, .horc-filter-episode, button, input').prop('disabled', true)
  1665. });
  1666.  
  1667. // Make the main UI disappear into a small dragged window
  1668. // Show the position drop panels
  1669. $(document).on('set-ui-droppanel', function (e) {
  1670. $('.horc-ui-droppanel').fadeIn(100);
  1671. $('.horc-posdrag-icon').show();
  1672. //$('.horc-ui-droppanel').find('.horc-circle')
  1673. });
  1674. $(document).on('clear-ui-droppanel', function (e) {
  1675. $('.horc-ui-droppanel').fadeOut(100);
  1676. $('.horc-posdrag-icon').hide();
  1677. });
  1678.  
  1679. // Make a small box displaying an episode entry
  1680. // Show the anime drop panels
  1681. $(document).on('set-ep-droppanel', function (e) {
  1682. var panel = $('.horc-ep-droppanel');
  1683. panel.fadeIn(100);
  1684. $('.horc-episode-icon').show();
  1685.  
  1686. //panel.css('left', document.prop.mousex + 'px');
  1687. //panel.css('top', document.prop.mousey + 'px');
  1688. });
  1689. $(document).on('clear-ep-droppanel', function (e) {
  1690. $('.horc-ep-droppanel').fadeOut(100);
  1691. $('.horc-episode-icon').hide();
  1692. });
  1693.  
  1694. // Update the counters on anime lists
  1695. $(document).on('update-animelist', function (e) {
  1696. // Clear mainui anime list
  1697. $('#horc-watchlist, #horc-droplist, #horc-hidelist').find('.horc-content').empty();
  1698.  
  1699. // Repopulate with the episode listing
  1700. $('.horc-episode').map(function (i, element) {
  1701. var jqe = $(element);
  1702.  
  1703. console.warn('Repopulate Incomplete');
  1704. });
  1705.  
  1706. // Count number of animes in list
  1707. var eps = $('.horc-episode-orig');
  1708. var watchcount = eps.filter('.watch').length;
  1709. var dropcount = eps.filter('.drop').length;
  1710. var hidecount = eps.filter('.hide').length;
  1711. $('#horc-watchlist').find('.horc-listcounter').text(watchcount);
  1712. $('#horc-droplist').find('.horc-listcounter').text(dropcount);
  1713. $('#horc-hidelist').find('.horc-listcounter').text(hidecount);
  1714. });
  1715. }
  1716.  
  1717.  
  1718.  
  1719. this.MainUI();
  1720. };
  1721.  
  1722. MainUI.create_embed = function (jqe, contentstyles, draguistyles) {
  1723. jqe.empty().addClass('horc-slot droppable');
  1724.  
  1725.  
  1726. var dragui = $('<div class="horc-ui-droppanel">').hide();
  1727. dragui.append($('<div class="horc-circle">'));
  1728. jqe.append(dragui);
  1729.  
  1730.  
  1731. var content = $('<div class="horc-content horc-embed">');
  1732. jqe.append(content);
  1733.  
  1734.  
  1735. if (contentstyles) {
  1736. for (var i = 0; i < contentstyles.length; i += 2) {
  1737. content.css(contentstyles[i], contentstyles[i + 1]);;
  1738. }
  1739. }
  1740.  
  1741. if (draguistyles) {
  1742. for (var i = 0; i < draguistyles.length; i += 2) {
  1743. dragui.css(draguistyles[i], draguistyles[i + 1]);
  1744. }
  1745. }
  1746. }
  1747.  
  1748. MainUI.create_sidebar = function (jqe, contentstyles, draguistyles) {
  1749. jqe.empty().addClass('horc-slot droppable');
  1750.  
  1751.  
  1752. var dragui = $('<div class="horc-ui-droppanel">').hide();
  1753. dragui.append($('<div class="horc-circle">'));
  1754. jqe.append(dragui);
  1755.  
  1756.  
  1757. var content = $('<div class="horc-content horc-sidebar">');
  1758. jqe.append(content);
  1759.  
  1760.  
  1761. if (contentstyles) {
  1762. for (var i = 0; i < contentstyles.length; i += 2) {
  1763. content.css(contentstyles[i], contentstyles[i + 1]);;
  1764. }
  1765. }
  1766.  
  1767. if (draguistyles) {
  1768. for (var i = 0; i < draguistyles.length; i += 2) {
  1769. dragui.css(draguistyles[i], draguistyles[i + 1]);
  1770. }
  1771. }
  1772. }
  1773. // Storage Management
  1774. //
  1775. // This will create a new persistant variable
  1776. //
  1777. // Note:
  1778. // Constructor[3] has type checking.
  1779. //
  1780. // Usage [2]: new Store(keyname, value_default)
  1781. // Usage [3]: new Store(keyname, value_default, expected_type)
  1782. var Store = function (keyname, value_default, type) {
  1783. var thisstore = this;
  1784.  
  1785. this.Store = function (keyname, value_default, type) {
  1786.  
  1787.  
  1788.  
  1789.  
  1790.  
  1791. var value = localStorage.getItem(thisstore._keyname);
  1792. if (value === null) {
  1793. thisstore._cache = JSON.parse(thisstore._default);
  1794. } else {
  1795. value = JSON.parse(value);
  1796.  
  1797. if (type && typeof value !== type) {
  1798. console.warn('Loaded data for "' + keyname + '" is not of type "' + type + '"; Using default value');
  1799.  
  1800. thisstore._cache = JSON.parse(thisstore._default);
  1801. } else {
  1802. thisstore._cache = value;
  1803. }
  1804. }
  1805. };
  1806.  
  1807.  
  1808.  
  1809. this.get = function () {
  1810. return thisstore._cache;
  1811. };
  1812.  
  1813. this.set = function (value) {
  1814. if (type && typeof value !== type) {
  1815.  
  1816.  
  1817. thisstore._cache = JSON.parse(thisstore._default);
  1818. }
  1819.  
  1820. thisstore._cache = value;
  1821. localStorage.setItem(thisstore._keyname, JSON.stringify(value));
  1822. if (thisstore.onset) thisstore.onset();
  1823. };
  1824.  
  1825. this.rm = function () {
  1826. localStorage.removeItem(thisstore._keyname);
  1827. };
  1828.  
  1829.  
  1830.  
  1831. this._keyname = keyname;
  1832. this._default = JSON.stringify(value_default);
  1833. this._type = type;
  1834.  
  1835. this._cache = null;
  1836.  
  1837. this.onset = null;
  1838.  
  1839.  
  1840.  
  1841. this.Store(keyname, value_default, type);
  1842. };
  1843. // Store Updater
  1844. //
  1845. // This will force storage to clear for a specific key.
  1846. //
  1847. // Note:
  1848. // You should force refresh the page, in case your other modules loaded 1st.
  1849. //
  1850. // Usage [2]: new StoreUpdater(keyname, new_version)
  1851. var StoreUpdater = function (keyname, newversion) {
  1852.  
  1853.  
  1854.  
  1855. // Initialize position for the UI
  1856. if (!document.getElementById('horc-updater')) {
  1857. var updater = document.createElement('div');
  1858. document.body.insertBefore(updater, document.body.childNodes[0]);
  1859. updater.id = 'horc-updater';
  1860. updater.style.position = 'fixed';
  1861. updater.style.left = '0';
  1862. updater.style.top = '0';
  1863. updater.style.zIndex = '100';
  1864. }
  1865.  
  1866.  
  1867. if (document.horc_updated === undefined) document.horc_updated = false;
  1868.  
  1869.  
  1870. var moduleversion = localStorage.getItem(keyname + '.version');
  1871.  
  1872.  
  1873. if (moduleversion !== newversion) {
  1874. // Site theme changed since last time; Delete key & prepare to reboot
  1875.  
  1876. document.horc_updated = true;
  1877.  
  1878. localStorage.removeItem(keyname);
  1879. localStorage.setItem(keyname + '.version', newversion);
  1880. localStorage.removeItem(keyname + '.updated');
  1881.  
  1882. return;
  1883. }
  1884.  
  1885.  
  1886. if (null === localStorage.getItem(keyname + '.updated')) {
  1887. // Tell the user it updated
  1888.  
  1889. localStorage.setItem(keyname + '.updated', 'true');
  1890.  
  1891. var container = document.createElement('div');
  1892. container.style.color = '#000000';
  1893. container.style.backgroundColor = '#eeffee';
  1894. container.style.padding = '.5em 2em';
  1895. container.style.margin = '.5em';
  1896. container.style.marginLeft = '1em';
  1897. container.style.width = '16em';
  1898. container.style.borderRadius = '0.5em';
  1899. container.style.boxShadow = '0 0 1em #000000';
  1900.  
  1901. var icon = document.createElement('img');
  1902. icon.src = 'https://bitbucket.org/horc/anime-lighter/raw/master/img/Logo.png';
  1903. icon.style.width = '1.2em'
  1904. icon.style.verticalAlign = 'middle'
  1905.  
  1906. var msg = ' <b>' + keyname + '</b> was reset';
  1907.  
  1908. var counter = document.createElement('span');
  1909. counter.style.cssFloat = 'right';
  1910.  
  1911. var desc = document.createElement('span');
  1912. desc.innerHTML = msg;
  1913. desc.appendChild(counter);
  1914.  
  1915. container.appendChild(icon);
  1916. container.appendChild(desc);
  1917.  
  1918. document.getElementById('horc-updater').appendChild(container);
  1919.  
  1920. var time = 10000;
  1921. var _updater = setInterval(function () {
  1922. if (time <= 0) {
  1923. clearInterval(_updater);
  1924. container.remove();
  1925. } else {
  1926. var t = (time / 100 | 0) / 10;
  1927. if (t % 1 === 0) t = t + '.0';
  1928. counter.innerHTML = t;
  1929. time -= 100;
  1930. }
  1931. }, 100);
  1932.  
  1933. container.onclick = function () {
  1934. clearInterval(_updater);
  1935. container.remove();
  1936. }
  1937. }
  1938. }
  1939. /// Dependencies:
  1940. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  1941. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  1942.  
  1943.  
  1944. // Mouse & Touch module
  1945. //
  1946. // Makes mouse & touch interaction consistent
  1947. // This guarantees that for every "start" event, there is a matching "end" event
  1948. // Assign the event handler to the correct functions of these
  1949. //
  1950. // If you need to test against left/middle/right click, negate the number before testing
  1951. // Real touch events will use positive numbers: 0, 1, 2, +
  1952. // Mouse events will use negative numbers, -1, -2, -3
  1953. //
  1954. // Important functions:
  1955. // .mousedown | Event proxies that you must pass to an object when binding
  1956. // .mouseup | ^
  1957. // .mousemove | ^
  1958. // .touchstart | ^
  1959. // .touchend | ^
  1960. // .touchmove | ^
  1961. //
  1962. // Important Variables:
  1963. // .onstart | Callback for mouse or touch start event
  1964. // .onend | Callback for matching end event
  1965. // .ondragstart | Callback for when a drag is detected
  1966. // .ondragend | Callback for matching end event
  1967. // .onmove | Callback for matching move event
  1968. // .onfirst | Callback for 1st start event
  1969. // .onlast | Callback for last end event
  1970. //
  1971. // Constructor:
  1972. // Constructor takes 3 functions, listed below
  1973. // Make sure each of those are defined with parameters(identifiers, changedtouches, event)
  1974. //
  1975. // Usage[0]: new Touch()
  1976. var Touch = function () {
  1977. var thistouch = this;
  1978.  
  1979.  
  1980.  
  1981. var touchsupport = 'ontouchstart' in window;
  1982.  
  1983. this.Touch = function () {
  1984.  
  1985. };
  1986.  
  1987.  
  1988.  
  1989. this.mousedown = function (e) {
  1990.  
  1991.  
  1992. if (e.which < 1) return;
  1993. if (thistouch.ismdown) return;
  1994. thistouch.ismdown = true;
  1995. var isfirst = !thistouch.dragging;
  1996.  
  1997. thistouch.mousetouch = new VirtualTouchEvent(e, -e.which);
  1998. thistouch.target = e.originalEvent.target;
  1999.  
  2000. recheck_mouse(e);
  2001.  
  2002. $(document).on('mouseup mousemove mouseenter mouseleave', mousemove_correction);
  2003.  
  2004. var changes = e.originalEvent.changedTouches;
  2005.  
  2006. // Trigger first
  2007. if (thistouch.onfirst && isfirst) thistouch.onfirst([-e.which], changes, e);
  2008.  
  2009. // Trigger mouse start
  2010.  
  2011. if (thistouch.onstart) thistouch.onstart([-e.which], changes, e);
  2012.  
  2013. e.done = true; // Notify mouse correction handler
  2014. };
  2015. this.mousemove = function (e) {
  2016.  
  2017.  
  2018. if (e.which < 1) return;
  2019. if (!thistouch.ismdown) return;
  2020.  
  2021. recheck_mouse(e);
  2022.  
  2023. var changes = e.originalEvent.changedTouches;
  2024.  
  2025. // Trigger dragging
  2026. if (!isdragging(-e.which)) {
  2027. setdragging(-e.which);
  2028. if (thistouch.ondragstart) thistouch.ondragstart([-e.which], changes, e);
  2029. }
  2030.  
  2031. // Trigger moving
  2032. if (thistouch.onmove) thistouch.onmove([-e.which], changes, e);
  2033.  
  2034. e.done = true; // Notify mouse correction handler
  2035. };
  2036. this.mouseup = function (e) {
  2037.  
  2038.  
  2039. if (e.which < 1) return;
  2040. if (!thistouch.ismdown) return;
  2041. thistouch.ismdown = false;
  2042.  
  2043. e.originalEvent.target = thistouch.target;
  2044.  
  2045. recheck_mouse(e);
  2046.  
  2047. var changes = e.originalEvent.changedTouches;
  2048.  
  2049. // Trigger dragging
  2050. if (isdragging(-e.which)) {
  2051. cleardragging(-e.which);
  2052. if (thistouch.ondragend) thistouch.ondragend([-e.which], e.originalEvent.changedTouches, e);
  2053. }
  2054.  
  2055. // Trigger mouse end
  2056. if (thistouch.onend) thistouch.onend([-e.which], changes, e);
  2057.  
  2058.  
  2059. // Trigger last
  2060. if (thistouch.onlast) thistouch.onlast([-e.which], changes, e);
  2061.  
  2062. thistouch.mousetouch = null; // Delete last mouse
  2063.  
  2064. e.done = true; // Notify mouse correction handler
  2065. };
  2066.  
  2067.  
  2068. this.touchstart = function (e) {
  2069.  
  2070.  
  2071. normalize(e);
  2072.  
  2073. var isfirst = !thistouch.istdown;
  2074. thistouch.istdown = true;
  2075.  
  2076. // For all changes
  2077. var changes = e.originalEvent.changedTouches;
  2078. var ids = $(changes).map(function (i, changed) { return changed.identifier; });
  2079.  
  2080.  
  2081.  
  2082. // Trigger first
  2083. if (isfirst && thistouch.onfirst) thistouch.onfirst(ids, changes, e);
  2084.  
  2085. // Trigger touch start
  2086. if (thistouch.onstart) thistouch.onstart(ids, changes, e);
  2087.  
  2088. e.done = true; // Notify mouse correction handler
  2089. };
  2090. this.touchmove = function (e) {
  2091.  
  2092.  
  2093. try { // Unknown error possible where the user taps once extremely fast [takes a lot of tries to replicate]
  2094. normalize(e);
  2095. } catch (err) {
  2096. return; // Just skip this move event
  2097. }
  2098.  
  2099. // For all changes
  2100. var changes = e.originalEvent.changedTouches;
  2101. var ids = $(changes).map(function (i, changed) { return changed.identifier; });
  2102. var changes_start = [];
  2103. var ids_start = [];
  2104. $(changes).map(function (i, changed) {
  2105. var id = changed.identifier;
  2106. if (!isdragging(id)) {
  2107. setdragging(id);
  2108. changes_start.push(changed);
  2109. ids_start.push(id);
  2110. }
  2111. });
  2112.  
  2113. // Trigger dragging
  2114. if (ids_start.length && thistouch.ondragstart) thistouch.ondragstart(ids_start, changes_start, e);
  2115.  
  2116. // Trigger moving
  2117. if (thistouch.onmove) thistouch.onmove(ids, changes, e);
  2118.  
  2119. e.done = true; // Notify mouse correction handler
  2120. };
  2121. this.touchend = function (e) {
  2122.  
  2123.  
  2124. try { // Unknown error possible where the user taps once extremely fast [takes a lot of tries to replicate]
  2125. normalize(e);
  2126. } catch (err) {
  2127. console.error('Error: Unknown issue where user performs 2 finger right click.\nThat breaks: normalize::importchanges::changes.push'); // Allow cleanup to happen, but alert developer
  2128. console.warn('Attempting to recover from error')
  2129. }
  2130.  
  2131. // For all changes
  2132. var changes = e.originalEvent.changedTouches;
  2133. var ids = $(changes).map(function (i, changed) { return changed.identifier; });
  2134. var changes_end = [];
  2135. var ids_end = [];
  2136. $(changes).map(function (i, changed) {
  2137. var id = changed.identifier;
  2138. if (isdragging(id)) {
  2139. cleardragging(id);
  2140. changes_end.push(changed);
  2141. ids_end.push(id);
  2142. }
  2143. });
  2144.  
  2145. // Trigger dragging
  2146. if (ids_end.length && thistouch.ondragend) thistouch.ondragend(ids_end, changes_end, e);
  2147.  
  2148. // Trigger touch end
  2149. if (thistouch.onend) thistouch.onend(ids, changes, e);
  2150.  
  2151.  
  2152. // Trigger last
  2153. if (!e.originalEvent.touches.length) thistouch.istdown = false;
  2154. var islast = !thistouch.istdown;
  2155. if (thistouch.onlast && islast) thistouch.onlast(ids, changes, e);
  2156.  
  2157. e.done = true; // Notify mouse correction handler
  2158. };
  2159.  
  2160.  
  2161. // Unified handler for all
  2162. this.onstart = null;
  2163. this.onend = null;
  2164. this.ondragstart = null;
  2165. this.ondragend = null;
  2166. this.onmove = null;
  2167. this.onfirst = null;
  2168. this.onlast = null;
  2169.  
  2170.  
  2171. // Check Dragging State
  2172. //
  2173. // Used to keep track of whats being dragged
  2174. //
  2175. // Usage [1]: setdragging(id)
  2176. function isdragging(id) {
  2177.  
  2178. var shift = (0x1 << (id + 3));
  2179. return !!(thistouch.dragging & shift);
  2180. }
  2181. // Set Dragging State
  2182. //
  2183. // Used to keep track of whats being dragged
  2184. //
  2185. // Usage [1]: setdragging(id)
  2186. // Usage [2]: setdragging(ids)
  2187. function setdragging(ids) {
  2188.  
  2189. if (ids.length === undefined) {
  2190. var shift = (0x1 << (ids + 3));
  2191. thistouch.dragging |= shift;
  2192. } else {
  2193. $(ids).map(function (i, id) { setdragging(id); });
  2194. }
  2195. }
  2196. // Clear Dragging State
  2197. //
  2198. // Used to keep track of whats being dragged
  2199. //
  2200. // Usage [1]: cleardragging(id)
  2201. // Usage [2]: cleardragging(ids)
  2202. function cleardragging(ids) {
  2203.  
  2204. if (ids.length === undefined) {
  2205. var shift = (0x1 << (ids + 3));
  2206. thistouch.dragging &= ~shift;
  2207. } else {
  2208. $(ids).map(function (i, id) { cleardragging(id); });
  2209. }
  2210. }
  2211.  
  2212.  
  2213. // Normalize
  2214. //
  2215. // Combines .which, .client, .changedTouches, .touches, & .targetnow
  2216. //
  2217. // Usage [1]: normalize(event)
  2218. function normalize(e) {
  2219.  
  2220. if (e.done) return;
  2221.  
  2222. if (e.originalEvent.changedTouches === undefined) e.originalEvent.changedTouches = [];
  2223. if (e.originalEvent.touches === undefined) e.originalEvent.touches = [];
  2224.  
  2225. for (var i = 0; i < e.originalEvent.changedTouches.length; i++) {
  2226. var changed = e.originalEvent.changedTouches[i];
  2227. changed.target = thistouch.target;
  2228. changed.targetnow = gettargetnow(e);
  2229. }
  2230.  
  2231. var changed = e.originalEvent.changedTouches;
  2232. if (e.which && changed.length) {
  2233. delete changed.target; // a bug requires original target to be deleted before reassignment
  2234. changed.target = thistouch.target;
  2235. }
  2236.  
  2237. var changes = e.originalEvent.changedTouches;
  2238. if (thistouch.mousetouch) changes.push(thistouch.mousetouch);
  2239.  
  2240. var touches = e.originalEvent.touches;
  2241. if (thistouch.mousetouch) touches.push(thistouch.mousetouch);
  2242. }
  2243. // Recheck Mouse
  2244. //
  2245. // For mouse events, this ensures consistency between mouse & touch.
  2246. //
  2247. // Note:
  2248. // Only for mouse events; Don't call this from a touch event.
  2249. //
  2250. // Usage [1]: recheck_mouse(event)
  2251. function recheck_mouse(e) {
  2252.  
  2253.  
  2254. normalize(e);
  2255.  
  2256. if (thistouch.mousetouch) {
  2257. thistouch.mousetouch.copyfrom(e);
  2258. }
  2259.  
  2260. if (e.which !== thistouch.lastmouse) {
  2261. if (thistouch.lastmouse) {
  2262. var nowwhich = e.which;
  2263. e.which = thistouch.lastmouse;
  2264. thistouch.mouseup(e);
  2265. e.which = nowwhich;
  2266. }
  2267.  
  2268. thistouch.lastmouse = e.which;
  2269. }
  2270. }
  2271. // Mouse Move Correction
  2272. //
  2273. // Bound when a mousedown happens
  2274. // This ensures that a mouse up will happen
  2275. //
  2276. // Note:
  2277. // Only for mouse events; Don't call this from a touch event.
  2278. //
  2279. // Usage [1]: recheck_mouse(event)
  2280. function mousemove_correction(e) {
  2281. if (e.done) return;
  2282. recheck_mouse(e);
  2283. if (!thistouch.mousetouch) {
  2284. $(document).off('mouseup mousemove mouseenter mouseleave', mousemove_correction);
  2285. thistouch.lastmouse = 0;
  2286. thistouch.mouseup(e);
  2287. } else {
  2288. thistouch.mousemove(e);
  2289. }
  2290. }
  2291.  
  2292.  
  2293. // Get Cursor Position
  2294. //
  2295. // Note:
  2296. // Requires at least 1 touch or mouse point to exist.
  2297. //
  2298. // Usage [1]: getcursor(event)
  2299. function getcursor(e) {
  2300.  
  2301.  
  2302. var changed = e.originalEvent.changedTouches;
  2303. if (changed && 0 < changed.length) {
  2304. var touch = e.originalEvent.changedTouches[0];
  2305. return [touch.clientX, touch.clientY];
  2306. } else if (e.clientX !== undefined) {
  2307. return [e.clientX, e.clientY];
  2308. }
  2309.  
  2310.  
  2311. return null;
  2312. }
  2313. // Get Target Now
  2314. //
  2315. // Note:
  2316. // Requires at least 1 touch or mouse point to exist.
  2317. //
  2318. // Usage [1]: gettargetnow(event)
  2319. function gettargetnow(e) {
  2320.  
  2321.  
  2322. var pos = getcursor(e);
  2323. if (pos) return document.elementFromPoint(pos[0], pos[1]);
  2324. return null;
  2325. }
  2326.  
  2327.  
  2328. // Virtual Touch Event
  2329. //
  2330. // Note:
  2331. // Constructor Takes the mouse event & which mouse.
  2332. // It has a copyfrom() method to copy new XY values.
  2333. // copyfrom() will not override the id or element target.
  2334. var VirtualTouchEvent = function (e, id) {
  2335.  
  2336.  
  2337.  
  2338.  
  2339. this.clientX = e.originalEvent.clientX;
  2340. this.clientY = e.originalEvent.clientY;
  2341. this.force = 0; // Pressure
  2342. this.identifier = id; // ID of this touch event
  2343. this.pageX = e.originalEvent.pageX;
  2344. this.pageY = e.originalEvent.pageY;
  2345. this.radiusX = 25; // Default to 25 for mouse
  2346. this.radiusY = 25; // ^
  2347. this.screenX = e.originalEvent.screenX;
  2348. this.screenY = e.originalEvent.screenY;
  2349. this.target = e.originalEvent.target; // Element that triggered the event
  2350. this.targetnow = gettargetnow(e); // Element the mouse is focused on
  2351.  
  2352. this.copyfrom = function (e) {
  2353. this.clientX = e.originalEvent.clientX;
  2354. this.clientY = e.originalEvent.clientY;
  2355. this.pageX = e.originalEvent.pageX;
  2356. this.pageY = e.originalEvent.pageY;
  2357. this.screenX = e.originalEvent.screenX;
  2358. this.screenY = e.originalEvent.screenY;
  2359. this.targetnow = gettargetnow(e); // Element the mouse is focused on
  2360. };
  2361. }
  2362.  
  2363. this.mousetouch = null;
  2364. this.dragging = 0x0; // dragging flag
  2365. this.target = null;
  2366.  
  2367. this.lastmouse = 0;
  2368.  
  2369. this.ismdown = false;
  2370. this.istdown = false;
  2371.  
  2372.  
  2373. this.Touch();
  2374. };
  2375. /// Dependencies:
  2376. /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
  2377. /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
  2378.  
  2379.  
  2380. // Get URL Search +1 overloads
  2381. //
  2382. // This will read a URL for the search query
  2383. //
  2384. // Examples of accepted URLs:
  2385. // http://horc.bitbucket.org/
  2386. // http://horc.bitbucket.org?
  2387. // http://horc.bitbucket.org/?path=projects
  2388. // ?path=projects/test/
  2389. // path=projects/test&
  2390. //
  2391. // Return:
  2392. // Object with the key & string values
  2393. //
  2394. // Usage [0]: geturlsearch()
  2395. // Usage [1]: geturlsearch(string_url)
  2396. function geturlsearch(url) {
  2397. if (typeof url !== 'string') {
  2398. if (url === undefined) url = location.search;
  2399.  
  2400. }
  2401.  
  2402. function cleanurlsearch(url) {
  2403. console.warn('\t' + url);
  2404. if (-1 < url.search(/:\/\//)) { // This is a url
  2405. var p = url.search(/\?/);
  2406. if (p === -1) url = '';
  2407. else url = url.substr(p + 1);
  2408. }
  2409.  
  2410. if (url.substr(0, 1) === '?') url = url.substr(1);
  2411.  
  2412. return url;
  2413. }
  2414.  
  2415. url = cleanurlsearch(url);
  2416.  
  2417. var params = {};
  2418. var split = url.split('&');
  2419. for (var i = 0; i < split.length; i++) {
  2420. var u = split[i];
  2421. if (u.length) {
  2422. var p = u.search('=');
  2423. if (-1 < p) {
  2424. var lhs = u.substr(0, p);
  2425. var rhs = u.substr(p + 1);
  2426. if (!lhs.length) continue;
  2427. params[lhs] = rhs;
  2428. } else {
  2429. var lhs = u;
  2430. params[lhs] = '';
  2431. }
  2432. }
  2433. }
  2434. return params;
  2435. }
  2436.  
  2437.  
  2438. // Load Script +3 overloads
  2439. //
  2440. // This will sequentially load 1 script at a time.
  2441. // Run multiple times to load in parallel
  2442. //
  2443. // Usage [1]: loadscript(string_url)
  2444. // Usage [2]: loadscript(string_url, oncomplete_function)
  2445. // Usage [1]: loadscript(array_of_urls)
  2446. // Usage [2]: loadscript(array_of_urls, oncomplete_function)
  2447. function loadscript(urls, oncomplete) {
  2448. if (typeof urls === 'object') {
  2449.  
  2450. if (!urls.length) {
  2451. if (oncomplete) return oncomplete();
  2452. return;
  2453. }
  2454.  
  2455. loadscript(urls[0], function () {
  2456. loadscript(urls.slice(1), oncomplete);
  2457. });
  2458. } else if (typeof urls === 'string') {
  2459. var url = urls;
  2460.  
  2461. var scripts = document.getElementsByTagName('script');
  2462. for (var i = 0; i < scripts.length; ++i) {
  2463. if (scripts[i].src === url) {
  2464. console.log(' Script already loaded:\n' + url);
  2465. if (oncomplete) oncomplete();
  2466. return;
  2467. }
  2468. }
  2469.  
  2470. var e = document.createElement('script');
  2471. e.src = url;
  2472. e.onload = function () {
  2473. console.log(' Injected script:\n' + url);
  2474. if (oncomplete) oncomplete();
  2475. }
  2476. e.onerror = function () {
  2477. e.remove();
  2478. console.warn(' Failed to injected script:\n' + url);
  2479. if (oncomplete) oncomplete();
  2480. }
  2481. document.head.appendChild(e);
  2482.  
  2483. } else {
  2484.  
  2485. }
  2486. }
  2487.  
  2488.  
  2489. // Load Style +3 overloads
  2490. //
  2491. // This will sequentially load 1 style at a time.
  2492. // Run multiple times to load in parallel
  2493. //
  2494. // Usage [1]: loadstyle(string_url)
  2495. // Usage [2]: loadstyle(string_url, oncomplete_function)
  2496. // Usage [1]: loadstyle(array_of_urls)
  2497. // Usage [2]: loadstyle(array_of_urls, oncomplete_function)
  2498. function loadstyle(urls, oncomplete) {
  2499. if (typeof urls === 'object') {
  2500.  
  2501. if (!urls.length) {
  2502. if (oncomplete) return oncomplete();
  2503. return;
  2504. }
  2505.  
  2506. loadstyle(urls[0], function () {
  2507. loadstyle(urls.slice(1), oncomplete);
  2508. });
  2509. } else if (typeof urls === 'string') {
  2510. var url = urls;
  2511.  
  2512. var links = document.getElementsByTagName('link');
  2513. for (var i = 0; i < links.length; ++i) {
  2514. if (links[i].href === url) {
  2515. console.log(' Style already loaded:\n' + url);
  2516. if (oncomplete) oncomplete();
  2517. return;
  2518. }
  2519. }
  2520. var styles = document.getElementsByTagName('style');
  2521. for (var i = 0; i < styles.length; ++i) {
  2522. if (styles[i].attributes && (url === styles[i].attributes.src.value)) {
  2523. console.log(' Style already loaded:\n' + url);
  2524. if (oncomplete) oncomplete();
  2525. return;
  2526. }
  2527. }
  2528.  
  2529. var e = document.createElement('link');
  2530. e.rel = 'stylesheet';
  2531. e.type = 'text/css';
  2532. e.href = url;
  2533. e.onload = function () {
  2534. console.log(' Injected style:\n' + url);
  2535. if (oncomplete) oncomplete();
  2536. }
  2537. e.onerror = function () {
  2538. e.remove();
  2539. console.warn(' Failed to injected style:\n' + url);
  2540. if (oncomplete) oncomplete();
  2541. }
  2542. document.head.appendChild(e);
  2543.  
  2544. } else {
  2545.  
  2546. }
  2547. }
  2548.  
  2549.  
  2550. function recheck_prefixfree() {
  2551. var sleeptime = 200;
  2552. var maxtime = 10000;
  2553. var addtime = Date.now();
  2554.  
  2555. function helper() {
  2556. var realtimemax = maxtime + addtime;
  2557. if (realtimemax < Date.now()) return;
  2558.  
  2559. var time = Date.now();
  2560.  
  2561. setTimeout(function () {
  2562. try {
  2563. StyleFix.link; // test for Prefix Free
  2564.  
  2565. var links = document.getElementsByTagName('link');
  2566. if (links.length) {
  2567. addtime = Date.now();
  2568. for (var i = 0; i < links.length; ++i) {
  2569. StyleFix.link(links[i]);
  2570. }
  2571. }
  2572. } catch (err) {
  2573. }
  2574.  
  2575. helper();
  2576. }, sleeptime);
  2577. }
  2578.  
  2579. helper();
  2580. }
  2581.  
  2582.  
  2583.  
  2584. function benchmark(f) {
  2585. s = Date.now();
  2586. for (var i = 0; i < 1000000; ++i) f();
  2587. s = Date.now() - s;
  2588. return s / 1000;
  2589. }