Version 5.1 - All the GraphQL #32
@ -13,10 +13,10 @@
|
||||
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<script nomodule='nomodule' async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
|
||||
<script type="module" src="<?= $urlGenerator->assetUrl('es/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/index.min.js') ?>"></script>
|
||||
<script type="module" src="<?= $urlGenerator->assetUrl('es/anon.js') ?>"></script>
|
||||
<?php endif ?>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import compiler from '@ampproject/rollup-plugin-closure-compiler';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
|
||||
const plugins = [
|
||||
compiler({
|
||||
@ -52,16 +51,14 @@ let modules = [{
|
||||
input: './js/anon.js',
|
||||
output: {
|
||||
...moduleOutput,
|
||||
file: '../public/es/anon.min.js',
|
||||
file: '../public/es/anon.js',
|
||||
},
|
||||
plugins: [terser()],
|
||||
}, {
|
||||
input: './js/index.js',
|
||||
output: {
|
||||
...moduleOutput,
|
||||
file: '../public/es/scripts.min.js',
|
||||
file: '../public/es/scripts.js',
|
||||
},
|
||||
plugins: [terser()],
|
||||
}];
|
||||
|
||||
// Return the config array for rollup
|
||||
|
@ -16,7 +16,6 @@
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"rollup": "^2.4.0",
|
||||
"rollup-plugin-terser": "^5.3.0",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -23,27 +23,6 @@
|
||||
magic-string "0.25.7"
|
||||
uuid "7.0.2"
|
||||
|
||||
"@babel/code-frame@^7.5.5":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
||||
integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.8.3"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.9.0":
|
||||
version "7.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
|
||||
integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
|
||||
|
||||
"@babel/highlight@^7.8.3":
|
||||
version "7.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079"
|
||||
integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.9.0"
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@csstools/convert-colors@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
|
||||
@ -125,11 +104,6 @@ browserslist@^4.0.0, browserslist@^4.6.3, browserslist@^4.6.4:
|
||||
electron-to-chromium "^1.3.188"
|
||||
node-releases "^1.1.25"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
caller-callsite@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
|
||||
@ -169,7 +143,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000981:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000983.tgz#ab3c70061ca2a3467182a10ac75109b199b647f8"
|
||||
integrity sha512-/llD1bZ6qwNkt41AsvjsmwNOoA4ZB+8iqmf5LVyeSXuBODT/hAMFNVOh84NdUzoiYiSKqo5vQ3ZzeYHSi/olDQ==
|
||||
|
||||
chalk@2.x, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||
chalk@2.x, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
@ -253,11 +227,6 @@ color@^3.0.0:
|
||||
color-convert "^1.9.1"
|
||||
color-string "^1.5.2"
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
concurrently@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.1.0.tgz#05523986ba7aaf4b58a49ddd658fab88fa783132"
|
||||
@ -557,11 +526,6 @@ estree-walker@2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0"
|
||||
integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==
|
||||
|
||||
estree-walker@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
|
||||
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
|
||||
|
||||
exec-sh@^0.2.0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36"
|
||||
@ -780,19 +744,6 @@ isarray@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
jest-worker@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
|
||||
integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
|
||||
dependencies:
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^6.1.0"
|
||||
|
||||
js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.13.1:
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||
@ -861,11 +812,6 @@ mdn-data@~1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01"
|
||||
integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||
|
||||
merge@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
|
||||
@ -1663,24 +1609,6 @@ rgba-regex@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
|
||||
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
|
||||
|
||||
rollup-plugin-terser@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz#9c0dd33d5771df9630cd027d6a2559187f65885e"
|
||||
integrity sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.5.5"
|
||||
jest-worker "^24.9.0"
|
||||
rollup-pluginutils "^2.8.2"
|
||||
serialize-javascript "^2.1.2"
|
||||
terser "^4.6.2"
|
||||
|
||||
rollup-pluginutils@^2.8.2:
|
||||
version "2.8.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
|
||||
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
|
||||
dependencies:
|
||||
estree-walker "^0.6.1"
|
||||
|
||||
rollup@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.4.0.tgz#b136a4d701d24dd79ec9551ee0330e7f632ee9d2"
|
||||
@ -1710,11 +1638,6 @@ sax@~1.2.4:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
|
||||
integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
|
||||
|
||||
serialize-javascript@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
|
||||
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
@ -1727,20 +1650,12 @@ simple-swizzle@^0.2.2:
|
||||
dependencies:
|
||||
is-arrayish "^0.3.1"
|
||||
|
||||
source-map-support@~0.5.12:
|
||||
version "0.5.16"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
|
||||
integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.5.1, source-map@^0.5.3:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
|
||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
@ -1862,15 +1777,6 @@ svgo@^1.0.0:
|
||||
unquote "~1.1.1"
|
||||
util.promisify "~1.0.0"
|
||||
|
||||
terser@^4.6.2:
|
||||
version "4.6.11"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
|
||||
integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.6.1"
|
||||
source-map-support "~0.5.12"
|
||||
|
||||
timsort@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||
|
460
public/es/anon.js
Normal file
460
public/es/anon.js
Normal file
@ -0,0 +1,460 @@
|
||||
// -------------------------------------------------------------------------
|
||||
// ! 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 {void}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Do a get request
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {object|function} data
|
||||
* @param {function} [callback]
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
721
public/es/scripts.js
Normal file
721
public/es/scripts.js
Normal file
@ -0,0 +1,721 @@
|
||||
// -------------------------------------------------------------------------
|
||||
// ! 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 {void}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Do a get request
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {object|function} data
|
||||
* @param {function} [callback]
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const results = [];
|
||||
|
||||
data.forEach(x => {
|
||||
const item = x.attributes;
|
||||
const titles = item.titles.reduce((prev, current) => {
|
||||
return prev + `${current}<br />`;
|
||||
}, []);
|
||||
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
<div class="name">
|
||||
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
|
||||
<label for="${item.slug}">
|
||||
<picture width="220">
|
||||
<source srcset="/public/images/anime/${x.id}.webp" type="image/webp" />
|
||||
<source srcset="/public/images/anime/${x.id}.jpg" type="image/jpeg" />
|
||||
<img src="/public/images/anime/${x.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>
|
||||
`);
|
||||
});
|
||||
|
||||
return results.join('');
|
||||
}
|
||||
|
||||
function renderMangaSearchResults (data) {
|
||||
const results = [];
|
||||
|
||||
data.forEach(x => {
|
||||
const item = x.attributes;
|
||||
const titles = item.titles.reduce((prev, current) => {
|
||||
return prev + `${current}<br />`;
|
||||
}, []);
|
||||
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
<div class="name">
|
||||
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
|
||||
<label for="${item.slug}">
|
||||
<picture width="220">
|
||||
<source srcset="/public/images/manga/${x.id}.webp" type="image/webp" />
|
||||
<source srcset="/public/images/manga/${x.id}.jpg" type="image/jpeg" />
|
||||
<img src="/public/images/manga/${x.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>
|
||||
`);
|
||||
});
|
||||
|
||||
return results.join('');
|
||||
}
|
||||
|
||||
const search = (query) => {
|
||||
// Show the loader
|
||||
AnimeClient.show('.cssload-loader');
|
||||
|
||||
// Do the api search
|
||||
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.data);
|
||||
});
|
||||
};
|
||||
|
||||
if (AnimeClient.hasElement('.anime #search')) {
|
||||
AnimeClient.on('#search', 'input', AnimeClient.throttle(250, (e) => {
|
||||
const query = encodeURIComponent(e.target.value);
|
||||
if (query === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
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.errors) {
|
||||
AnimeClient.hide('#loading-shadow');
|
||||
AnimeClient.showMessage('error', `Failed to update ${title}. `);
|
||||
AnimeClient.scrollToTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (resData.data.attributes.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');
|
||||
AnimeClient.get(AnimeClient.url('/manga/search'), { query }, (searchResults, status) => {
|
||||
searchResults = JSON.parse(searchResults);
|
||||
AnimeClient.hide('.cssload-loader');
|
||||
AnimeClient.$('#series-list')[ 0 ].innerHTML = renderMangaSearchResults(searchResults.data);
|
||||
});
|
||||
};
|
||||
|
||||
if (AnimeClient.hasElement('.manga #search')) {
|
||||
AnimeClient.on('#search', 'input', AnimeClient.throttle(250, (e) => {
|
||||
let query = encodeURIComponent(e.target.value);
|
||||
if (query === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
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: () => {
|
||||
if (data.data.status === 'completed') {
|
||||
AnimeClient.hide(parentSel);
|
||||
}
|
||||
|
||||
AnimeClient.hide('#loading-shadow');
|
||||
|
||||
AnimeClient.$(`.${type}s_read`, parentSel)[ 0 ].textContent = completed;
|
||||
AnimeClient.showMessage('success', `Successfully updated ${mangaName}`);
|
||||
AnimeClient.scrollToTop();
|
||||
},
|
||||
error: () => {
|
||||
AnimeClient.hide('#loading-shadow');
|
||||
AnimeClient.showMessage('error', `Failed to update ${mangaName}`);
|
||||
AnimeClient.scrollToTop();
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user