您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Filter for anime trackers [planning for many]
- // ==UserScript==
- // @name Anime Lighter
- // @namespace horc.net
- // @version 3.7.1
- // @description Filter for anime trackers [planning for many]
- // @author RandomClown @ HoRC
- // @copyright © 2015
- // @homepage http://git.horc.net
- // @icon https://bitbucket.org/horc/anime-lighter/raw/master/img/Logo.png
- // @grant GM_xmlhttpRequest
- // @grant GM_log
- // @require http://code.jquery.com/jquery-2.1.3.min.js
- // @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
- // @match http://www.nyaa.se/*
- // @match http://horriblesubs.info
- // ==/UserScript==
- var greasyfork = true;
- //
- //
- // Source code available: https://bitbucket.org/horc/anime-lighter/
- //
- //
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- /// <reference path="Class AnimeFilter.js" />
- /// <reference path="Class LighterUI.js" />
- /// <reference path="Class Run.js" />
- /// <reference path="Class StoreUpdater.js" />
- /// <reference path="Standard.js" />
- var HORC_NAME = 'Anime Lighter Beta';
- // Wait for ready
- new function () {
- var runonce = 0;
- function complete() {
- if (document.readyState === 'complete') {
- document.removeEventListener('DOMContentLoaded', complete, false);
- window.removeEventListener('load', complete, false);
- if (runonce++) return;
- setTimeout(ready);
- } else {
- document.addEventListener('DOMContentLoaded', complete, false);
- window.addEventListener('load', complete, false);
- }
- }
- complete();
- }
- // Now ready
- function ready() {
- new StoreUpdater('horc-animes', '1.0'); // <-- refrain from updating this one
- new StoreUpdater('horc-position', '1.1');
- new StoreUpdater('horc-style', '1.0');
- if (document.horc_updated) {
- location.reload(true);
- return;
- }
- run_animelighter();
- }
- // Run main module
- function run_animelighter() {
- // Load site mod
- var mod = document.mod;
- delete document.readymods;
- console.log(' Host: ' + window.location.host);
- mod.createpositions();
- new MainUI(mod.style);
- new AnimeFilter(mod.filteroptions);
- mod.run();
- }
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- // Get Anime - Generic
- //
- // Parse an episode text for the anime name
- // Works for most things
- function default_getanime(jqe) {
- var str = jqe.text();
- return smartget(str);
- }
- ////////////////////////////////////////
- //// Useful Functions
- function smartget(str) {
- // Make it sane
- var str = trimws(str.replace(/_/g, ' ').replace(/\s\s+/g, ' '));
- if (!str.length) throw 'Expected a string';
- // Get rid of leading subber
- if (-1 < str.charAt(0).search(/[\[\(\{]/)) str = trimws(consumetag(str));
- // Get rid of next tag & beyond
- str = trimws(str.substr(0, str.search(/[\[\(\{]/)));
- // Get rid of episode number[s] of format
- str = trimws(consumenumber(str));
- if (!str.length) throw 'Couldn\'t get the name';
- return str;
- }
- // Consume Tag
- //
- // Gets rid of 1 tag: [subber], [1080p], [etc]
- function consumetag(str) {
- var mode = false, done = false;
- var count = 0;
- var s = 0, e = 0;
- for (var i = 0; i < str.length; ++i) {
- if (done) break;
- switch (str.charAt(i)) {
- case '[':
- case '(':
- case '{':
- mode = true;
- count++;
- break;
- case ']':
- case ')':
- case '}':
- count--;
- if (mode && !count) done = true;
- break;
- default:
- break;
- }
- if (mode) e++;
- else s++, e++;
- }
- return str.substr(0, s) + str.substr(e);
- }
- // Consume Number
- //
- // Gets rid of the episode number
- function consumenumber(str) {
- // Consumes numbers with a tack before
- function cnum_tack(str) {
- for (var s = 0; s < str.length ; ++s) {
- var hitnum = false;
- if (str.charAt(s) === '-' || str.charAt(s) === '‒') {
- var n = s + 1;
- // Consume ws
- for (; n < str.length; ++n) if (!isws(str.charAt(n))) break;
- // Consume number
- for (; n < str.length; ++n) {
- if (-1 < str.charAt(n).search(/\d/)) hitnum = true;
- break;
- }
- if (hitnum) {
- return str.substr(0, s);
- }
- }
- }
- return str;
- }
- // Consumes numbers without a tack before
- function cnum_none(str) {
- var digits = 0;
- var i = 0;
- for (i = str.length - 1; 0 <= i; --i) {
- if (-1 < str.charAt(i).search(/\d/)) digits++;
- if (str.charAt(i).search(/\d/) === -1) break;
- }
- if (i === 0) return str; // reached the end
- if (1 < digits && isws(str.charAt(i))) return str.substr(0, i); // lone number & at least 2 digits
- return str; // some mixed number like: S2
- }
- str = cnum_tack(str);
- str = cnum_none(str);
- return str;
- }
- // Trim Whitespace
- //
- // Gets rid of beginning & ending whitespace
- function trimws(str) {
- var mode = false, done = false;
- var count = 0;
- var s = 0, e = 0;
- for (s = 0; s < str.length; ++s) {
- if (str.charAt(s) === ' ') continue;
- if (str.charAt(s) === '\t') continue;
- if (str.charAt(s) === '\r') continue;
- if (str.charAt(s) === '\n') continue;
- break;
- }
- for (e = str.length - 1; 0 <= e; --e) {
- if (str.charAt(e) === ' ') continue;
- if (str.charAt(e) === '\t') continue;
- if (str.charAt(e) === '\r') continue;
- if (str.charAt(e) === '\n') continue;
- break;
- }
- return str.substr(s, e - s + 1);
- }
- function isws(str) {
- 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;
- return true;
- }
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- /// <reference path="_ Host All.js" />
- if ('horriblesubs.info' === window.location.host) {
- var options = {};
- 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";
- options.createpositions = function () { // Create possible UI positions
- MainUI.create_sidebar($('<div>').insertBefore($('h2:contains("Releases")')), undefined, ['position', 'absolute', 'right', '4em', ]);
- MainUI.create_embed($('<div>').insertBefore($('h2:contains("Releases")')));
- MainUI.create_embed($('<div>').insertAfter($('.episodecontainer')));
- MainUI.create_embed($('<div>').prependTo($('#sidebar')));
- MainUI.create_embed($('<div class="horc-default">').insertAfter($('#text-16')));
- MainUI.create_embed($('<div>').insertAfter($('#text-8')));
- MainUI.create_embed($('<div>').appendTo($('#sidebar')));
- };
- options.filteroptions = {
- // What contains the episode listing?
- epcontainer: '.episodecontainer',
- // How to get an episode element?
- epselector: '.episodecontainer .episode',
- // How to get anime name from an episode element?
- getanimename: default_getanime,
- // How to grep episode container for an anime?
- epsearch: function (animename) {
- return $('.episodecontainer .episode').filter(':contains("' + animename + '")');
- },
- };
- options.run = function () {
- // Find every way to update content
- function refresh(e) {
- var lastcount = 0;
- var timeout = 8000;
- var _check = setInterval(function () {
- var episodes = $('.episodecontainer .episode:not(.draggable)');
- if (lastcount != episodes.length) {
- lastcount = episodes.length;
- document.refreshfilters();
- timeout = 1000;
- return;
- } else if (0 < timeout) {
- timeout -= 50;
- return;
- }
- clearInterval(_check);
- }, 50);
- }
- function refresh_enter(e) { if (e.which == 13) refresh(); }
- function refresh_click(e) { refresh(); }
- $('.searchbar').on('keyup', refresh_enter);
- $('.refreshbutton').on('keyup', refresh_enter);
- $('.refreshbutton').on('mouseup', refresh_click);
- $('.morebox').on('keyup', '.morebutton, .searchmorebutton', refresh_enter);
- $('.morebox').on('mouseup', '.morebutton, .searchmorebutton', refresh_click);
- };
- document.mod = options;
- }
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- /// <reference path="_ Host All.js" />
- if ('www.nyaa.se' === window.location.host) {
- var options = {};
- 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";
- options.createpositions = function () { // Create possible UI positions
- MainUI.create_sidebar($('<div>').insertBefore($('#main')), undefined, ['position', 'absolute', 'right', '4em', 'top', '20em']);
- MainUI.create_embed($('<div>').insertBefore($('#main .tlistsortorder')), ['width', '100%', 'text-align', 'center']);
- MainUI.create_embed($('<div class="horc-default">').insertAfter($('#main .torrentsubcatlist')), ['width', '100%', 'text-align', 'center']);
- };
- options.filteroptions = {
- // What contains the episode listing?
- epcontainer: '.tlist',
- // How to get an episode element?
- epselector: '.tlist .tlistrow',
- // How to get anime name from an episode element?
- getanimename: default_getanime,
- // How to grep episode container for an anime?
- epsearch: function (animename) {
- return $('.tlist .tlistrow').has('.tlistname:contains("' + animename + '")');
- },
- };
- options.run = function () {
- };
- document.mod = options;
- }
- // String Compare Case-Sensitive
- function strcmp(lhs, rhs) {
- for (var i = 0; i < lhs.length && i < rhs.length; ++i) {
- if (lhs[i] === rhs[i]) continue;
- return lhs[i] < rhs[i] ? -1 : 1;
- }
- if (lhs.length === rhs.length) return 0;
- return lhs.length < rhs.length ? -1 : 1;
- }
- // String Compare Case-Insensitive
- function strcmpi(lhs, rhs) {
- for (var i = 0; i < lhs.length && i < rhs.length; ++i) {
- if (lhs[i].toLowerCase() === rhs[i].toLowerCase()) continue;
- return lhs[i].toLowerCase() < rhs[i].toLowerCase() ? -1 : 1;
- }
- if (lhs.length === rhs.length) return 0;
- return lhs.length < rhs.length ? -1 : 1;
- }
- // Binary Search Template
- //
- // In the options, method_options below, you can override the methods:
- //
- // int compare(element_to_find, element_in_container)
- // Expected return: -1, 0, 1
- // Determines if the element is less or greater than
- // Behavior should match this: http://www.cplusplus.com/reference/cstring/strcmp/
- //
- // int length(container)
- // Expected return: int
- // Redefines how to check the size of the container [default container.length]
- //
- // ElementType get(i, container)
- // Expected return: a type matching the parameters of compare(e0, e1)
- // Redefines how to access the container by some index
- //
- // * found(i, container)
- // Expected return: anything you require
- // Redefines what to return
- //
- // * notfound(i, container)
- // Expected return: anything you require
- // Redefines what to return when not found [default null]
- // This function is useful to ask "where to insert", since
- // "i" represent the position to insert the new element, using splice:
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
- //
- // Usage [2]: int, null binsearch(string_to_find, array_of_strings)
- // Usage [3]: custom binsearch(element_to_find, container_object, method_options)
- function binsearch(element, container, options) {
- if (options === undefined) options = {
- compare: undefined,
- length: undefined,
- get: undefined,
- found: undefined,
- notfound: undefined,
- };
- var compare = options.compare;
- var length = options.length;
- var get = options.get;
- var found = options.found;
- var notfound = options.notfound;
- if (compare === undefined) compare = strcmpi;
- function default_len(container) { return container.length; }
- if (length === undefined) length = default_len;
- function default_arrget(i, container) { return container[i]; }
- if (get === undefined) get = default_arrget;
- function default_arrret(i, container) { return i; }
- if (found === undefined) found = default_arrret;
- function default_arrnotret(i, container) { return null; }
- if (notfound === undefined) notfound = default_arrnotret;
- if (!length(container)) return notfound(0, container);
- var begin = 0;
- var end = length(container) - 1;
- var mid = begin + (end - begin) / 2 | 0;
- while (true) {
- if (begin === end) {
- switch (compare(element, get(mid, container))) {
- case -1:
- return notfound(mid, container);
- case 0:
- return found(mid, container);
- case 1:
- return notfound(mid + 1, container);
- }
- }
- switch (compare(element, get(mid, container))) {
- case -1:
- end = mid;
- mid = begin + (end - begin) / 2 | 0;
- break;
- case 0:
- return found(mid, container);
- case 1:
- begin = mid + 1;
- mid = begin + (end - begin) / 2 | 0;
- break;
- }
- }
- }
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- /// <reference path="Algorithms.js" />
- /// <reference path="Class Store.js" />
- var FLAG_WATCH = 0;
- var FLAG_DROP = 1;
- var FLAG_HIDE = 2;
- var ANIME_NAME = 0;
- var ANIME_FLAG = 1;
- var ANIME_DATE = 2;
- var ANIME_EXPIRE = 3;
- // Anime Structure
- //
- // Holds information on an anime
- //
- // Member Data:
- // - name | Name of the anime according to the tracker
- // - flag | View flag
- // - date_y | Date added
- // - date_m | ^
- // - date_d | ^
- // - expire_y | Date this will expire on [-1 for never]
- // - expire_m | ^
- // - expire_d | ^
- //
- // Member Functions:
- // - isgood | Test if expired
- // - setexpirecours | Set the expiration date ## "anime network season" from today
- // - setexpireweeks | Set the expiration date ## weeks from today
- // - setexpiredays | Set the expiration date ## days from today
- // - compress | Compress the data for storage
- // - decompress | Copies data from a compressed data array
- //
- // Usage [0]: new Anime()
- // Usage [1]: new Anime(anime_dataset)
- var Anime = function (anime_dataset) {
- var thisanime = this;
- this.Anime = function (anime_dataset) {
- if (typeof anime_dataset === 'object') {
- thisanime.decompress(anime_dataset);
- } else {
- var d = new Date();
- thisanime.date_y = d.getFullYear();
- thisanime.date_m = d.getMonth() + 1;
- thisanime.date_d = d.getDate();
- delete d;
- }
- };
- this.isgood = function () {
- if (thisanime.expire_y === -1) return true;
- 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));
- if (-1 < diff) return true;
- return false;
- };
- this.setexpiredays = function (amount) {
- var d = new Date();
- var e = new Date(d.getFullYear(), d.getMonth(), d.getDate() + amount);
- thisanime.expire_y = e.getFullYear();
- thisanime.expire_m = e.getMonth() + 1;
- thisanime.expire_d = e.getDate();
- };
- this.setexpireweeks = function (amount) {
- thisanime.setexpiredays(amount * 7);
- };
- this.setexpirecours = function (amount) {
- thisanime.setexpiredays(amount * 7 * 13);
- };
- this.compress = function () {
- var anime = [];
- anime[ANIME_NAME] = thisanime.name; // Name of anime
- anime[ANIME_FLAG] = thisanime.flag; // w d h
- anime[ANIME_DATE] = thisanime.date_y;
- anime[ANIME_DATE] = anime[ANIME_DATE] * 100 + thisanime.date_m;
- anime[ANIME_DATE] = anime[ANIME_DATE] * 100 + thisanime.date_d;
- if (thisanime.expire_y === -1) {
- anime[ANIME_EXPIRE] = -1;
- } else {
- anime[ANIME_EXPIRE] = thisanime.expire_y;
- anime[ANIME_EXPIRE] = anime[ANIME_EXPIRE] * 100 + thisanime.expire_m;
- anime[ANIME_EXPIRE] = anime[ANIME_EXPIRE] * 100 + thisanime.expire_d;
- }
- return anime;
- };
- this.decompress = function (anime_dataset) {
- thisanime.name = anime_dataset[ANIME_NAME]; // Name of anime_dataset
- thisanime.flag = anime_dataset[ANIME_FLAG]; // w d h
- var value = anime_dataset[ANIME_DATE];
- thisanime.date_d = Math.floor(value % 100);
- value /= 100;
- thisanime.date_m = Math.floor(value % 100);
- value /= 100;
- thisanime.date_y = Math.floor(value);
- var value = anime_dataset[ANIME_EXPIRE];
- if (value === -1) {
- thisanime.expire_y = thisanime.expire_m = thisanime.expire_d = -1;
- } else {
- thisanime.expire_d = Math.floor(value % 100);
- value /= 100;
- thisanime.expire_m = Math.floor(value % 100);
- value /= 100;
- thisanime.expire_y = Math.floor(value);
- }
- return thisanime;
- };
- this.name = ''; // Name of anime
- this.flag = -1; // w d h
- this.date_y = -1;
- this.date_m = -1;
- this.date_d = -1;
- this.expire_y = -1;
- this.expire_m = -1;
- this.expire_d = -1;
- this.Anime(anime_dataset);
- };
- // Anime List Manager
- //
- // Holds information on an anime
- //
- // Public Methods
- // - get | Get an anime entry; If not found, return the insertion index
- // - add | Add an anime by Anime struct
- // - rm | Set the expiration date ## weeks from today
- // - save | Save data to storage
- // - load | Reload data from storage
- //
- // Usage [0]: AnimeList()
- var AnimeList = function () {
- var thisanimelist = this;
- var store = new function () {
- this.anime = new Store('horc-animes', [], 'object');
- };
- store.anime.list = [];
- this.AnimeList = function () {
- thisanimelist.load();
- };
- // Add Anime
- //
- // Inserts the anime to the list & storage
- //
- // Usage [1]: add(Anime_structure)
- this.add = function (anime) {
- var find = thisanimelist.get(anime.name);
- if (typeof find === 'number') { // Not found
- store.anime.list.splice(find, 0, anime); // insert
- } else { // Found
- store.anime.list.splice(find[0], 1, anime); // replace
- }
- thisanimelist.save();
- };
- // Remove Anime
- //
- // Remove the anime from the list & storage
- //
- // Usage [1]: rm(string_name)
- this.rm = function (name) {
- var find = thisanimelist.get(name);
- if (typeof find === 'number') { // Not found
- } else { // Found
- store.anime.list.splice(find[0], 1); // replace
- }
- thisanimelist.save();
- };
- // Get Anime
- //
- // Return:
- // If found, the array [ index, Anime structure ]
- // If not found, the index at which to insert the anime
- //
- // Usage [1]: get(string_name)
- this.get = function (name) {
- return binsearch(name, store.anime.list, {
- get: function (i, container) { return container[i].name; },
- found: function (i, container) { return [i, container[i]]; },
- notfound: function (i, container) { return i; },
- });
- };
- // Save Anime List
- //
- // Save the anime list to storage
- //
- // Usage [0]: save()
- this.save = function () {
- var compressed = $(store.anime.list).map(function (i, e) {
- return [e.compress()];
- });
- store.anime.set(compressed);
- };
- // Load Anime List
- //
- // Reload the anime list from storage
- //
- // Note:
- // This will also delete expired entries from storage.
- //
- // Usage [0]: load()
- this.load = function () {
- var deletions = false;
- store.anime.list = $(store.anime.get()).map(function (i, e) {
- var anime = new Anime(e);
- if (anime.isgood()) return anime;
- deletions = true;
- });
- if (deletions) thisanimelist.save();
- };
- this.AnimeList();
- };
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- /// <reference path="Class Anime.js" />
- /// <reference path="Class Store.js" />
- /// <reference path="Class Touch.js" />
- // AnimeFilter module
- //
- // Constructor takes the string selector for slots the UI may position itself in.
- // Events are listened to & fired in $(document)
- //
- // Events this will listen for:
- // - set-active | Make the program interactable
- // - clear-active | Make the program non-interactable
- // - set-ui-droppanel | Open the MainUI position drop panel
- // - clear-ui-droppanel | Close the MainUI position drop panel
- // - set-ep-droppanel | Open the episode list drop panel
- // - clear-ep-droppanel | Close the episode list drop panel
- // - update-animelist | Update the anime list count
- //
- // Events this will trigger:
- // -
- //
- // Options:
- // -
- //
- // Note:
- // You are required to allocate at least 1 position as empty elements before constructing this.
- var AnimeFilter = function (options) {
- var thisanimefilter = this;
- var animelist = null;
- this.AnimeFilter = function () {
- document.animelist = animelist = new AnimeList();
- // Bind episodes now & add to document
- thisanimefilter.refreshfilters();
- document.refreshfilters = thisanimefilter.refreshfilters;
- firstbindtouch();
- $(document).on('update-animelist', updatelist);
- $(document).trigger('update-animelist');
- };
- function firstbindtouch() {
- // Search an element, then it's parents for a selector
- function treehas(element, selector) {
- // Check this
- var jqe = $(element).filter(selector);
- if (jqe.length) return $(element);
- // Check parents
- var jqe = $(element).parents(selector);
- if (jqe.length) return jqe;
- return null;
- }
- function typeofelement(jqe) {
- if (jqe.hasClass('horc-episode-filter')) return 'fi'; // Episode Filter
- if (jqe.hasClass('horc-episode-orig')) return 'ep'; // Episode Original
- if (jqe.hasClass('horc-circle')) return 'ep'; // Episode Original
- if (jqe.hasClass('horc-slot')) return 'ui'; // UI
- if (jqe.hasClass('horc-ui')) return 'ui'; // UI
- return '';
- }
- function shouldreject(jqe) {
- return !!treehas(jqe, 'a,button,datalist,input,keygen,output,select,textarea');
- }
- var touch = new Touch();
- var container = $('body');
- container.on('mousedown', touch.mousedown);
- container.on('mousemove', touch.mousemove);
- container.on('mouseup', touch.mouseup);
- container.on('touchstart', touch.touchstart);
- container.on('touchmove', touch.touchmove);
- container.on('touchend', touch.touchend);
- touch.onstart = function (ids, changes, e) {
- };
- touch.onend = function (ids, changes, e) {
- };
- touch.ondragstart = function (ids, changes, e) {
- // If requires control & pressing control don't match, cancel
- if (e.ctrlKey ^ document.store.requirectrl.get()) return;
- $(changes).map(function (i, changed) {
- if (id < -1) return; // ignore middle & right click
- var changed = e.originalEvent.changedTouches[i];
- var id = changed.identifier;
- if (shouldreject(changed.target)) return;
- // Check if drag exists
- var jqedrag = treehas(changed.target, '.draggable');
- if (!jqedrag) return;
- // Check type of drag & drop
- var typeofdrag = typeofelement(jqedrag);
- switch (typeofdrag) {
- case 'ep':
- var panel = $('.horc-ep-droppanel');
- if (!panel.is(':visible')) {
- panel.css('left', changed.pageX);
- panel.css('top', changed.pageY);
- }
- $(document).trigger('set-ep-droppanel')
- try {
- addepicon(id, options.getanimename(jqedrag));
- } catch (err) {
- setstatus('<br>Could\'t get the anime\'s name, please report this:<br>\n<span style="color: #800000;">' + jqedrag.text() + '</span>');
- }
- break;
- case 'fi':
- var panel = $('.horc-ep-droppanel');
- if (!panel.is(':visible')) {
- panel.css('left', changed.pageX);
- panel.css('top', changed.pageY);
- }
- $(document).trigger('set-ep-droppanel')
- addepicon(id, jqedrag.text());
- break;
- case 'ui':
- $(document).trigger('set-ui-droppanel')
- $('#horc-mainui').hide();
- adduiicon(id);
- break;
- }
- e.preventDefault();
- });
- };
- touch.ondragend = function (ids, changes, e) {
- $(changes).map(function (i, changed) {
- if (id < -1) return; // ignore middle & right click
- var changed = e.originalEvent.changedTouches[i];
- var id = changed.identifier;
- if (shouldreject(changed.target)) return;
- rmepicon(id);
- rmuiicon(id);
- // Check if drag & drop both exists
- var jqedrag = treehas(changed.target, '.draggable');
- if (!jqedrag) return;
- var jqedrop = treehas(changed.targetnow, '.droppable');
- if (!jqedrop) $('#horc-mainui').show();
- if (!jqedrop) return;
- // Check type of drag & drop
- var typeofdrag = typeofelement(jqedrag);
- var typeofdrop = typeofelement(jqedrop);
- switch (typeofdrag) {
- case 'ep':
- switch (typeofdrop) {
- case 'ep':
- try {
- filteranime(options.getanimename(jqedrag), jqedrop);
- } catch (err) {
- setstatus('<br>Could\'t get the anime\'s name, please report this:<br>\n<span style="color: #800000;">' + jqedrag.text() + '</span>');
- }
- break;
- case 'ui':
- break;
- }
- break;
- case 'fi':
- switch (typeofdrop) {
- case 'ep':
- filteranime(jqedrag.text(), jqedrop);
- break;
- case 'ui':
- break;
- }
- break;
- case 'ui':
- switch (typeofdrop) {
- case 'ep':
- break;
- case 'ui':
- $('.horc-slot').map(function (i, element) {
- if (jqedrop[0] === element) document.store.position.set(i);
- });
- $('#horc-mainui').appendTo(jqedrop.children('.horc-content')).show();
- break;
- }
- break;
- }
- e.preventDefault();
- });
- };
- touch.onmove = function (ids, changes, e) {
- $(changes).map(function (i, changed) {
- if (id < -1) return; // ignore middle & right click
- var id = changed.identifier;
- var icon = $('#horc-ep' + id + ',#horc-pos' + id);
- if (icon.length) {
- icon.css('left', changed.clientX);
- icon.css('top', changed.clientY);
- e.preventDefault();
- }
- });
- };
- touch.onfirst = function (ids, changes, e) {
- if (e.ctrlKey ^ document.store.requirectrl.get()) return;
- $('#horc-mainui,' + options.epcontainer).addClass('noselect');
- };
- touch.onlast = function (ids, changes, e) {
- $(document).trigger('clear-ep-droppanel');
- $(document).trigger('clear-ui-droppanel');
- $('#horc-mainui,' + options.epcontainer).removeClass('noselect');
- };
- }
- function updatelist() {
- $('#horc-mainui .horc-episode-filter').remove();
- var eps = $('.horc-episode-orig');
- eps.map(function (i, element) {
- var ep = $(element);
- var animename = '';
- try {
- animename = options.getanimename(ep);
- } catch (err) {
- return;
- }
- // Check which filter this episode falls under
- var flag = -1;
- if (ep.hasClass('watch')) flag = FLAG_WATCH;
- else if (ep.hasClass('drop')) flag = FLAG_DROP;
- else if (ep.hasClass('hide')) flag = FLAG_HIDE;
- if (flag == -1) return;
- // Construct the list selector
- var whichselector = '';
- switch (flag) {
- case FLAG_WATCH:
- whichselector = 'watch';
- break;
- case FLAG_DROP:
- whichselector = 'drop';
- break;
- case FLAG_HIDE:
- whichselector = 'hide';
- break;
- }
- // Populate the filter lists, since they were cleared before this map
- // Check if the list has the anime
- var list = $('#horc-' + whichselector + 'list .horc-content');
- var inlist = !!list.find('.horc-episode-filter').map(function (i, element) {
- var filter = $(element);
- var filtername = filter.text();
- if (filtername === animename) return filtername;
- }).length;
- // Add it maybe
- if (!inlist) {
- var filter = $('<div class="horc-episode-filter draggable">');
- filter.text(animename);
- list.append(filter);
- }
- });
- }
- // Rebind Episodes
- //
- // Rebinds the drag event to new episode elements
- //
- // Note:
- // You will need to call this anytime episode lists are populated dynamically.
- this.refreshfilters = function () {
- $(options.epselector).filter(':not(.horc-episode-orig)').map(function (i, element) {
- var jqe = $(element);
- jqe.addClass('horc-episode-orig draggable');
- var animename = '';
- try {
- animename = options.getanimename(jqe);
- } catch (err) {
- return;
- }
- var search = animelist.get(animename);
- if (typeof search === 'number') { // Not yet marked
- return; // Dont highlight
- }
- var flag = search[1].flag;
- var whichselector = '';
- switch (flag) {
- case FLAG_WATCH:
- whichselector = 'watch';
- break;
- case FLAG_DROP:
- whichselector = 'drop';
- break;
- case FLAG_HIDE:
- whichselector = 'hide';
- break;
- }
- jqe.removeClass('watch drop hide');
- jqe.addClass(whichselector);
- });
- $(document).trigger('update-animelist');
- };
- function filteranime(animename, jqedrop) {
- // Check which filter this episode falls under
- var flag = 0;
- if (jqedrop.filter('#clearcircle').length) flag = -1;
- else if (jqedrop.filter('#watchcircle').length) flag = FLAG_WATCH;
- else if (jqedrop.filter('#dropcircle').length) flag = FLAG_DROP;
- else if (jqedrop.filter('#hidecircle').length) flag = FLAG_HIDE;
- // Construct the list selector
- var whichselector = '';
- switch (flag) {
- case FLAG_WATCH:
- whichselector = 'watch';
- break;
- case FLAG_DROP:
- whichselector = 'drop';
- break;
- case FLAG_HIDE:
- whichselector = 'hide';
- break;
- case -1:
- break;
- }
- if (flag === -1) { // Clear
- // Remove highlight
- var eps = options.epsearch(animename);
- eps.removeClass('watch drop hide');
- // Remove from list
- animelist.rm(animename);
- $(document).trigger('update-animelist');
- return;
- }
- ///////////////////////
- // Watch/Drop/Hide //
- // Re-highlight
- var eps = options.epsearch(animename);
- eps.removeClass('watch drop hide');
- eps.addClass(whichselector);
- var search = animelist.get(animename);
- if (typeof search === 'number') { // Not yet added
- var newanime = new Anime();
- // Update
- newanime.name = animename;
- newanime.flag = flag;
- newanime.setexpirecours(2);
- // Save
- animelist.add(newanime);
- animelist.save();
- } else { // Already there; update it
- if (search[1].flag !== flag) { // update the flag
- // Update
- search[1].flag = flag;
- animelist.save();
- } // Else do nothing
- }
- $(document).trigger('update-animelist');
- }
- function addepicon(id, name) {
- rmepicon(id);
- // Drag icon
- var icon = $('<div id="horc-ep' + id + '" class="horc-episode-icon">' + name + '</div>');
- $('body').append(icon);
- }
- function adduiicon(id) {
- rmuiicon(id);
- // Drag icon
- var icon = $('<div id="horc-pos' + id + '" class="horc-posdrag-icon">');
- $('body').append(icon);
- }
- function rmepicon(id) {
- $('#horc-ep' + id).remove();
- }
- function rmuiicon(id) {
- $('#horc-pos' + id).remove();
- }
- var timer_statusbar = null;
- function setstatus(msg) {
- var statusbar = $('#horc-statusbar');
- var msgbox = statusbar.children();
- if (timer_statusbar === null) {
- msgbox.empty().append(msg);
- statusbar.show({ direction: 'down' }, 250);
- timer_statusbar = setTimeout(function () {
- statusbar.hide({ direction: 'down' }, 250);
- }, 10000);
- } else {
- clearTimeout(timer_statusbar);
- timer_statusbar = null;
- setstatus(msg);
- }
- }
- this.AnimeFilter();
- };
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- /// <reference path="Class Store.js" />
- // MainUI module
- //
- // Constructor takes the string selector for slots the UI may position itself in.
- // Events are listened to & fired in $(document)
- //
- // Events this will listen for:
- // - set-active | Make the program interactable
- // - clear-active | Make the program non-interactable
- // - set-ui-droppanel | Open the MainUI position drop panel
- // - clear-ui-droppanel | Close the MainUI position drop panel
- // - set-ep-droppanel | Open the episode list drop panel
- // - clear-ep-droppanel | Close the episode list drop panel
- // - update-animelist | Update the anime list count
- //
- // Static methods to know of:
- // - MainUI.create_embed | Creates an embedded position
- // - MainUI.create_sidebar | Creates a position for a sidebar
- //
- // Note:
- // You are required to allocate at least 1 position as empty elements before constructing this.
- // Pass the string selector to your allocated positions.
- var MainUI = function (default_css) {
- var thismainui = this;
- if (document.store == undefined) document.store = {};
- document.store.css = new Store('horc-style', default_css, 'string');
- document.store.handedness = new Store('horc-handedness', 1, 'number');
- document.store.position = new Store('horc-position', -1, 'number');
- document.store.requirectrl = new Store('horc-requirectrl', false, 'boolean');
- document.store.settings = new Store('horc-settings', false, 'boolean');
- document.store.updater = new Store('horc-updater', true, 'boolean');
- document.store.showwatch = new Store('horc-show-watch', true, 'boolean');
- document.store.showdrop = new Store('horc-show-drop', false, 'boolean');
- document.store.showhide = new Store('horc-show-hide', false, 'boolean');
- this.MainUI = function () {
- var slots = $('.horc-slot');
- if (slots.length === 0) throw 'No positions for the UI to take';
- // Load CSS
- $('<style id="horc-css">').text(document.store.css.get()).appendTo($('head'));
- create_mainui();
- create_statusbar();
- create_epdragui();
- create_events();
- };
- // Get Position Value [+1 overloads]
- //
- // Return [0]:
- // Returns the position of the current selection
- // The range is from including -1 to number_of_slots
- //
- // Return [1]:
- // Returns the position value passed in, but corrected for out of bounds
- // The range is from including -1 to number_of_slots
- //
- // Note:
- // Use if you need to calculate a new position that might be out of bounds.
- //
- // Usage [0]: getpositionvalue()
- // Usage [1]: getpositionvalue(position_value_to_test)
- function getpositionvalue(pos) {
- var len = $('.horc-slot').length;
- if (pos === undefined) pos = document.store.position.get();
- if (pos < -1) pos = -1;
- if (len <= pos) pos = len - 1;
- return pos;
- };
- // Get Currently Selected Position
- //
- // Return:
- // Returns the elements that is chosen to hold the UI
- //
- // Usage [0]: getposition()
- function getposition() {
- var slots = $('.horc-slot > .horc-content');
- var pos = getpositionvalue();
- if (pos === -1) { // Find default slot
- var defaultslot = slots.parent('.horc-default').children('.horc-content');
- if (defaultslot.length) {
- return $(defaultslot[0]);
- } else {
- return $(slots[0]);
- }
- }
- return $(slots[pos]);
- };
- // Create Main UI
- function create_mainui() {
- if (0 < $('#horc-mainui').length) throw 'Stopped UserScript from loading again'; // when UserScript loads twice, cancel load
- // Create main ui area
- var mainui = $('<div id="horc-mainui" class="horc-ui draggable">');
- getposition().append(mainui);
- // Add title & logo
- var title = $('<h1>' + HORC_NAME + '</h1>');
- var logo = $('<img>');
- title.prepend(logo);
- logo.css('width', '2em');
- logo.css('height', '2em');
- logo.css('margin-right', '0.5em');
- logo.css('vertical-align', 'middle');
- logo.on('load', function () {
- setTimeout(function () {
- title.slideUp(250, function () {
- title.remove();
- });
- }, 5000);
- });
- logo.prop('src', 'https://bitbucket.org/horc/anime-lighter/raw/master/img/Logo.png');
- mainui.append(title);
- // Updater
- var updater = $('<div>');
- updater.hide().appendTo(mainui);
- var url = $('<span>[<a href="https://openuserjs.org/scripts/RandomClown/Anime_Lighter">OpenUserJS</a>]</span>');
- function nothing(newversion) { console.log('New version available: v' + newversion); }
- function versionchecker(newversion) {
- updater.slideDown(250);
- setTimeout(function () {
- updater.slideUp(250, function () {
- updater.remove();
- });
- }, 10000);
- }
- if (greasyfork) {
- updater.text('May be out-dated. ');
- updater.append(url);
- document.versionchecker = nothing;
- versionchecker('0.0');
- } else {
- updater.text('New version available! ');
- updater.append(url);
- document.versionchecker = document.store.updater.get() ? versionchecker : nothing;
- }
- // Create individual filter lists
- var watchlist = $('<div id="horc-watchlist">');
- var droplist = $('<div id="horc-droplist">');
- var hidelist = $('<div id="horc-hidelist">');
- mainui.append(watchlist);
- mainui.append(droplist);
- mainui.append(hidelist);
- // List headers
- var watchhead = $('<h2 class="horc-listhead">Watch List</h2>');
- var drophead = $('<h2 class="horc-listhead">Drop List</h2>');
- var hidehead = $('<h2 class="horc-listhead">Hide List</h2>');
- watchlist.append(watchhead);
- droplist.append(drophead);
- hidelist.append(hidehead);
- if (!document.store.showwatch.get()) watchhead.addClass('horc-listhead-hide');
- if (!document.store.showdrop.get()) drophead.addClass('horc-listhead-hide');
- if (!document.store.showhide.get()) hidehead.addClass('horc-listhead-hide');
- var watchcontent = $('<div class="horc-content">');
- var dropcontent = $('<div class="horc-content">');
- var hidecontent = $('<div class="horc-content">');
- watchlist.append(watchcontent);
- droplist.append(dropcontent);
- hidelist.append(hidecontent);
- if (!document.store.showwatch.get()) watchcontent.hide();
- if (!document.store.showdrop.get()) dropcontent.hide();
- if (!document.store.showhide.get()) hidecontent.hide();
- watchhead.click(function () {
- if ($(this).prop('disabled')) return;
- watchcontent.slideToggle(250)
- watchhead.toggleClass('horc-listhead-hide');
- document.store.showwatch.set(!document.store.showwatch.get());
- });
- drophead.click(function () {
- if ($(this).prop('disabled')) return;
- dropcontent.slideToggle(250)
- drophead.toggleClass('horc-listhead-hide');
- document.store.showdrop.set(!document.store.showdrop.get());
- });
- hidehead.click(function () {
- if ($(this).prop('disabled')) return;
- hidecontent.slideToggle(250)
- hidehead.toggleClass('horc-listhead-hide');
- document.store.showhide.set(!document.store.showhide.get());
- });
- var watchcount = $('<span class="horc-listcounter">0</span>')
- var dropcount = $('<span class="horc-listcounter">0</span>')
- var hidecount = $('<span class="horc-listcounter">0</span>')
- watchhead.append(watchcount);
- drophead.append(dropcount);
- hidehead.append(hidecount);
- var delay = null;
- $('#horc-mainui').on('anime-update', function () {
- if (!delay) delay = setTimeout(function () {
- delay = null;
- watchcount.text(watchlist.find('.episode').length);
- dropcount.text(droplist.find('.episode').length);
- hidecount.text(hidelist.find('.episode').length);
- }, 100);
- });
- create_settings();
- }
- // Create Settings
- //
- // This will only show if the "settings" flag in localStorage is set
- function create_settings() {
- var mainui = $('#horc-mainui');
- // Create Settings button
- var settingsbutton = $('<button>Settings</button>');
- settingsbutton.click(function () {
- var settings = document.store.settings.get();
- document.store.settings.set(!settings);
- $('#horc-settings').slideToggle(200);
- });
- mainui.append(settingsbutton);
- // Create settings area
- var settingui = $('<div id="horc-settings">');
- mainui.append(settingui);
- settingui.css('text-align', 'center');
- if (!document.store.settings.get()) $('#horc-settings').hide();
- // Create ctrl mode button
- var ctrlmod = $('<input id="horc-ctrlmod" type="checkbox">')
- settingui.append(ctrlmod);
- settingui.append('Require <ctrl> modifier');
- ctrlmod.prop('checked', document.store.requirectrl.get())
- ctrlmod.on('change', function () {
- document.store.requirectrl.set($(ctrlmod).prop('checked'));
- });
- // Create CSS Update button
- var cssupdate = $('<button>Update CSS</button>');
- cssupdate.css('float', 'left');
- cssupdate.click(function () {
- var css = $('#horc-cssbox').val();
- var stringified = JSON.stringify(css);
- // update current CSS style
- document.store.css.set(css);
- $('#horc-css').text(css);
- try {
- StyleFix.styleElement($('#horc-css')[0]);
- } catch (err) {
- // Prefix Free doesnt exist
- }
- // update css dev box
- $('#stringify').val(stringified);
- });
- // Create Clear Storage button
- var clearstorage = $('<button>Clear Storage</button>');
- clearstorage.css('float', 'right');
- clearstorage.css('background-color', 'rgba(255, 0, 0, 0.4)');
- clearstorage.click(function () {
- localStorage.clear();
- window.location.reload(true);
- });
- // Create CSS box
- var cssbox = $('<textarea id="horc-cssbox">');
- cssbox.css('width', '100%');
- cssbox.css('height', '10em');
- cssbox.val(document.store.css.get());
- $(document).on('keydown', '#horc-cssbox', function (e) {
- // Detect save
- if ((e.which == '115' || e.which == '83') && (e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- cssupdate.trigger('click');
- }
- });
- // Create Stringified box
- var stringbox = $('<textarea id="stringify">');
- stringbox.attr('disabled', 'true');
- stringbox.css('width', '100%');
- stringbox.css('height', '8em');
- stringbox.val(JSON.stringify(document.store.css.get()));
- // Append CSS text editors
- settingui.append($('<br>'));
- settingui.append($('<br>'));
- settingui.append(clearstorage);
- settingui.append(cssupdate);
- settingui.append($('<div>Current CSS</div>'));
- settingui.append(cssbox);
- settingui.append($('<br>'));
- settingui.append($('<br>'));
- settingui.append($('<div>Stringified CSS</div>'));
- settingui.append(stringbox);
- }
- // Create status bar
- function create_statusbar() {
- var statusbar = $('<div id="horc-statusbar"><div></div></div>').hide();
- $('body').append(statusbar);
- }
- // Create Episode Drag & Drop UI
- function create_epdragui() {
- // Find panel
- var ep_droppanel = $('<div class="horc-ep-droppanel">').hide();
- $('body').append(ep_droppanel);
- ep_droppanel.css('left', '10em');
- ep_droppanel.css('top', '30em');
- // Create circles
- var watchcircle = $('<div id="watchcircle" class="horc-circle droppable"><div>Watch</div></div>');
- var dropcircle = $('<div id="dropcircle" class="horc-circle droppable"><div>Drop</div></div>');
- var hidecircle = $('<div id="hidecircle" class="horc-circle droppable"><div>Hide</div></div>');
- var clearcircle = $('<div id="clearcircle" class="horc-circle droppable"><div>Clear</div></div>');
- ep_droppanel.append(watchcircle);
- ep_droppanel.append(dropcircle);
- ep_droppanel.append(hidecircle);
- ep_droppanel.append(clearcircle);
- }
- // Create Events
- function create_events() {
- // Make the program interactable
- $(document).on('set-active', function (e) {
- $('#horc-mainui').find('.horc-listhead, .horc-filter-episode, button, input').prop('disabled', false);
- });
- $(document).on('clear-active', function (e) {
- $('#horc-mainui').find('.horc-listhead, .horc-filter-episode, button, input').prop('disabled', true)
- });
- // Make the main UI disappear into a small dragged window
- // Show the position drop panels
- $(document).on('set-ui-droppanel', function (e) {
- $('.horc-ui-droppanel').fadeIn(100);
- $('.horc-posdrag-icon').show();
- //$('.horc-ui-droppanel').find('.horc-circle')
- });
- $(document).on('clear-ui-droppanel', function (e) {
- $('.horc-ui-droppanel').fadeOut(100);
- $('.horc-posdrag-icon').hide();
- });
- // Make a small box displaying an episode entry
- // Show the anime drop panels
- $(document).on('set-ep-droppanel', function (e) {
- var panel = $('.horc-ep-droppanel');
- panel.fadeIn(100);
- $('.horc-episode-icon').show();
- //panel.css('left', document.prop.mousex + 'px');
- //panel.css('top', document.prop.mousey + 'px');
- });
- $(document).on('clear-ep-droppanel', function (e) {
- $('.horc-ep-droppanel').fadeOut(100);
- $('.horc-episode-icon').hide();
- });
- // Update the counters on anime lists
- $(document).on('update-animelist', function (e) {
- // Clear mainui anime list
- $('#horc-watchlist, #horc-droplist, #horc-hidelist').find('.horc-content').empty();
- // Repopulate with the episode listing
- $('.horc-episode').map(function (i, element) {
- var jqe = $(element);
- console.warn('Repopulate Incomplete');
- });
- // Count number of animes in list
- var eps = $('.horc-episode-orig');
- var watchcount = eps.filter('.watch').length;
- var dropcount = eps.filter('.drop').length;
- var hidecount = eps.filter('.hide').length;
- $('#horc-watchlist').find('.horc-listcounter').text(watchcount);
- $('#horc-droplist').find('.horc-listcounter').text(dropcount);
- $('#horc-hidelist').find('.horc-listcounter').text(hidecount);
- });
- }
- this.MainUI();
- };
- MainUI.create_embed = function (jqe, contentstyles, draguistyles) {
- jqe.empty().addClass('horc-slot droppable');
- var dragui = $('<div class="horc-ui-droppanel">').hide();
- dragui.append($('<div class="horc-circle">'));
- jqe.append(dragui);
- var content = $('<div class="horc-content horc-embed">');
- jqe.append(content);
- if (contentstyles) {
- for (var i = 0; i < contentstyles.length; i += 2) {
- content.css(contentstyles[i], contentstyles[i + 1]);;
- }
- }
- if (draguistyles) {
- for (var i = 0; i < draguistyles.length; i += 2) {
- dragui.css(draguistyles[i], draguistyles[i + 1]);
- }
- }
- }
- MainUI.create_sidebar = function (jqe, contentstyles, draguistyles) {
- jqe.empty().addClass('horc-slot droppable');
- var dragui = $('<div class="horc-ui-droppanel">').hide();
- dragui.append($('<div class="horc-circle">'));
- jqe.append(dragui);
- var content = $('<div class="horc-content horc-sidebar">');
- jqe.append(content);
- if (contentstyles) {
- for (var i = 0; i < contentstyles.length; i += 2) {
- content.css(contentstyles[i], contentstyles[i + 1]);;
- }
- }
- if (draguistyles) {
- for (var i = 0; i < draguistyles.length; i += 2) {
- dragui.css(draguistyles[i], draguistyles[i + 1]);
- }
- }
- }
- // Storage Management
- //
- // This will create a new persistant variable
- //
- // Note:
- // Constructor[3] has type checking.
- //
- // Usage [2]: new Store(keyname, value_default)
- // Usage [3]: new Store(keyname, value_default, expected_type)
- var Store = function (keyname, value_default, type) {
- var thisstore = this;
- this.Store = function (keyname, value_default, type) {
- var value = localStorage.getItem(thisstore._keyname);
- if (value === null) {
- thisstore._cache = JSON.parse(thisstore._default);
- } else {
- value = JSON.parse(value);
- if (type && typeof value !== type) {
- console.warn('Loaded data for "' + keyname + '" is not of type "' + type + '"; Using default value');
- thisstore._cache = JSON.parse(thisstore._default);
- } else {
- thisstore._cache = value;
- }
- }
- };
- this.get = function () {
- return thisstore._cache;
- };
- this.set = function (value) {
- if (type && typeof value !== type) {
- thisstore._cache = JSON.parse(thisstore._default);
- }
- thisstore._cache = value;
- localStorage.setItem(thisstore._keyname, JSON.stringify(value));
- if (thisstore.onset) thisstore.onset();
- };
- this.rm = function () {
- localStorage.removeItem(thisstore._keyname);
- };
- this._keyname = keyname;
- this._default = JSON.stringify(value_default);
- this._type = type;
- this._cache = null;
- this.onset = null;
- this.Store(keyname, value_default, type);
- };
- // Store Updater
- //
- // This will force storage to clear for a specific key.
- //
- // Note:
- // You should force refresh the page, in case your other modules loaded 1st.
- //
- // Usage [2]: new StoreUpdater(keyname, new_version)
- var StoreUpdater = function (keyname, newversion) {
- // Initialize position for the UI
- if (!document.getElementById('horc-updater')) {
- var updater = document.createElement('div');
- document.body.insertBefore(updater, document.body.childNodes[0]);
- updater.id = 'horc-updater';
- updater.style.position = 'fixed';
- updater.style.left = '0';
- updater.style.top = '0';
- updater.style.zIndex = '100';
- }
- if (document.horc_updated === undefined) document.horc_updated = false;
- var moduleversion = localStorage.getItem(keyname + '.version');
- if (moduleversion !== newversion) {
- // Site theme changed since last time; Delete key & prepare to reboot
- document.horc_updated = true;
- localStorage.removeItem(keyname);
- localStorage.setItem(keyname + '.version', newversion);
- localStorage.removeItem(keyname + '.updated');
- return;
- }
- if (null === localStorage.getItem(keyname + '.updated')) {
- // Tell the user it updated
- localStorage.setItem(keyname + '.updated', 'true');
- var container = document.createElement('div');
- container.style.color = '#000000';
- container.style.backgroundColor = '#eeffee';
- container.style.padding = '.5em 2em';
- container.style.margin = '.5em';
- container.style.marginLeft = '1em';
- container.style.width = '16em';
- container.style.borderRadius = '0.5em';
- container.style.boxShadow = '0 0 1em #000000';
- var icon = document.createElement('img');
- icon.src = 'https://bitbucket.org/horc/anime-lighter/raw/master/img/Logo.png';
- icon.style.width = '1.2em'
- icon.style.verticalAlign = 'middle'
- var msg = ' <b>' + keyname + '</b> was reset';
- var counter = document.createElement('span');
- counter.style.cssFloat = 'right';
- var desc = document.createElement('span');
- desc.innerHTML = msg;
- desc.appendChild(counter);
- container.appendChild(icon);
- container.appendChild(desc);
- document.getElementById('horc-updater').appendChild(container);
- var time = 10000;
- var _updater = setInterval(function () {
- if (time <= 0) {
- clearInterval(_updater);
- container.remove();
- } else {
- var t = (time / 100 | 0) / 10;
- if (t % 1 === 0) t = t + '.0';
- counter.innerHTML = t;
- time -= 100;
- }
- }, 100);
- container.onclick = function () {
- clearInterval(_updater);
- container.remove();
- }
- }
- }
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- // Mouse & Touch module
- //
- // Makes mouse & touch interaction consistent
- // This guarantees that for every "start" event, there is a matching "end" event
- // Assign the event handler to the correct functions of these
- //
- // If you need to test against left/middle/right click, negate the number before testing
- // Real touch events will use positive numbers: 0, 1, 2, +
- // Mouse events will use negative numbers, -1, -2, -3
- //
- // Important functions:
- // .mousedown | Event proxies that you must pass to an object when binding
- // .mouseup | ^
- // .mousemove | ^
- // .touchstart | ^
- // .touchend | ^
- // .touchmove | ^
- //
- // Important Variables:
- // .onstart | Callback for mouse or touch start event
- // .onend | Callback for matching end event
- // .ondragstart | Callback for when a drag is detected
- // .ondragend | Callback for matching end event
- // .onmove | Callback for matching move event
- // .onfirst | Callback for 1st start event
- // .onlast | Callback for last end event
- //
- // Constructor:
- // Constructor takes 3 functions, listed below
- // Make sure each of those are defined with parameters(identifiers, changedtouches, event)
- //
- // Usage[0]: new Touch()
- var Touch = function () {
- var thistouch = this;
- var touchsupport = 'ontouchstart' in window;
- this.Touch = function () {
- };
- this.mousedown = function (e) {
- if (e.which < 1) return;
- if (thistouch.ismdown) return;
- thistouch.ismdown = true;
- var isfirst = !thistouch.dragging;
- thistouch.mousetouch = new VirtualTouchEvent(e, -e.which);
- thistouch.target = e.originalEvent.target;
- recheck_mouse(e);
- $(document).on('mouseup mousemove mouseenter mouseleave', mousemove_correction);
- var changes = e.originalEvent.changedTouches;
- // Trigger first
- if (thistouch.onfirst && isfirst) thistouch.onfirst([-e.which], changes, e);
- // Trigger mouse start
- if (thistouch.onstart) thistouch.onstart([-e.which], changes, e);
- e.done = true; // Notify mouse correction handler
- };
- this.mousemove = function (e) {
- if (e.which < 1) return;
- if (!thistouch.ismdown) return;
- recheck_mouse(e);
- var changes = e.originalEvent.changedTouches;
- // Trigger dragging
- if (!isdragging(-e.which)) {
- setdragging(-e.which);
- if (thistouch.ondragstart) thistouch.ondragstart([-e.which], changes, e);
- }
- // Trigger moving
- if (thistouch.onmove) thistouch.onmove([-e.which], changes, e);
- e.done = true; // Notify mouse correction handler
- };
- this.mouseup = function (e) {
- if (e.which < 1) return;
- if (!thistouch.ismdown) return;
- thistouch.ismdown = false;
- e.originalEvent.target = thistouch.target;
- recheck_mouse(e);
- var changes = e.originalEvent.changedTouches;
- // Trigger dragging
- if (isdragging(-e.which)) {
- cleardragging(-e.which);
- if (thistouch.ondragend) thistouch.ondragend([-e.which], e.originalEvent.changedTouches, e);
- }
- // Trigger mouse end
- if (thistouch.onend) thistouch.onend([-e.which], changes, e);
- // Trigger last
- if (thistouch.onlast) thistouch.onlast([-e.which], changes, e);
- thistouch.mousetouch = null; // Delete last mouse
- e.done = true; // Notify mouse correction handler
- };
- this.touchstart = function (e) {
- normalize(e);
- var isfirst = !thistouch.istdown;
- thistouch.istdown = true;
- // For all changes
- var changes = e.originalEvent.changedTouches;
- var ids = $(changes).map(function (i, changed) { return changed.identifier; });
- // Trigger first
- if (isfirst && thistouch.onfirst) thistouch.onfirst(ids, changes, e);
- // Trigger touch start
- if (thistouch.onstart) thistouch.onstart(ids, changes, e);
- e.done = true; // Notify mouse correction handler
- };
- this.touchmove = function (e) {
- try { // Unknown error possible where the user taps once extremely fast [takes a lot of tries to replicate]
- normalize(e);
- } catch (err) {
- return; // Just skip this move event
- }
- // For all changes
- var changes = e.originalEvent.changedTouches;
- var ids = $(changes).map(function (i, changed) { return changed.identifier; });
- var changes_start = [];
- var ids_start = [];
- $(changes).map(function (i, changed) {
- var id = changed.identifier;
- if (!isdragging(id)) {
- setdragging(id);
- changes_start.push(changed);
- ids_start.push(id);
- }
- });
- // Trigger dragging
- if (ids_start.length && thistouch.ondragstart) thistouch.ondragstart(ids_start, changes_start, e);
- // Trigger moving
- if (thistouch.onmove) thistouch.onmove(ids, changes, e);
- e.done = true; // Notify mouse correction handler
- };
- this.touchend = function (e) {
- try { // Unknown error possible where the user taps once extremely fast [takes a lot of tries to replicate]
- normalize(e);
- } catch (err) {
- 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
- console.warn('Attempting to recover from error')
- }
- // For all changes
- var changes = e.originalEvent.changedTouches;
- var ids = $(changes).map(function (i, changed) { return changed.identifier; });
- var changes_end = [];
- var ids_end = [];
- $(changes).map(function (i, changed) {
- var id = changed.identifier;
- if (isdragging(id)) {
- cleardragging(id);
- changes_end.push(changed);
- ids_end.push(id);
- }
- });
- // Trigger dragging
- if (ids_end.length && thistouch.ondragend) thistouch.ondragend(ids_end, changes_end, e);
- // Trigger touch end
- if (thistouch.onend) thistouch.onend(ids, changes, e);
- // Trigger last
- if (!e.originalEvent.touches.length) thistouch.istdown = false;
- var islast = !thistouch.istdown;
- if (thistouch.onlast && islast) thistouch.onlast(ids, changes, e);
- e.done = true; // Notify mouse correction handler
- };
- // Unified handler for all
- this.onstart = null;
- this.onend = null;
- this.ondragstart = null;
- this.ondragend = null;
- this.onmove = null;
- this.onfirst = null;
- this.onlast = null;
- // Check Dragging State
- //
- // Used to keep track of whats being dragged
- //
- // Usage [1]: setdragging(id)
- function isdragging(id) {
- var shift = (0x1 << (id + 3));
- return !!(thistouch.dragging & shift);
- }
- // Set Dragging State
- //
- // Used to keep track of whats being dragged
- //
- // Usage [1]: setdragging(id)
- // Usage [2]: setdragging(ids)
- function setdragging(ids) {
- if (ids.length === undefined) {
- var shift = (0x1 << (ids + 3));
- thistouch.dragging |= shift;
- } else {
- $(ids).map(function (i, id) { setdragging(id); });
- }
- }
- // Clear Dragging State
- //
- // Used to keep track of whats being dragged
- //
- // Usage [1]: cleardragging(id)
- // Usage [2]: cleardragging(ids)
- function cleardragging(ids) {
- if (ids.length === undefined) {
- var shift = (0x1 << (ids + 3));
- thistouch.dragging &= ~shift;
- } else {
- $(ids).map(function (i, id) { cleardragging(id); });
- }
- }
- // Normalize
- //
- // Combines .which, .client, .changedTouches, .touches, & .targetnow
- //
- // Usage [1]: normalize(event)
- function normalize(e) {
- if (e.done) return;
- if (e.originalEvent.changedTouches === undefined) e.originalEvent.changedTouches = [];
- if (e.originalEvent.touches === undefined) e.originalEvent.touches = [];
- for (var i = 0; i < e.originalEvent.changedTouches.length; i++) {
- var changed = e.originalEvent.changedTouches[i];
- changed.target = thistouch.target;
- changed.targetnow = gettargetnow(e);
- }
- var changed = e.originalEvent.changedTouches;
- if (e.which && changed.length) {
- delete changed.target; // a bug requires original target to be deleted before reassignment
- changed.target = thistouch.target;
- }
- var changes = e.originalEvent.changedTouches;
- if (thistouch.mousetouch) changes.push(thistouch.mousetouch);
- var touches = e.originalEvent.touches;
- if (thistouch.mousetouch) touches.push(thistouch.mousetouch);
- }
- // Recheck Mouse
- //
- // For mouse events, this ensures consistency between mouse & touch.
- //
- // Note:
- // Only for mouse events; Don't call this from a touch event.
- //
- // Usage [1]: recheck_mouse(event)
- function recheck_mouse(e) {
- normalize(e);
- if (thistouch.mousetouch) {
- thistouch.mousetouch.copyfrom(e);
- }
- if (e.which !== thistouch.lastmouse) {
- if (thistouch.lastmouse) {
- var nowwhich = e.which;
- e.which = thistouch.lastmouse;
- thistouch.mouseup(e);
- e.which = nowwhich;
- }
- thistouch.lastmouse = e.which;
- }
- }
- // Mouse Move Correction
- //
- // Bound when a mousedown happens
- // This ensures that a mouse up will happen
- //
- // Note:
- // Only for mouse events; Don't call this from a touch event.
- //
- // Usage [1]: recheck_mouse(event)
- function mousemove_correction(e) {
- if (e.done) return;
- recheck_mouse(e);
- if (!thistouch.mousetouch) {
- $(document).off('mouseup mousemove mouseenter mouseleave', mousemove_correction);
- thistouch.lastmouse = 0;
- thistouch.mouseup(e);
- } else {
- thistouch.mousemove(e);
- }
- }
- // Get Cursor Position
- //
- // Note:
- // Requires at least 1 touch or mouse point to exist.
- //
- // Usage [1]: getcursor(event)
- function getcursor(e) {
- var changed = e.originalEvent.changedTouches;
- if (changed && 0 < changed.length) {
- var touch = e.originalEvent.changedTouches[0];
- return [touch.clientX, touch.clientY];
- } else if (e.clientX !== undefined) {
- return [e.clientX, e.clientY];
- }
- return null;
- }
- // Get Target Now
- //
- // Note:
- // Requires at least 1 touch or mouse point to exist.
- //
- // Usage [1]: gettargetnow(event)
- function gettargetnow(e) {
- var pos = getcursor(e);
- if (pos) return document.elementFromPoint(pos[0], pos[1]);
- return null;
- }
- // Virtual Touch Event
- //
- // Note:
- // Constructor Takes the mouse event & which mouse.
- // It has a copyfrom() method to copy new XY values.
- // copyfrom() will not override the id or element target.
- var VirtualTouchEvent = function (e, id) {
- this.clientX = e.originalEvent.clientX;
- this.clientY = e.originalEvent.clientY;
- this.force = 0; // Pressure
- this.identifier = id; // ID of this touch event
- this.pageX = e.originalEvent.pageX;
- this.pageY = e.originalEvent.pageY;
- this.radiusX = 25; // Default to 25 for mouse
- this.radiusY = 25; // ^
- this.screenX = e.originalEvent.screenX;
- this.screenY = e.originalEvent.screenY;
- this.target = e.originalEvent.target; // Element that triggered the event
- this.targetnow = gettargetnow(e); // Element the mouse is focused on
- this.copyfrom = function (e) {
- this.clientX = e.originalEvent.clientX;
- this.clientY = e.originalEvent.clientY;
- this.pageX = e.originalEvent.pageX;
- this.pageY = e.originalEvent.pageY;
- this.screenX = e.originalEvent.screenX;
- this.screenY = e.originalEvent.screenY;
- this.targetnow = gettargetnow(e); // Element the mouse is focused on
- };
- }
- this.mousetouch = null;
- this.dragging = 0x0; // dragging flag
- this.target = null;
- this.lastmouse = 0;
- this.ismdown = false;
- this.istdown = false;
- this.Touch();
- };
- /// Dependencies:
- /// <reference path="http://code.jquery.com/jquery-2.1.3.js" />
- /// <reference path="https://code.jquery.com/ui/1.11.4/jquery-ui.js" />
- // Get URL Search +1 overloads
- //
- // This will read a URL for the search query
- //
- // Examples of accepted URLs:
- // http://horc.bitbucket.org/
- // http://horc.bitbucket.org?
- // http://horc.bitbucket.org/?path=projects
- // ?path=projects/test/
- // path=projects/test&
- //
- // Return:
- // Object with the key & string values
- //
- // Usage [0]: geturlsearch()
- // Usage [1]: geturlsearch(string_url)
- function geturlsearch(url) {
- if (typeof url !== 'string') {
- if (url === undefined) url = location.search;
- }
- function cleanurlsearch(url) {
- console.warn('\t' + url);
- if (-1 < url.search(/:\/\//)) { // This is a url
- var p = url.search(/\?/);
- if (p === -1) url = '';
- else url = url.substr(p + 1);
- }
- if (url.substr(0, 1) === '?') url = url.substr(1);
- return url;
- }
- url = cleanurlsearch(url);
- var params = {};
- var split = url.split('&');
- for (var i = 0; i < split.length; i++) {
- var u = split[i];
- if (u.length) {
- var p = u.search('=');
- if (-1 < p) {
- var lhs = u.substr(0, p);
- var rhs = u.substr(p + 1);
- if (!lhs.length) continue;
- params[lhs] = rhs;
- } else {
- var lhs = u;
- params[lhs] = '';
- }
- }
- }
- return params;
- }
- // Load Script +3 overloads
- //
- // This will sequentially load 1 script at a time.
- // Run multiple times to load in parallel
- //
- // Usage [1]: loadscript(string_url)
- // Usage [2]: loadscript(string_url, oncomplete_function)
- // Usage [1]: loadscript(array_of_urls)
- // Usage [2]: loadscript(array_of_urls, oncomplete_function)
- function loadscript(urls, oncomplete) {
- if (typeof urls === 'object') {
- if (!urls.length) {
- if (oncomplete) return oncomplete();
- return;
- }
- loadscript(urls[0], function () {
- loadscript(urls.slice(1), oncomplete);
- });
- } else if (typeof urls === 'string') {
- var url = urls;
- var scripts = document.getElementsByTagName('script');
- for (var i = 0; i < scripts.length; ++i) {
- if (scripts[i].src === url) {
- console.log(' Script already loaded:\n' + url);
- if (oncomplete) oncomplete();
- return;
- }
- }
- var e = document.createElement('script');
- e.src = url;
- e.onload = function () {
- console.log(' Injected script:\n' + url);
- if (oncomplete) oncomplete();
- }
- e.onerror = function () {
- e.remove();
- console.warn(' Failed to injected script:\n' + url);
- if (oncomplete) oncomplete();
- }
- document.head.appendChild(e);
- } else {
- }
- }
- // Load Style +3 overloads
- //
- // This will sequentially load 1 style at a time.
- // Run multiple times to load in parallel
- //
- // Usage [1]: loadstyle(string_url)
- // Usage [2]: loadstyle(string_url, oncomplete_function)
- // Usage [1]: loadstyle(array_of_urls)
- // Usage [2]: loadstyle(array_of_urls, oncomplete_function)
- function loadstyle(urls, oncomplete) {
- if (typeof urls === 'object') {
- if (!urls.length) {
- if (oncomplete) return oncomplete();
- return;
- }
- loadstyle(urls[0], function () {
- loadstyle(urls.slice(1), oncomplete);
- });
- } else if (typeof urls === 'string') {
- var url = urls;
- var links = document.getElementsByTagName('link');
- for (var i = 0; i < links.length; ++i) {
- if (links[i].href === url) {
- console.log(' Style already loaded:\n' + url);
- if (oncomplete) oncomplete();
- return;
- }
- }
- var styles = document.getElementsByTagName('style');
- for (var i = 0; i < styles.length; ++i) {
- if (styles[i].attributes && (url === styles[i].attributes.src.value)) {
- console.log(' Style already loaded:\n' + url);
- if (oncomplete) oncomplete();
- return;
- }
- }
- var e = document.createElement('link');
- e.rel = 'stylesheet';
- e.type = 'text/css';
- e.href = url;
- e.onload = function () {
- console.log(' Injected style:\n' + url);
- if (oncomplete) oncomplete();
- }
- e.onerror = function () {
- e.remove();
- console.warn(' Failed to injected style:\n' + url);
- if (oncomplete) oncomplete();
- }
- document.head.appendChild(e);
- } else {
- }
- }
- function recheck_prefixfree() {
- var sleeptime = 200;
- var maxtime = 10000;
- var addtime = Date.now();
- function helper() {
- var realtimemax = maxtime + addtime;
- if (realtimemax < Date.now()) return;
- var time = Date.now();
- setTimeout(function () {
- try {
- StyleFix.link; // test for Prefix Free
- var links = document.getElementsByTagName('link');
- if (links.length) {
- addtime = Date.now();
- for (var i = 0; i < links.length; ++i) {
- StyleFix.link(links[i]);
- }
- }
- } catch (err) {
- }
- helper();
- }, sleeptime);
- }
- helper();
- }
- function benchmark(f) {
- s = Date.now();
- for (var i = 0; i < 1000000; ++i) f();
- s = Date.now() - s;
- return s / 1000;
- }