Drastically simplify setup for bundling js files

This commit is contained in:
Timothy Warren 2021-04-23 18:58:51 -04:00
parent 7839cf1515
commit 4c85c22c30
19 changed files with 774 additions and 2690 deletions

View File

@ -11,12 +11,6 @@
</div> </div>
</section> </section>
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script> <script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
<?php if ($auth->isAuthenticated()): ?> <script async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
<script nomodule='nomodule' async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
<script type="module" src="<?= $urlGenerator->assetUrl('es/scripts.js') ?>"></script>
<?php else: ?>
<script nomodule="nomodule" async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/anon.min.js') ?>"></script>
<script type="module" src="<?= $urlGenerator->assetUrl('es/anon.js') ?>"></script>
<?php endif ?>
</body> </body>
</html> </html>

View File

@ -1,68 +0,0 @@
import compiler from '@ampproject/rollup-plugin-closure-compiler';
const plugins = [
compiler({
assumeFunctionWrapper: true,
compilationLevel: 'WHITESPACE_ONLY', //'ADVANCED',
createSourceMap: true,
env: 'BROWSER',
languageIn: 'ECMASCRIPT_2018',
languageOut: 'ES3'
})
];
const defaultOutput = {
format: 'iife',
sourcemap: true,
}
const nonModules = [{
input: './js/anon.js',
output: {
...defaultOutput,
file: '../public/js/anon.min.js',
sourcemapFile: '../public/js/anon.min.js.map',
},
plugins,
}, {
input: './js/index.js',
output: {
...defaultOutput,
file: '../public/js/scripts.min.js',
sourcemapFile: '../public/js/scripts.min.js.map',
},
plugins,
}, {
input: './js/base/sort-tables.js',
output: {
...defaultOutput,
file: '../public/js/tables.min.js',
sourcemapFile: '../public/js/tables.min.js.map',
},
plugins,
}];
const moduleOutput = {
format: 'es',
sourcemap: false,
}
let modules = [{
input: './js/anon.js',
output: {
...moduleOutput,
file: '../public/es/anon.js',
},
}, {
input: './js/index.js',
output: {
...moduleOutput,
file: '../public/es/scripts.js',
},
}];
// Return the config array for rollup
export default [
...nonModules,
...modules,
];

View File

@ -9,7 +9,7 @@ const matches = (elm, selector) => {
return i > -1; return i > -1;
} }
export const AnimeClient = { const AnimeClient = {
/** /**
* Placeholder function * Placeholder function
*/ */
@ -18,8 +18,8 @@ export const AnimeClient = {
* DOM selector * DOM selector
* *
* @param {string} selector - The dom selector string * @param {string} selector - The dom selector string
* @param {object} [context] * @param {Element} [context]
* @return {[HTMLElement]} - array of dom elements * @return array of dom elements
*/ */
$(selector, context = null) { $(selector, context = null) {
if (typeof selector !== 'string') { if (typeof selector !== 'string') {
@ -60,7 +60,7 @@ export const AnimeClient = {
/** /**
* Hide the selected element * Hide the selected element
* *
* @param {string|Element} sel - the selector of the element to hide * @param {string|Element|Element[]} sel - the selector of the element to hide
* @return {void} * @return {void}
*/ */
hide (sel) { hide (sel) {
@ -77,7 +77,7 @@ export const AnimeClient = {
/** /**
* UnHide the selected element * UnHide the selected element
* *
* @param {string|Element} sel - the selector of the element to hide * @param {string|Element|Element[]} sel - the selector of the element to hide
* @return {void} * @return {void}
*/ */
show (sel) { show (sel) {
@ -116,9 +116,9 @@ export const AnimeClient = {
/** /**
* Finds the closest parent element matching the passed selector * Finds the closest parent element matching the passed selector
* *
* @param {HTMLElement} current - the current HTMLElement * @param {Element} current - the current Element
* @param {string} parentSelector - selector for the parent element * @param {string} parentSelector - selector for the parent element
* @return {HTMLElement|null} - the parent element * @return {Element|null} - the parent element
*/ */
closestParent (current, parentSelector) { closestParent (current, parentSelector) {
if (Element.prototype.closest !== undefined) { if (Element.prototype.closest !== undefined) {
@ -204,9 +204,9 @@ function delegateEvent(sel, target, event, listener) {
/** /**
* Add an event listener * Add an event listener
* *
* @param {string|HTMLElement} sel - the parent selector to bind to * @param {string|Element} sel - the parent selector to bind to
* @param {string} event - event name(s) to bind * @param {string} event - event name(s) to bind
* @param {string|HTMLElement|function} target - the element to directly bind the event to * @param {string|Element|function} target - the element to directly bind the event to
* @param {function} [listener] - event listener callback * @param {function} [listener] - event listener callback
* @return {void} * @return {void}
*/ */

View File

@ -1,227 +0,0 @@
/*
* classList.js: Cross-browser full element.classList implementation.
* 2014-07-23
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
/*global self, document, DOMException */
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
if ("document" in self) {
// Full polyfill for browsers with no classList support
if (!("classList" in document.createElement("_"))) {
(function(view) {
"use strict";
if (!('Element' in view)) return;
var
classListProp = "classList",
protoProp = "prototype",
elemCtrProto = view.Element[protoProp],
objCtr = Object,
strTrim = String[protoProp].trim || function() {
return this.replace(/^\s+|\s+$/g, "");
},
arrIndexOf = Array[protoProp].indexOf || function(item) {
var
i = 0,
len = this.length;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
,
DOMEx = function(type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
},
checkTokenAndGetIndex = function(classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR", "An invalid or illegal string was specified"
);
}
if (/\s/.test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR", "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
},
ClassList = function(elem) {
var
trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
i = 0,
len = classes.length;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function() {
elem.setAttribute("class", this.toString());
};
},
classListProto = ClassList[protoProp] = [],
classListGetter = function() {
return new ClassList(this);
};
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function(i) {
return this[i] || null;
};
classListProto.contains = function(token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function() {
var
tokens = arguments,
i = 0,
l = tokens.length,
token, updated = false;
do {
token = tokens[i] + "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
updated = true;
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.remove = function() {
var
tokens = arguments,
i = 0,
l = tokens.length,
token, updated = false,
index;
do {
token = tokens[i] + "";
index = checkTokenAndGetIndex(this, token);
while (index !== -1) {
this.splice(index, 1);
updated = true;
index = checkTokenAndGetIndex(this, token);
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.toggle = function(token, force) {
token += "";
var
result = this.contains(token),
method = result ?
force !== true && "remove" :
force !== false && "add";
if (method) {
this[method](token);
}
if (force === true || force === false) {
return force;
} else {
return !result;
}
};
classListProto.toString = function() {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter,
enumerable: true,
configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
if (ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}(self));
} else {
// There is full or partial native classList support, so just check if we need
// to normalize the add/remove and toggle APIs.
(function() {
"use strict";
var testElement = document.createElement("_");
testElement.classList.add("c1", "c2");
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
// classList.remove exist but support only one argument at a time.
if (!testElement.classList.contains("c2")) {
var createMethod = function(method) {
var original = DOMTokenList.prototype[method];
DOMTokenList.prototype[method] = function(token) {
var i, len = arguments.length;
for (i = 0; i < len; i++) {
token = arguments[i];
original.call(this, token);
}
};
};
createMethod('add');
createMethod('remove');
}
testElement.classList.toggle("c3", false);
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if (testElement.classList.contains("c3")) {
var _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function(token, force) {
if (1 in arguments && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
testElement = null;
}());
}
}

View File

@ -16,7 +16,7 @@ _.on('.media-filter', 'input', filterMedia);
/** /**
* Hide the html element attached to the event * Hide the html element attached to the event
* *
* @param event * @param {MouseEvent} event
* @return void * @return void
*/ */
function hide (event) { function hide (event) {
@ -26,7 +26,7 @@ function hide (event) {
/** /**
* Confirm deletion of an item * Confirm deletion of an item
* *
* @param event * @param {MouseEvent} event
* @return void * @return void
*/ */
function confirmDelete (event) { function confirmDelete (event) {
@ -52,7 +52,7 @@ function clearAPICache () {
/** /**
* Scroll to the accordion/vertical tab section just opened * Scroll to the accordion/vertical tab section just opened
* *
* @param event * @param {InputEvent} event
* @return void * @return void
*/ */
function scrollToSection (event) { function scrollToSection (event) {
@ -70,7 +70,7 @@ function scrollToSection (event) {
/** /**
* Filter an anime or manga list * Filter an anime or manga list
* *
* @param event * @param {InputEvent} event
* @return void * @return void
*/ */
function filterMedia (event) { function filterMedia (event) {

View File

@ -1,5 +1,5 @@
import './anon.js'; import './sw.js';
import './events.js';
import './session-check.js'; import './session-check.js';
import './anime.js'; import './anime.js';
import './manga.js'; import './manga.js';

View File

@ -1,9 +1,8 @@
import _ from './anime-client.js'; import _ from './anime-client.js';
(() => { (() => {
// Var is intentional let hidden = null;
var hidden = null; let visibilityChange = null;
var visibilityChange = null;
if (typeof document.hidden !== "undefined") { if (typeof document.hidden !== "undefined") {
hidden = "hidden"; hidden = "hidden";

View File

@ -1,10 +1,8 @@
import './events.js'; // Start the service worker, if you can
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => { navigator.serviceWorker.register('/sw.js').then(reg => {
console.log('Service worker registered', reg.scope); console.log('Service worker registered', reg.scope);
}).catch(error => { }).catch(error => {
console.error('Failed to register service worker', error); console.error('Failed to register service worker', error);
}); });
} }

View File

@ -3,19 +3,19 @@
"scripts": { "scripts": {
"build": "npm run build:css && npm run build:js", "build": "npm run build:css && npm run build:js",
"build:css": "node ./css.js", "build:css": "node ./css.js",
"build:js": "rollup -c ./build-js.js", "build:js": "spack",
"watch:css": "watch 'npm run build:css' --filter=./cssfilter.js", "watch:css": "watch 'npm run build:css' --filter=./cssfilter.js",
"watch:js": "watch 'npm run build:js' ./js", "watch:js": "watch 'npm run build:js' ./js",
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others" "watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
}, },
"devDependencies": { "devDependencies": {
"@ampproject/rollup-plugin-closure-compiler": "^0.26.0", "@swc/cli": "^0.1.39",
"concurrently": "^5.1.0", "@swc/core": "^1.2.54",
"cssnano": "^4.1.10", "concurrently": "^6.0.2",
"cssnano": "^5.0.1",
"postcss": "^8.2.6", "postcss": "^8.2.6",
"postcss-import": "^14.0.0", "postcss-import": "^14.0.0",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"rollup": "^2.4.0",
"watch": "^1.0.2" "watch": "^1.0.2"
} }
} }

View File

@ -0,0 +1,19 @@
module.exports = {
entry: {
'scripts.min': __dirname + '/js/index.js',
'tables.min': __dirname + '/js/base/sort-tables.js',
},
output: {
path: '../public/js',
},
options: {
jsc: {
target: 'es3',
loose: true,
},
minify: true,
module: {
type: 'es6'
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,463 +0,0 @@
// -------------------------------------------------------------------------
// ! Base
// -------------------------------------------------------------------------
const matches = (elm, selector) => {
let m = (elm.document || elm.ownerDocument).querySelectorAll(selector);
let i = matches.length;
while (--i >= 0 && m.item(i) !== elm) {} return i > -1;
};
const AnimeClient = {
/**
* Placeholder function
*/
noop: () => {},
/**
* DOM selector
*
* @param {string} selector - The dom selector string
* @param {object} [context]
* @return {[HTMLElement]} - array of dom elements
*/
$(selector, context = null) {
if (typeof selector !== 'string') {
return selector;
}
context = (context !== null && context.nodeType === 1)
? context
: document;
let elements = [];
if (selector.match(/^#([\w]+$)/)) {
elements.push(document.getElementById(selector.split('#')[1]));
} else {
elements = [].slice.apply(context.querySelectorAll(selector));
}
return elements;
},
/**
* Does the selector exist on the current page?
*
* @param {string} selector
* @returns {boolean}
*/
hasElement (selector) {
return AnimeClient.$(selector).length > 0;
},
/**
* Scroll to the top of the Page
*
* @return {void}
*/
scrollToTop () {
const el = AnimeClient.$('header')[0];
el.scrollIntoView(true);
},
/**
* Hide the selected element
*
* @param {string|Element} sel - the selector of the element to hide
* @return {void}
*/
hide (sel) {
if (typeof sel === 'string') {
sel = AnimeClient.$(sel);
}
if (Array.isArray(sel)) {
sel.forEach(el => el.setAttribute('hidden', 'hidden'));
} else {
sel.setAttribute('hidden', 'hidden');
}
},
/**
* UnHide the selected element
*
* @param {string|Element} sel - the selector of the element to hide
* @return {void}
*/
show (sel) {
if (typeof sel === 'string') {
sel = AnimeClient.$(sel);
}
if (Array.isArray(sel)) {
sel.forEach(el => el.removeAttribute('hidden'));
} else {
sel.removeAttribute('hidden');
}
},
/**
* Display a message box
*
* @param {string} type - message type: info, error, success
* @param {string} message - the message itself
* @return {void}
*/
showMessage (type, message) {
let template =
`<div class='message ${type}'>
<span class='icon'></span>
${message}
<span class='close'></span>
</div>`;
let sel = AnimeClient.$('.message');
if (sel[0] !== undefined) {
sel[0].remove();
}
AnimeClient.$('header')[0].insertAdjacentHTML('beforeend', template);
},
/**
* Finds the closest parent element matching the passed selector
*
* @param {HTMLElement} current - the current HTMLElement
* @param {string} parentSelector - selector for the parent element
* @return {HTMLElement|null} - the parent element
*/
closestParent (current, parentSelector) {
if (Element.prototype.closest !== undefined) {
return current.closest(parentSelector);
}
while (current !== document.documentElement) {
if (matches(current, parentSelector)) {
return current;
}
current = current.parentElement;
}
return null;
},
/**
* Generate a full url from a relative path
*
* @param {string} path - url path
* @return {string} - full url
*/
url (path) {
let uri = `//${document.location.host}`;
uri += (path.charAt(0) === '/') ? path : `/${path}`;
return uri;
},
/**
* Throttle execution of a function
*
* @see https://remysharp.com/2010/07/21/throttling-function-calls
* @see https://jsfiddle.net/jonathansampson/m7G64/
* @param {Number} interval - the minimum throttle time in ms
* @param {Function} fn - the function to throttle
* @param {Object} [scope] - the 'this' object for the function
* @return {Function}
*/
throttle (interval, fn, scope) {
let wait = false;
return function (...args) {
const context = scope || this;
if ( ! wait) {
fn.apply(context, args);
wait = true;
setTimeout(function() {
wait = false;
}, interval);
}
};
},
};
// -------------------------------------------------------------------------
// ! Events
// -------------------------------------------------------------------------
function addEvent(sel, event, listener) {
// Recurse!
if (! event.match(/^([\w\-]+)$/)) {
event.split(' ').forEach((evt) => {
addEvent(sel, evt, listener);
});
}
sel.addEventListener(event, listener, false);
}
function delegateEvent(sel, target, event, listener) {
// Attach the listener to the parent
addEvent(sel, event, (e) => {
// Get live version of the target selector
AnimeClient.$(target, sel).forEach((element) => {
if(e.target == element) {
listener.call(element, e);
e.stopPropagation();
}
});
});
}
/**
* Add an event listener
*
* @param {string|HTMLElement} sel - the parent selector to bind to
* @param {string} event - event name(s) to bind
* @param {string|HTMLElement|function} target - the element to directly bind the event to
* @param {function} [listener] - event listener callback
* @return {void}
*/
AnimeClient.on = (sel, event, target, listener) => {
if (listener === undefined) {
listener = target;
AnimeClient.$(sel).forEach((el) => {
addEvent(el, event, listener);
});
} else {
AnimeClient.$(sel).forEach((el) => {
delegateEvent(el, target, event, listener);
});
}
};
// -------------------------------------------------------------------------
// ! Ajax
// -------------------------------------------------------------------------
/**
* Url encoding for non-get requests
*
* @param data
* @returns {string}
* @private
*/
function ajaxSerialize(data) {
let pairs = [];
Object.keys(data).forEach((name) => {
let value = data[name].toString();
name = encodeURIComponent(name);
value = encodeURIComponent(value);
pairs.push(`${name}=${value}`);
});
return pairs.join('&');
}
/**
* Make an ajax request
*
* Config:{
* data: // data to send with the request
* type: // http verb of the request, defaults to GET
* success: // success callback
* error: // error callback
* }
*
* @param {string} url - the url to request
* @param {Object} config - the configuration object
* @return {XMLHttpRequest}
*/
AnimeClient.ajax = (url, config) => {
// Set some sane defaults
const defaultConfig = {
data: {},
type: 'GET',
dataType: '',
success: AnimeClient.noop,
mimeType: 'application/x-www-form-urlencoded',
error: AnimeClient.noop
};
config = {
...defaultConfig,
...config,
};
let request = new XMLHttpRequest();
let method = String(config.type).toUpperCase();
if (method === 'GET') {
url += (url.match(/\?/))
? ajaxSerialize(config.data)
: `?${ajaxSerialize(config.data)}`;
}
request.open(method, url);
request.onreadystatechange = () => {
if (request.readyState === 4) {
let responseText = '';
if (request.responseType === 'json') {
responseText = JSON.parse(request.responseText);
} else {
responseText = request.responseText;
}
if (request.status > 299) {
config.error.call(null, request.status, responseText, request.response);
} else {
config.success.call(null, responseText, request.status);
}
}
};
if (config.dataType === 'json') {
config.data = JSON.stringify(config.data);
config.mimeType = 'application/json';
} else {
config.data = ajaxSerialize(config.data);
}
request.setRequestHeader('Content-Type', config.mimeType);
if (method === 'GET') {
request.send(null);
} else {
request.send(config.data);
}
return request
};
/**
* Do a get request
*
* @param {string} url
* @param {object|function} data
* @param {function} [callback]
* @return {XMLHttpRequest}
*/
AnimeClient.get = (url, data, callback = null) => {
if (callback === null) {
callback = data;
data = {};
}
return AnimeClient.ajax(url, {
data,
success: callback
});
};
// ----------------------------------------------------------------------------
// Event subscriptions
// ----------------------------------------------------------------------------
AnimeClient.on('header', 'click', '.message', hide);
AnimeClient.on('form.js-delete', 'submit', confirmDelete);
AnimeClient.on('.js-clear-cache', 'click', clearAPICache);
AnimeClient.on('.vertical-tabs input', 'change', scrollToSection);
AnimeClient.on('.media-filter', 'input', filterMedia);
// ----------------------------------------------------------------------------
// Handler functions
// ----------------------------------------------------------------------------
/**
* Hide the html element attached to the event
*
* @param event
* @return void
*/
function hide (event) {
AnimeClient.hide(event.target);
}
/**
* Confirm deletion of an item
*
* @param event
* @return void
*/
function confirmDelete (event) {
const proceed = confirm('Are you ABSOLUTELY SURE you want to delete this item?');
if (proceed === false) {
event.preventDefault();
event.stopPropagation();
}
}
/**
* Clear the API cache, and show a message if the cache is cleared
*
* @return void
*/
function clearAPICache () {
AnimeClient.get('/cache_purge', () => {
AnimeClient.showMessage('success', 'Successfully purged api cache');
});
}
/**
* Scroll to the accordion/vertical tab section just opened
*
* @param event
* @return void
*/
function scrollToSection (event) {
const el = event.currentTarget.parentElement;
const rect = el.getBoundingClientRect();
const top = rect.top + window.pageYOffset;
window.scrollTo({
top,
behavior: 'smooth',
});
}
/**
* Filter an anime or manga list
*
* @param event
* @return void
*/
function filterMedia (event) {
const rawFilter = event.target.value;
const filter = new RegExp(rawFilter, 'i');
// console.log('Filtering items by: ', filter);
if (rawFilter !== '') {
// Filter the cover view
AnimeClient.$('article.media').forEach(article => {
const titleLink = AnimeClient.$('.name a', article)[0];
const title = String(titleLink.textContent).trim();
if ( ! filter.test(title)) {
AnimeClient.hide(article);
} else {
AnimeClient.show(article);
}
});
// Filter the list view
AnimeClient.$('table.media-wrap tbody tr').forEach(tr => {
const titleCell = AnimeClient.$('td.align-left', tr)[0];
const titleLink = AnimeClient.$('a', titleCell)[0];
const linkTitle = String(titleLink.textContent).trim();
const textTitle = String(titleCell.textContent).trim();
if ( ! (filter.test(linkTitle) || filter.test(textTitle))) {
AnimeClient.hide(tr);
} else {
AnimeClient.show(tr);
}
});
} else {
AnimeClient.show('article.media');
AnimeClient.show('table.media-wrap tbody tr');
}
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
console.log('Service worker registered', reg.scope);
}).catch(error => {
console.error('Failed to register service worker', error);
});
}

View File

@ -1,768 +0,0 @@
// -------------------------------------------------------------------------
// ! Base
// -------------------------------------------------------------------------
const matches = (elm, selector) => {
let m = (elm.document || elm.ownerDocument).querySelectorAll(selector);
let i = matches.length;
while (--i >= 0 && m.item(i) !== elm) {} return i > -1;
};
const AnimeClient = {
/**
* Placeholder function
*/
noop: () => {},
/**
* DOM selector
*
* @param {string} selector - The dom selector string
* @param {object} [context]
* @return {[HTMLElement]} - array of dom elements
*/
$(selector, context = null) {
if (typeof selector !== 'string') {
return selector;
}
context = (context !== null && context.nodeType === 1)
? context
: document;
let elements = [];
if (selector.match(/^#([\w]+$)/)) {
elements.push(document.getElementById(selector.split('#')[1]));
} else {
elements = [].slice.apply(context.querySelectorAll(selector));
}
return elements;
},
/**
* Does the selector exist on the current page?
*
* @param {string} selector
* @returns {boolean}
*/
hasElement (selector) {
return AnimeClient.$(selector).length > 0;
},
/**
* Scroll to the top of the Page
*
* @return {void}
*/
scrollToTop () {
const el = AnimeClient.$('header')[0];
el.scrollIntoView(true);
},
/**
* Hide the selected element
*
* @param {string|Element} sel - the selector of the element to hide
* @return {void}
*/
hide (sel) {
if (typeof sel === 'string') {
sel = AnimeClient.$(sel);
}
if (Array.isArray(sel)) {
sel.forEach(el => el.setAttribute('hidden', 'hidden'));
} else {
sel.setAttribute('hidden', 'hidden');
}
},
/**
* UnHide the selected element
*
* @param {string|Element} sel - the selector of the element to hide
* @return {void}
*/
show (sel) {
if (typeof sel === 'string') {
sel = AnimeClient.$(sel);
}
if (Array.isArray(sel)) {
sel.forEach(el => el.removeAttribute('hidden'));
} else {
sel.removeAttribute('hidden');
}
},
/**
* Display a message box
*
* @param {string} type - message type: info, error, success
* @param {string} message - the message itself
* @return {void}
*/
showMessage (type, message) {
let template =
`<div class='message ${type}'>
<span class='icon'></span>
${message}
<span class='close'></span>
</div>`;
let sel = AnimeClient.$('.message');
if (sel[0] !== undefined) {
sel[0].remove();
}
AnimeClient.$('header')[0].insertAdjacentHTML('beforeend', template);
},
/**
* Finds the closest parent element matching the passed selector
*
* @param {HTMLElement} current - the current HTMLElement
* @param {string} parentSelector - selector for the parent element
* @return {HTMLElement|null} - the parent element
*/
closestParent (current, parentSelector) {
if (Element.prototype.closest !== undefined) {
return current.closest(parentSelector);
}
while (current !== document.documentElement) {
if (matches(current, parentSelector)) {
return current;
}
current = current.parentElement;
}
return null;
},
/**
* Generate a full url from a relative path
*
* @param {string} path - url path
* @return {string} - full url
*/
url (path) {
let uri = `//${document.location.host}`;
uri += (path.charAt(0) === '/') ? path : `/${path}`;
return uri;
},
/**
* Throttle execution of a function
*
* @see https://remysharp.com/2010/07/21/throttling-function-calls
* @see https://jsfiddle.net/jonathansampson/m7G64/
* @param {Number} interval - the minimum throttle time in ms
* @param {Function} fn - the function to throttle
* @param {Object} [scope] - the 'this' object for the function
* @return {Function}
*/
throttle (interval, fn, scope) {
let wait = false;
return function (...args) {
const context = scope || this;
if ( ! wait) {
fn.apply(context, args);
wait = true;
setTimeout(function() {
wait = false;
}, interval);
}
};
},
};
// -------------------------------------------------------------------------
// ! Events
// -------------------------------------------------------------------------
function addEvent(sel, event, listener) {
// Recurse!
if (! event.match(/^([\w\-]+)$/)) {
event.split(' ').forEach((evt) => {
addEvent(sel, evt, listener);
});
}
sel.addEventListener(event, listener, false);
}
function delegateEvent(sel, target, event, listener) {
// Attach the listener to the parent
addEvent(sel, event, (e) => {
// Get live version of the target selector
AnimeClient.$(target, sel).forEach((element) => {
if(e.target == element) {
listener.call(element, e);
e.stopPropagation();
}
});
});
}
/**
* Add an event listener
*
* @param {string|HTMLElement} sel - the parent selector to bind to
* @param {string} event - event name(s) to bind
* @param {string|HTMLElement|function} target - the element to directly bind the event to
* @param {function} [listener] - event listener callback
* @return {void}
*/
AnimeClient.on = (sel, event, target, listener) => {
if (listener === undefined) {
listener = target;
AnimeClient.$(sel).forEach((el) => {
addEvent(el, event, listener);
});
} else {
AnimeClient.$(sel).forEach((el) => {
delegateEvent(el, target, event, listener);
});
}
};
// -------------------------------------------------------------------------
// ! Ajax
// -------------------------------------------------------------------------
/**
* Url encoding for non-get requests
*
* @param data
* @returns {string}
* @private
*/
function ajaxSerialize(data) {
let pairs = [];
Object.keys(data).forEach((name) => {
let value = data[name].toString();
name = encodeURIComponent(name);
value = encodeURIComponent(value);
pairs.push(`${name}=${value}`);
});
return pairs.join('&');
}
/**
* Make an ajax request
*
* Config:{
* data: // data to send with the request
* type: // http verb of the request, defaults to GET
* success: // success callback
* error: // error callback
* }
*
* @param {string} url - the url to request
* @param {Object} config - the configuration object
* @return {XMLHttpRequest}
*/
AnimeClient.ajax = (url, config) => {
// Set some sane defaults
const defaultConfig = {
data: {},
type: 'GET',
dataType: '',
success: AnimeClient.noop,
mimeType: 'application/x-www-form-urlencoded',
error: AnimeClient.noop
};
config = {
...defaultConfig,
...config,
};
let request = new XMLHttpRequest();
let method = String(config.type).toUpperCase();
if (method === 'GET') {
url += (url.match(/\?/))
? ajaxSerialize(config.data)
: `?${ajaxSerialize(config.data)}`;
}
request.open(method, url);
request.onreadystatechange = () => {
if (request.readyState === 4) {
let responseText = '';
if (request.responseType === 'json') {
responseText = JSON.parse(request.responseText);
} else {
responseText = request.responseText;
}
if (request.status > 299) {
config.error.call(null, request.status, responseText, request.response);
} else {
config.success.call(null, responseText, request.status);
}
}
};
if (config.dataType === 'json') {
config.data = JSON.stringify(config.data);
config.mimeType = 'application/json';
} else {
config.data = ajaxSerialize(config.data);
}
request.setRequestHeader('Content-Type', config.mimeType);
if (method === 'GET') {
request.send(null);
} else {
request.send(config.data);
}
return request
};
/**
* Do a get request
*
* @param {string} url
* @param {object|function} data
* @param {function} [callback]
* @return {XMLHttpRequest}
*/
AnimeClient.get = (url, data, callback = null) => {
if (callback === null) {
callback = data;
data = {};
}
return AnimeClient.ajax(url, {
data,
success: callback
});
};
// ----------------------------------------------------------------------------
// Event subscriptions
// ----------------------------------------------------------------------------
AnimeClient.on('header', 'click', '.message', hide);
AnimeClient.on('form.js-delete', 'submit', confirmDelete);
AnimeClient.on('.js-clear-cache', 'click', clearAPICache);
AnimeClient.on('.vertical-tabs input', 'change', scrollToSection);
AnimeClient.on('.media-filter', 'input', filterMedia);
// ----------------------------------------------------------------------------
// Handler functions
// ----------------------------------------------------------------------------
/**
* Hide the html element attached to the event
*
* @param event
* @return void
*/
function hide (event) {
AnimeClient.hide(event.target);
}
/**
* Confirm deletion of an item
*
* @param event
* @return void
*/
function confirmDelete (event) {
const proceed = confirm('Are you ABSOLUTELY SURE you want to delete this item?');
if (proceed === false) {
event.preventDefault();
event.stopPropagation();
}
}
/**
* Clear the API cache, and show a message if the cache is cleared
*
* @return void
*/
function clearAPICache () {
AnimeClient.get('/cache_purge', () => {
AnimeClient.showMessage('success', 'Successfully purged api cache');
});
}
/**
* Scroll to the accordion/vertical tab section just opened
*
* @param event
* @return void
*/
function scrollToSection (event) {
const el = event.currentTarget.parentElement;
const rect = el.getBoundingClientRect();
const top = rect.top + window.pageYOffset;
window.scrollTo({
top,
behavior: 'smooth',
});
}
/**
* Filter an anime or manga list
*
* @param event
* @return void
*/
function filterMedia (event) {
const rawFilter = event.target.value;
const filter = new RegExp(rawFilter, 'i');
// console.log('Filtering items by: ', filter);
if (rawFilter !== '') {
// Filter the cover view
AnimeClient.$('article.media').forEach(article => {
const titleLink = AnimeClient.$('.name a', article)[0];
const title = String(titleLink.textContent).trim();
if ( ! filter.test(title)) {
AnimeClient.hide(article);
} else {
AnimeClient.show(article);
}
});
// Filter the list view
AnimeClient.$('table.media-wrap tbody tr').forEach(tr => {
const titleCell = AnimeClient.$('td.align-left', tr)[0];
const titleLink = AnimeClient.$('a', titleCell)[0];
const linkTitle = String(titleLink.textContent).trim();
const textTitle = String(titleCell.textContent).trim();
if ( ! (filter.test(linkTitle) || filter.test(textTitle))) {
AnimeClient.hide(tr);
} else {
AnimeClient.show(tr);
}
});
} else {
AnimeClient.show('article.media');
AnimeClient.show('table.media-wrap tbody tr');
}
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
console.log('Service worker registered', reg.scope);
}).catch(error => {
console.error('Failed to register service worker', error);
});
}
(() => {
// Var is intentional
var hidden = null;
var visibilityChange = null;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
function handleVisibilityChange() {
// Check the user's session to see if they are currently logged-in
// when the page becomes visible
if ( ! document[hidden]) {
AnimeClient.get('/heartbeat', (beat) => {
const status = JSON.parse(beat);
// If the session is expired, immediately reload so that
// you can't attempt to do an action that requires authentication
if (status.hasAuth !== true) {
document.removeEventListener(visibilityChange, handleVisibilityChange, false);
location.reload();
}
});
}
}
if (hidden === null) {
console.info('Page visibility API not supported, JS session check will not work');
} else {
document.addEventListener(visibilityChange, handleVisibilityChange, false);
}
})();
// Click on hidden MAL checkbox so
// that MAL id is passed
AnimeClient.on('main', 'change', '.big-check', (e) => {
const id = e.target.id;
document.getElementById(`mal_${id}`).checked = true;
});
function renderAnimeSearchResults (data) {
return data.map(item => {
const titles = item.titles.join('<br />');
return `
<article class="media search">
<div class="name">
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" />
<label for="${item.slug}">
<picture width="220">
<source srcset="/public/images/anime/${item.id}.webp" type="image/webp" />
<source srcset="/public/images/anime/${item.id}.jpg" type="image/jpeg" />
<img src="/public/images/anime/${item.id}.jpg" alt="" width="220" />
</picture>
<span class="name">
${item.canonicalTitle}<br />
<small>${titles}</small>
</span>
</label>
</div>
<div class="table">
<div class="row">
<span class="edit">
<a class="bracketed" href="/anime/details/${item.slug}">Info Page</a>
</span>
</div>
</div>
</article>
`;
}).join('');
}
function renderMangaSearchResults (data) {
return data.map(item => {
const titles = item.titles.join('<br />');
return `
<article class="media search">
<div class="name">
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" />
<label for="${item.slug}">
<picture width="220">
<source srcset="/public/images/manga/${item.id}.webp" type="image/webp" />
<source srcset="/public/images/manga/${item.id}.jpg" type="image/jpeg" />
<img src="/public/images/manga/${item.id}.jpg" alt="" width="220" />
</picture>
<span class="name">
${item.canonicalTitle}<br />
<small>${titles}</small>
</span>
</label>
</div>
<div class="table">
<div class="row">
<span class="edit">
<a class="bracketed" href="/manga/details/${item.slug}">Info Page</a>
</span>
</div>
</div>
</article>
`;
}).join('');
}
const search = (query) => {
// Show the loader
AnimeClient.show('.cssload-loader');
// Do the api search
return AnimeClient.get(AnimeClient.url('/anime-collection/search'), { query }, (searchResults, status) => {
searchResults = JSON.parse(searchResults);
// Hide the loader
AnimeClient.hide('.cssload-loader');
// Show the results
AnimeClient.$('#series-list')[ 0 ].innerHTML = renderAnimeSearchResults(searchResults);
});
};
if (AnimeClient.hasElement('.anime #search')) {
let prevRequest = null;
AnimeClient.on('#search', 'input', AnimeClient.throttle(250, (e) => {
const query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}
if (prevRequest !== null) {
prevRequest.abort();
}
prevRequest = search(query);
}));
}
// Action to increment episode count
AnimeClient.on('body.anime.list', 'click', '.plus-one', (e) => {
let parentSel = AnimeClient.closestParent(e.target, 'article');
let watchedCount = parseInt(AnimeClient.$('.completed_number', parentSel)[ 0 ].textContent, 10) || 0;
let totalCount = parseInt(AnimeClient.$('.total_number', parentSel)[ 0 ].textContent, 10);
let title = AnimeClient.$('.name a', parentSel)[ 0 ].textContent;
// Setup the update data
let data = {
id: parentSel.dataset.kitsuId,
mal_id: parentSel.dataset.malId,
data: {
progress: watchedCount + 1
}
};
// If the episode count is 0, and incremented,
// change status to currently watching
if (isNaN(watchedCount) || watchedCount === 0) {
data.data.status = 'CURRENT';
}
// If you increment at the last episode, mark as completed
if ((!isNaN(watchedCount)) && (watchedCount + 1) === totalCount) {
data.data.status = 'COMPLETED';
}
AnimeClient.show('#loading-shadow');
// okay, lets actually make some changes!
AnimeClient.ajax(AnimeClient.url('/anime/increment'), {
data,
dataType: 'json',
type: 'POST',
success: (res) => {
const resData = JSON.parse(res);
if (resData.error) {
AnimeClient.hide('#loading-shadow');
AnimeClient.showMessage('error', `Failed to update ${title}. `);
AnimeClient.scrollToTop();
return;
}
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
AnimeClient.hide(parentSel);
}
AnimeClient.hide('#loading-shadow');
AnimeClient.showMessage('success', `Successfully updated ${title}`);
AnimeClient.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
AnimeClient.scrollToTop();
},
error: () => {
AnimeClient.hide('#loading-shadow');
AnimeClient.showMessage('error', `Failed to update ${title}. `);
AnimeClient.scrollToTop();
}
});
});
const search$1 = (query) => {
AnimeClient.show('.cssload-loader');
return AnimeClient.get(AnimeClient.url('/manga/search'), { query }, (searchResults, status) => {
searchResults = JSON.parse(searchResults);
AnimeClient.hide('.cssload-loader');
AnimeClient.$('#series-list')[ 0 ].innerHTML = renderMangaSearchResults(searchResults);
});
};
if (AnimeClient.hasElement('.manga #search')) {
let prevRequest = null;
AnimeClient.on('#search', 'input', AnimeClient.throttle(250, (e) => {
let query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}
if (prevRequest !== null) {
prevRequest.abort();
}
prevRequest = search$1(query);
}));
}
/**
* Javascript for editing manga, if logged in
*/
AnimeClient.on('.manga.list', 'click', '.edit-buttons button', (e) => {
let thisSel = e.target;
let parentSel = AnimeClient.closestParent(e.target, 'article');
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
let completed = parseInt(AnimeClient.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
let total = parseInt(AnimeClient.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
let mangaName = AnimeClient.$('.name', parentSel)[ 0 ].textContent;
if (isNaN(completed)) {
completed = 0;
}
// Setup the update data
let data = {
id: parentSel.dataset.kitsuId,
mal_id: parentSel.dataset.malId,
data: {
progress: completed
}
};
// If the episode count is 0, and incremented,
// change status to currently reading
if (isNaN(completed) || completed === 0) {
data.data.status = 'CURRENT';
}
// If you increment at the last chapter, mark as completed
if ((!isNaN(completed)) && (completed + 1) === total) {
data.data.status = 'COMPLETED';
}
// Update the total count
data.data.progress = ++completed;
AnimeClient.show('#loading-shadow');
AnimeClient.ajax(AnimeClient.url('/manga/increment'), {
data,
dataType: 'json',
type: 'POST',
mimeType: 'application/json',
success: (res) => {
const resData = JSON.parse(res);
if (resData.error) {
AnimeClient.hide('#loading-shadow');
AnimeClient.showMessage('error', `Failed to update ${mangaName}. `);
AnimeClient.scrollToTop();
return;
}
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
AnimeClient.hide(parentSel);
}
AnimeClient.hide('#loading-shadow');
AnimeClient.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
AnimeClient.showMessage('success', `Successfully updated ${mangaName}`);
AnimeClient.scrollToTop();
},
error: () => {
AnimeClient.hide('#loading-shadow');
AnimeClient.showMessage('error', `Failed to update ${mangaName}`);
AnimeClient.scrollToTop();
}
});
});

14
public/js/anon.min.js vendored
View File

@ -1,14 +0,0 @@
(function(){var matches=function(elm,selector){var m=(elm.document||elm.ownerDocument).querySelectorAll(selector);var i=matches.length;while(--i>=0&&m.item(i)!==elm);return i>-1};var AnimeClient={noop:function(){},$:function(selector,context){context=context===undefined?null:context;if(typeof selector!=="string")return selector;context=context!==null&&context.nodeType===1?context:document;var elements=[];if(selector.match(/^#([\w]+$)/))elements.push(document.getElementById(selector.split("#")[1]));
else elements=[].slice.apply(context.querySelectorAll(selector));return elements},hasElement:function(selector){return AnimeClient.$(selector).length>0},scrollToTop:function(){var el=AnimeClient.$("header")[0];el.scrollIntoView(true)},hide:function(sel){if(typeof sel==="string")sel=AnimeClient.$(sel);if(Array.isArray(sel))sel.forEach(function(el){return el.setAttribute("hidden","hidden")});else sel.setAttribute("hidden","hidden")},show:function(sel){if(typeof sel==="string")sel=AnimeClient.$(sel);
if(Array.isArray(sel))sel.forEach(function(el){return el.removeAttribute("hidden")});else sel.removeAttribute("hidden")},showMessage:function(type,message){var template="<div class='message "+type+"'>\n\t\t\t\t<span class='icon'></span>\n\t\t\t\t"+message+"\n\t\t\t\t<span class='close'></span>\n\t\t\t</div>";var sel=AnimeClient.$(".message");if(sel[0]!==undefined)sel[0].remove();AnimeClient.$("header")[0].insertAdjacentHTML("beforeend",template)},closestParent:function(current,parentSelector){if(Element.prototype.closest!==
undefined)return current.closest(parentSelector);while(current!==document.documentElement){if(matches(current,parentSelector))return current;current=current.parentElement}return null},url:function(path){var uri="//"+document.location.host;uri+=path.charAt(0)==="/"?path:"/"+path;return uri},throttle:function(interval,fn,scope){var wait=false;return function(args){var $jscomp$restParams=[];for(var $jscomp$restIndex=0;$jscomp$restIndex<arguments.length;++$jscomp$restIndex)$jscomp$restParams[$jscomp$restIndex-
0]=arguments[$jscomp$restIndex];{var args$0=$jscomp$restParams;var context=scope||this;if(!wait){fn.apply(context,args$0);wait=true;setTimeout(function(){wait=false},interval)}}}}};function addEvent(sel,event,listener){if(!event.match(/^([\w\-]+)$/))event.split(" ").forEach(function(evt){addEvent(sel,evt,listener)});sel.addEventListener(event,listener,false)}function delegateEvent(sel,target,event,listener){addEvent(sel,event,function(e){AnimeClient.$(target,sel).forEach(function(element){if(e.target==
element){listener.call(element,e);e.stopPropagation()}})})}AnimeClient.on=function(sel,event,target,listener){if(listener===undefined){listener=target;AnimeClient.$(sel).forEach(function(el){addEvent(el,event,listener)})}else AnimeClient.$(sel).forEach(function(el){delegateEvent(el,target,event,listener)})};function ajaxSerialize(data){var pairs=[];Object.keys(data).forEach(function(name){var value=data[name].toString();name=encodeURIComponent(name);value=encodeURIComponent(value);pairs.push(name+
"="+value)});return pairs.join("&")}AnimeClient.ajax=function(url,config){var defaultConfig={data:{},type:"GET",dataType:"",success:AnimeClient.noop,mimeType:"application/x-www-form-urlencoded",error:AnimeClient.noop};config=Object.assign({},defaultConfig,config);var request=new XMLHttpRequest;var method=String(config.type).toUpperCase();if(method==="GET")url+=url.match(/\?/)?ajaxSerialize(config.data):"?"+ajaxSerialize(config.data);request.open(method,url);request.onreadystatechange=function(){if(request.readyState===
4){var responseText="";if(request.responseType==="json")responseText=JSON.parse(request.responseText);else responseText=request.responseText;if(request.status>299)config.error.call(null,request.status,responseText,request.response);else config.success.call(null,responseText,request.status)}};if(config.dataType==="json"){config.data=JSON.stringify(config.data);config.mimeType="application/json"}else config.data=ajaxSerialize(config.data);request.setRequestHeader("Content-Type",config.mimeType);if(method===
"GET")request.send(null);else request.send(config.data);return request};AnimeClient.get=function(url,data,callback){callback=callback===undefined?null:callback;if(callback===null){callback=data;data={}}return AnimeClient.ajax(url,{data:data,success:callback})};AnimeClient.on("header","click",".message",hide);AnimeClient.on("form.js-delete","submit",confirmDelete);AnimeClient.on(".js-clear-cache","click",clearAPICache);AnimeClient.on(".vertical-tabs input","change",scrollToSection);AnimeClient.on(".media-filter",
"input",filterMedia);function hide(event){AnimeClient.hide(event.target)}function confirmDelete(event){var proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();event.stopPropagation()}}function clearAPICache(){AnimeClient.get("/cache_purge",function(){AnimeClient.showMessage("success","Successfully purged api cache")})}function scrollToSection(event){var el=event.currentTarget.parentElement;var rect=el.getBoundingClientRect();var top=
rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})}function filterMedia(event){var rawFilter=event.target.value;var filter=new RegExp(rawFilter,"i");if(rawFilter!==""){AnimeClient.$("article.media").forEach(function(article){var titleLink=AnimeClient.$(".name a",article)[0];var title=String(titleLink.textContent).trim();if(!filter.test(title))AnimeClient.hide(article);else AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(function(tr){var titleCell=
AnimeClient.$("td.align-left",tr)[0];var titleLink=AnimeClient.$("a",titleCell)[0];var linkTitle=String(titleLink.textContent).trim();var textTitle=String(titleCell.textContent).trim();if(!(filter.test(linkTitle)||filter.test(textTitle)))AnimeClient.hide(tr);else AnimeClient.show(tr)})}else{AnimeClient.show("article.media");AnimeClient.show("table.media-wrap tbody tr")}}if("serviceWorker"in navigator)navigator.serviceWorker.register("/sw.js").then(function(reg){console.log("Service worker registered",
reg.scope)})["catch"](function(error){console.error("Failed to register service worker",error)})})()
//# sourceMappingURL=anon.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1 @@
(function(){var LightTableSorter=function(){var th=null;var cellIndex=null;var order="";var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);var n=parseInt(textA,10);if(n){textA=n;textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return-1;return 0};var toggle=function(){var c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order= var LightTableSorter=function(){var th=null;var cellIndex=null;var order='';var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase();};var sort=function(a,b){var textA=text(a);var textB=text(b);var n=parseInt(textA,10);if(n){textA=n;textB=parseInt(textB,10);}if(textA>textB)return 1;if(textA<textB)return -1;return 0;};var toggle=function(){var c=order!=='sorting-asc'?'sorting-asc':'sorting-desc';th.className=(th.className.replace(order,'')+' '+c).trim();return order=c;};var reset=function(){th.classList.remove('sorting-asc','sorting-desc');th.classList.add('sorting');return order='';};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==='th'){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName('tbody')[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==='sorting-asc')rows.reverse();toggle();tbody.innerHtml='';rows.forEach(function(row){tbody.appendChild(row);});}}};return {init:function(){var ths=document.getElementsByTagName('th');var results=[];for(var i=0,len=ths.length;i<len;i++){var th1=ths[i];th1.classList.add('sorting');results.push(th1.onclick=onClickEvent);}return results;}};}();LightTableSorter.init();
c};var reset=function(){th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName("tbody")[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(function(row){tbody.appendChild(row)})}}};
return{init:function(){var ths=document.getElementsByTagName("th");var results=[];for(var i=0,len=ths.length;i<len;i++){var th$0=ths[i];th$0.classList.add("sorting");results.push(th$0.onclick=onClickEvent)}return results}}}();LightTableSorter.init()})()
//# sourceMappingURL=tables.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"tables.min.js.map","sources":["../../frontEndSrc/js/base/sort-tables.js"],"sourcesContent":["const LightTableSorter = (() => {\n\tlet th = null;\n\tlet cellIndex = null;\n\tlet order = '';\n\tconst text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();\n\tconst sort = (a, b) => {\n\t\tlet textA = text(a);\n\t\tlet textB = text(b);\n\t\tconst n = parseInt(textA, 10);\n\t\tif (n) {\n\t\t\ttextA = n;\n\t\t\ttextB = parseInt(textB, 10);\n\t\t}\n\t\tif (textA > textB) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (textA < textB) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t};\n\tconst toggle = () => {\n\t\tconst c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';\n\t\tth.className = (th.className.replace(order, '') + ' ' + c).trim();\n\t\treturn order = c;\n\t};\n\tconst reset = () => {\n\t\tth.classList.remove('sorting-asc', 'sorting-desc');\n\t\tth.classList.add('sorting');\n\t\treturn order = '';\n\t};\n\tconst onClickEvent = (e) => {\n\t\tif (th && (cellIndex !== e.target.cellIndex)) {\n\t\t\treset();\n\t\t}\n\t\tth = e.target;\n\t\tif (th.nodeName.toLowerCase() === 'th') {\n\t\t\tcellIndex = th.cellIndex;\n\t\t\tconst tbody = th.offsetParent.getElementsByTagName('tbody')[0];\n\t\t\tlet rows = Array.from(tbody.rows);\n\t\t\tif (rows) {\n\t\t\t\trows.sort(sort);\n\t\t\t\tif (order === 'sorting-asc') {\n\t\t\t\t\trows.reverse();\n\t\t\t\t}\n\t\t\t\ttoggle();\n\t\t\t\ttbody.innerHtml = '';\n\n\t\t\t\trows.forEach(row => {\n\t\t\t\t\ttbody.appendChild(row);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\treturn {\n\t\tinit: () => {\n\t\t\tlet ths = document.getElementsByTagName('th');\n\t\t\tlet results = [];\n\t\t\tfor (let i = 0, len = ths.length; i < len; i++) {\n\t\t\t\tlet th = ths[i];\n\t\t\t\tth.classList.add('sorting');\n\t\t\t\tresults.push(th.onclick = onClickEvent);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t};\n})();\n\nLightTableSorter.init();"],"names":["th","cellIndex","order","text","row","cells","item","textContent","toLowerCase","sort","a","b","textA","textB","n","parseInt","toggle","c","className","trim","replace","reset","classList","remove","add","onClickEvent","e","target","nodeName","tbody","offsetParent","getElementsByTagName","rows","Array","from","reverse","innerHtml","forEach","appendChild","init","ths","document","results","i","len","length","push","onclick","LightTableSorter"],"mappings":"YAAA,gCACC,IAAIA,GAAK,IACT,KAAIC,UAAY,IAChB,KAAIC,MAAQ,EACZ,KAAMC,KAAOA,QAAA,CAACC,GAAD,CAAS,CAAA,MAAAA,IAAAC,MAAAC,KAAA,CAAeL,SAAf,CAAAM,YAAAC,YAAA,EAAA,CACtB,KAAMC,KAAOA,QAAA,CAACC,CAAD,CAAIC,CAAJ,CAAU,CACtB,IAAIC,MAAQT,IAAA,CAAKO,CAAL,CACZ,KAAIG,MAAQV,IAAA,CAAKQ,CAAL,CACZ,KAAMG,EAAIC,QAAA,CAASH,KAAT,CAAgB,EAAhB,CACV,IAAIE,CAAJ,CAAO,CACNF,KAAA,CAAQE,CACRD,MAAA,CAAQE,QAAA,CAASF,KAAT,CAAgB,EAAhB,CAFF,CAIP,GAAID,KAAJ,CAAYC,KAAZ,CACC,MAAO,EAER,IAAID,KAAJ,CAAYC,KAAZ,CACC,MAAO,EAER,OAAO,EAde,CAgBvB,KAAMG,OAASA,QAAA,EAAM,CACpB,IAAMC,EAAIf,KAAA,GAAU,aAAV,CAA0B,aAA1B,CAA0C,cACpDF,GAAAkB,UAAA,CAAeC,CAACnB,EAAAkB,UAAAE,QAAA,CAAqBlB,KAArB,CAA4B,EAA5B,CAADiB,CAAmC,GAAnCA,CAAyCF,CAAzCE,MAAA,EACf,OAAOjB,MAAP;AAAee,CAHK,CAKrB,KAAMI,MAAQA,QAAA,EAAM,CACnBrB,EAAAsB,UAAAC,OAAA,CAAoB,aAApB,CAAmC,cAAnC,CACAvB,GAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACA,OAAOtB,MAAP,CAAe,EAHI,CAKpB,KAAMuB,aAAeA,QAAA,CAACC,CAAD,CAAO,CAC3B,GAAI1B,EAAJ,EAAWC,SAAX,GAAyByB,CAAAC,OAAA1B,UAAzB,CACCoB,KAAA,EAEDrB,GAAA,CAAK0B,CAAAC,OACL,IAAI3B,EAAA4B,SAAApB,YAAA,EAAJ,GAAkC,IAAlC,CAAwC,CACvCP,SAAA,CAAYD,EAAAC,UACZ,KAAM4B,MAAQ7B,EAAA8B,aAAAC,qBAAA,CAAqC,OAArC,CAAA,CAA8C,CAA9C,CACd,KAAIC,KAAOC,KAAAC,KAAA,CAAWL,KAAAG,KAAX,CACX,IAAIA,IAAJ,CAAU,CACTA,IAAAvB,KAAA,CAAUA,IAAV,CACA,IAAIP,KAAJ,GAAc,aAAd,CACC8B,IAAAG,QAAA,EAEDnB,OAAA,EACAa,MAAAO,UAAA,CAAkB,EAElBJ,KAAAK,QAAA,CAAa,QAAA,CAAAjC,GAAA,CAAO,CACnByB,KAAAS,YAAA,CAAkBlC,GAAlB,CADmB,CAApB,CARS,CAJ6B,CALb,CAuB5B;MAAO,CACNmC,KAAMA,QAAA,EAAM,CACX,IAAIC,IAAMC,QAAAV,qBAAA,CAA8B,IAA9B,CACV,KAAIW,QAAU,EACd,KAAK,IAAIC,EAAI,CAAR,CAAWC,IAAMJ,GAAAK,OAAtB,CAAkCF,CAAlC,CAAsCC,GAAtC,CAA2CD,CAAA,EAA3C,CAAgD,CAC/C,IAAI3C,KAAKwC,GAAA,CAAIG,CAAJ,CACT3C,KAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACAkB,QAAAI,KAAA,CAAa9C,IAAA+C,QAAb,CAA0BtB,YAA1B,CAH+C,CAKhD,MAAOiB,QARI,CADN,IAcRM,iBAAAT,KAAA;"} {"version":3,"sources":["/var/www/htdocs/github.timshomepage.net/animeclient/frontEndSrc/js/base/sort-tables.js"],"sourcesContent":["const LightTableSorter = (() => {\n\tlet th = null;\n\tlet cellIndex = null;\n\tlet order = '';\n\tconst text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();\n\tconst sort = (a, b) => {\n\t\tlet textA = text(a);\n\t\tlet textB = text(b);\n\t\tconst n = parseInt(textA, 10);\n\t\tif (n) {\n\t\t\ttextA = n;\n\t\t\ttextB = parseInt(textB, 10);\n\t\t}\n\t\tif (textA > textB) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (textA < textB) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t};\n\tconst toggle = () => {\n\t\tconst c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';\n\t\tth.className = (th.className.replace(order, '') + ' ' + c).trim();\n\t\treturn order = c;\n\t};\n\tconst reset = () => {\n\t\tth.classList.remove('sorting-asc', 'sorting-desc');\n\t\tth.classList.add('sorting');\n\t\treturn order = '';\n\t};\n\tconst onClickEvent = (e) => {\n\t\tif (th && (cellIndex !== e.target.cellIndex)) {\n\t\t\treset();\n\t\t}\n\t\tth = e.target;\n\t\tif (th.nodeName.toLowerCase() === 'th') {\n\t\t\tcellIndex = th.cellIndex;\n\t\t\tconst tbody = th.offsetParent.getElementsByTagName('tbody')[0];\n\t\t\tlet rows = Array.from(tbody.rows);\n\t\t\tif (rows) {\n\t\t\t\trows.sort(sort);\n\t\t\t\tif (order === 'sorting-asc') {\n\t\t\t\t\trows.reverse();\n\t\t\t\t}\n\t\t\t\ttoggle();\n\t\t\t\ttbody.innerHtml = '';\n\n\t\t\t\trows.forEach(row => {\n\t\t\t\t\ttbody.appendChild(row);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\treturn {\n\t\tinit: () => {\n\t\t\tlet ths = document.getElementsByTagName('th');\n\t\t\tlet results = [];\n\t\t\tfor (let i = 0, len = ths.length; i < len; i++) {\n\t\t\t\tlet th = ths[i];\n\t\t\t\tth.classList.add('sorting');\n\t\t\t\tresults.push(th.onclick = onClickEvent);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t};\n})();\n\nLightTableSorter.init();"],"names":[],"mappings":"IAAM,gBAAgB,gBACjB,EAAE,CAAG,IAAI,KACT,SAAS,CAAG,IAAI,KAChB,KAAK,QACH,IAAI,UAAI,GAAG,SAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,WAAW,SACjE,IAAI,UAAI,CAAC,CAAE,CAAC,MACb,KAAK,CAAG,IAAI,CAAC,CAAC,MACd,KAAK,CAAG,IAAI,CAAC,CAAC,MACZ,CAAC,CAAG,QAAQ,CAAC,KAAK,CAAE,EAAE,KACxB,CAAC,EACJ,KAAK,CAAG,CAAC,CACT,KAAK,CAAG,QAAQ,CAAC,KAAK,CAAE,EAAE,MAEvB,KAAK,CAAG,KAAK,QACT,CAAC,IAEL,KAAK,CAAG,KAAK,QACT,EAAE,QAEH,CAAC,OAEH,MAAM,gBACL,CAAC,CAAG,KAAK,IAAK,WAAa,GAAG,WAAa,GAAG,YAAc,EAClE,EAAE,CAAC,SAAS,EAAI,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,MAAQ,CAAG,EAAG,CAAC,EAAE,IAAI,UACxD,KAAK,CAAG,CAAC,OAEX,KAAK,YACV,EAAE,CAAC,SAAS,CAAC,MAAM,EAAC,WAAa,GAAE,YAAc,GACjD,EAAE,CAAC,SAAS,CAAC,GAAG,EAAC,OAAS,UACnB,KAAK,UAEP,YAAY,UAAI,CAAC,KAClB,EAAE,EAAK,SAAS,GAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAC1C,KAAK,GAEN,EAAE,CAAG,CAAC,CAAC,MAAM,IACT,EAAE,CAAC,QAAQ,CAAC,WAAW,MAAO,EAAI,GACrC,SAAS,CAAG,EAAE,CAAC,SAAS,KAClB,KAAK,CAAG,EAAE,CAAC,YAAY,CAAC,oBAAoB,EAAC,KAAO,GAAE,CAAC,MACzD,IAAI,CAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAC5B,IAAI,EACP,IAAI,CAAC,IAAI,CAAC,IAAI,KACV,KAAK,IAAK,WAAa,EAC1B,IAAI,CAAC,OAAO,GAEb,MAAM,GACN,KAAK,CAAC,SAAS,IAEf,IAAI,CAAC,OAAO,UAAC,GAAG,EACf,KAAK,CAAC,WAAW,CAAC,GAAG,iBAMxB,IAAI,gBACC,GAAG,CAAG,QAAQ,CAAC,oBAAoB,EAAC,EAAI,OACxC,OAAO,YACF,CAAC,CAAG,CAAC,CAAE,GAAG,CAAG,GAAG,CAAC,MAAM,CAAE,CAAC,CAAG,GAAG,CAAE,CAAC,QACvC,GAAE,CAAG,GAAG,CAAC,CAAC,EACd,GAAE,CAAC,SAAS,CAAC,GAAG,EAAC,OAAS,GAC1B,OAAO,CAAC,IAAI,CAAC,GAAE,CAAC,OAAO,CAAG,YAAY,UAEhC,OAAO,QAKjB,gBAAgB,CAAC,IAAI"}