Merge remote-tracking branch 'origin/develop'
All checks were successful
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good
All checks were successful
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good
This commit is contained in:
commit
9cc3645950
@ -4,7 +4,6 @@ install:
|
||||
- composer install --ignore-platform-reqs
|
||||
|
||||
php:
|
||||
- 7.3
|
||||
- 7.4
|
||||
- nightly
|
||||
|
||||
@ -12,13 +11,6 @@ script:
|
||||
- mkdir -p build/logs
|
||||
- php vendor/bin/phpunit -c build
|
||||
|
||||
#after_script:
|
||||
# - CODECLIMATE_REPO_TOKEN=2cbddcebcb9256b3402867282e119dbe61de0b31039325356af3c7d72ed6d058 vendor/bin/test-reporter
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
|
||||
#addons:
|
||||
# code_climate:
|
||||
# repo_token: 2cbddcebcb9256b3402867282e119dbe61de0b31039325356af3c7d72ed6d058
|
@ -1,8 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## Version 5
|
||||
* Updated PHP requirement to 7.4
|
||||
* Added anime watching history view
|
||||
* Added manga reading history view
|
||||
* Updated anime collection to have more media types
|
||||
|
||||
## Version 4.2
|
||||
* Updated dependencies
|
||||
* Updated PHP requirement to 7.3
|
||||
* Added option to automatically set dark mode based on the OS setting
|
||||
|
||||
## Version 4.1
|
||||
* Added optional dark theme
|
||||
|
12
Jenkinsfile
vendored
12
Jenkinsfile
vendored
@ -10,18 +10,6 @@ pipeline {
|
||||
sh 'php composer.phar install --ignore-platform-reqs'
|
||||
}
|
||||
}
|
||||
stage('PHP 7.3') {
|
||||
agent {
|
||||
docker {
|
||||
image 'php:7.3-alpine'
|
||||
args '-u root --privileged'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'apk add --no-cache git'
|
||||
sh 'php ./vendor/bin/phpunit --colors=never'
|
||||
}
|
||||
}
|
||||
stage('PHP 7.4') {
|
||||
agent {
|
||||
docker {
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Hummingbird Anime Client
|
||||
|
||||
Update your anime/manga list on Kitsu.io and MyAnimeList.net
|
||||
Update your anime/manga list on Kitsu.io and Anilist
|
||||
|
||||
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
|
||||
[![Build Status](https://travis-ci.com/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.com/github/timw4mail/HummingBirdAnimeClient)
|
||||
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=timw4mail/HummingBirdAnimeClient/develop)](https://jenkins.timshomepage.net/job/timw4mail/HummingBirdAnimeClient/develop)
|
||||
|
||||
[[Hosted Example](https://list.timshomepage.net)]
|
||||
@ -31,7 +31,7 @@ Update your anime/manga list on Kitsu.io and MyAnimeList.net
|
||||
|
||||
### Requirements
|
||||
|
||||
* PHP 7.3+
|
||||
* PHP 7.4+
|
||||
* PDO SQLite or PDO PostgreSQL (For collection tab)
|
||||
* GD extension for caching images
|
||||
|
||||
|
@ -1,19 +1,21 @@
|
||||
[anime_list]
|
||||
route_prefix = "/anime"
|
||||
route_prefix = ""
|
||||
[anime_list.items]
|
||||
watching = '/watching'
|
||||
plan_to_watch = '/plan_to_watch'
|
||||
on_hold = '/on_hold'
|
||||
dropped = '/dropped'
|
||||
completed = '/completed'
|
||||
all = '/all'
|
||||
watch_history = '/history/anime'
|
||||
watching = '/anime/watching'
|
||||
plan_to_watch = '/anime/plan_to_watch'
|
||||
on_hold = '/anime/on_hold'
|
||||
dropped = '/anime/dropped'
|
||||
completed = '/anime/completed'
|
||||
all = '/anime/all'
|
||||
|
||||
[manga_list]
|
||||
route_prefix = "/manga"
|
||||
route_prefix = ""
|
||||
[manga_list.items]
|
||||
reading = '/reading'
|
||||
plan_to_read = '/plan_to_read'
|
||||
on_hold = '/on_hold'
|
||||
dropped = '/dropped'
|
||||
completed = '/completed'
|
||||
all = '/all'
|
||||
reading_history = '/history/manga'
|
||||
reading = '/manga/reading'
|
||||
plan_to_read = '/manga/plan_to_read'
|
||||
on_hold = '/manga/on_hold'
|
||||
dropped = '/manga/dropped'
|
||||
completed = '/manga/completed'
|
||||
all = '/manga/all'
|
@ -279,6 +279,13 @@ $routes = [
|
||||
'view' => ALPHA_SLUG_PATTERN,
|
||||
],
|
||||
],
|
||||
'history' => [
|
||||
'controller' => 'history',
|
||||
'path' => '/history/{type}',
|
||||
'tokens' => [
|
||||
'type' => SLUG_PATTERN
|
||||
]
|
||||
],
|
||||
'index_redirect' => [
|
||||
'path' => '/',
|
||||
'action' => 'redirectToDefaultRoute',
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -28,14 +28,15 @@ use Aviat\AnimeClient\Model;
|
||||
use Aviat\Banker\Pool;
|
||||
use Aviat\Ion\Config;
|
||||
use Aviat\Ion\Di\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Laminas\Diactoros\{Response, ServerRequestFactory};
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Logger;
|
||||
use Zend\Diactoros\{Response, ServerRequestFactory};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Setup DI container
|
||||
// -----------------------------------------------------------------------------
|
||||
return static function ($configArray = []) {
|
||||
return static function (array $configArray = []): Container {
|
||||
$container = new Container();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -60,26 +61,20 @@ return static function ($configArray = []) {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Create Config Object
|
||||
$container->set('config', static function() use ($configArray) {
|
||||
return new Config($configArray);
|
||||
});
|
||||
$container->set('config', fn () => new Config($configArray));
|
||||
|
||||
// Create Cache Object
|
||||
$container->set('cache', static function($container): Pool {
|
||||
$container->set('cache', static function(ContainerInterface $container): Pool {
|
||||
$logger = $container->getLogger();
|
||||
$config = $container->get('config')->get('cache');
|
||||
return new Pool($config, $logger);
|
||||
});
|
||||
|
||||
// Create List Cache
|
||||
|
||||
// Create Aura Router Object
|
||||
$container->set('aura-router', static function() {
|
||||
return new RouterContainer;
|
||||
});
|
||||
$container->set('aura-router', fn() => new RouterContainer);
|
||||
|
||||
// Create Html helper Object
|
||||
$container->set('html-helper', static function($container) {
|
||||
$container->set('html-helper', static function(ContainerInterface $container) {
|
||||
$htmlHelper = (new HelperLocatorFactory)->newInstance();
|
||||
$htmlHelper->set('menu', static function() use ($container) {
|
||||
$menuHelper = new Helper\Menu();
|
||||
@ -101,31 +96,23 @@ return static function ($configArray = []) {
|
||||
});
|
||||
|
||||
// Create Request/Response Objects
|
||||
$container->set('request', static function() {
|
||||
return ServerRequestFactory::fromGlobals(
|
||||
$container->set('request', fn () => ServerRequestFactory::fromGlobals(
|
||||
$_SERVER,
|
||||
$_GET,
|
||||
$_POST,
|
||||
$_COOKIE,
|
||||
$_FILES
|
||||
);
|
||||
});
|
||||
$container->set('response', static function() {
|
||||
return new Response;
|
||||
});
|
||||
));
|
||||
$container->set('response', fn () => new Response);
|
||||
|
||||
// Create session Object
|
||||
$container->set('session', static function() {
|
||||
return (new SessionFactory())->newInstance($_COOKIE);
|
||||
});
|
||||
$container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE));
|
||||
|
||||
// Miscellaneous helper methods
|
||||
$container->set('util', static function($container): Util {
|
||||
return new Util($container);
|
||||
});
|
||||
$container->set('util', fn ($container) => new Util($container));
|
||||
|
||||
// Models
|
||||
$container->set('kitsu-model', static function($container): Kitsu\Model {
|
||||
$container->set('kitsu-model', static function(ContainerInterface $container): Kitsu\Model {
|
||||
$requestBuilder = new KitsuRequestBuilder();
|
||||
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
|
||||
|
||||
@ -141,7 +128,7 @@ return static function ($configArray = []) {
|
||||
$model->setCache($cache);
|
||||
return $model;
|
||||
});
|
||||
$container->set('anilist-model', static function($container): Anilist\Model {
|
||||
$container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model {
|
||||
$requestBuilder = new Anilist\AnilistRequestBuilder();
|
||||
$requestBuilder->setLogger($container->getLogger('anilist-request'));
|
||||
|
||||
@ -155,19 +142,10 @@ return static function ($configArray = []) {
|
||||
|
||||
return $model;
|
||||
});
|
||||
|
||||
$container->set('anime-model', static function($container) {
|
||||
return new Model\Anime($container);
|
||||
});
|
||||
$container->set('manga-model', static function($container) {
|
||||
return new Model\Manga($container);
|
||||
});
|
||||
$container->set('anime-collection-model', static function($container) {
|
||||
return new Model\AnimeCollection($container);
|
||||
});
|
||||
$container->set('manga-collection-model', static function($container) {
|
||||
return new Model\MangaCollection($container);
|
||||
});
|
||||
$container->set('anime-model', fn ($container) => new Model\Anime($container));
|
||||
$container->set('manga-model', fn ($container) => new Model\Manga($container));
|
||||
$container->set('anime-collection-model', fn ($container) => new Model\AnimeCollection($container));
|
||||
$container->set('manga-collection-model', fn ($container) => new Model\MangaCollection($container));
|
||||
$container->set('settings-model', static function($container) {
|
||||
$model = new Model\Settings($container->get('config'));
|
||||
$model->setContainer($container);
|
||||
@ -175,19 +153,13 @@ return static function ($configArray = []) {
|
||||
});
|
||||
|
||||
// Miscellaneous Classes
|
||||
$container->set('auth', static function($container) {
|
||||
return new Kitsu\Auth($container);
|
||||
});
|
||||
$container->set('url-generator', static function($container) {
|
||||
return new UrlGenerator($container);
|
||||
});
|
||||
$container->set('auth', fn ($container) => new Kitsu\Auth($container));
|
||||
$container->set('url-generator', fn ($container) => new UrlGenerator($container));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Dispatcher
|
||||
// -------------------------------------------------------------------------
|
||||
$container->set('dispatcher', static function($container) {
|
||||
return new Dispatcher($container);
|
||||
});
|
||||
$container->set('dispatcher', fn ($container) => new Dispatcher($container));
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
<main>
|
||||
<h2>Add Anime to your List</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
|
||||
<section>
|
||||
<div class="cssload-loader" hidden="hidden">
|
||||
<div class="cssload-inner cssload-one"></div>
|
||||
|
@ -75,20 +75,20 @@
|
||||
<?php foreach($item['anime']['streaming_links'] as $link): ?>
|
||||
<?php if ($link['meta']['link'] !== FALSE): ?>
|
||||
<a href="<?= $link['link'] ?>" title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
|
||||
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
|
||||
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||
'class' => 'streaming-logo',
|
||||
'width' => 50,
|
||||
'height' => 50,
|
||||
'alt' => "{$link['meta']['name']} logo",
|
||||
]); ?>
|
||||
]) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
|
||||
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||
'class' => 'streaming-logo',
|
||||
'width' => 50,
|
||||
'height' => 50,
|
||||
'alt' => "{$link['meta']['name']} logo",
|
||||
]); ?>
|
||||
]) ?>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
|
11
app/views/collection/_media-list.php
Normal file
11
app/views/collection/_media-list.php
Normal file
@ -0,0 +1,11 @@
|
||||
<select name="media_id[]" id="media_id" multiple size="13">
|
||||
<?php foreach ($media_items as $group => $items): ?>
|
||||
<optgroup label='<?= $group ?>'>
|
||||
<?php foreach ($items as $id => $name): ?>
|
||||
<option <?= in_array($id, ($item['media_id'] ?? []), FALSE) ? 'selected="selected"' : '' ?> value="<?= $id ?>">
|
||||
<?= $name ?>
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
</optgroup>
|
||||
<?php endforeach ?>
|
||||
</select>
|
@ -2,6 +2,7 @@
|
||||
<main>
|
||||
<h2>Add <?= ucfirst($collection_type) ?> to your Collection</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
|
||||
<section>
|
||||
<div class="cssload-loader" hidden="hidden">
|
||||
<div class="cssload-inner cssload-one"></div>
|
||||
@ -16,13 +17,9 @@
|
||||
<table class="invisible form">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="media_id">Media</label></td>
|
||||
<td>
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
<td class="align-right"><label for="media_id">Media</label></td>
|
||||
<td class='align-left'>
|
||||
<?php include '_media-list.php' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -24,11 +24,7 @@
|
||||
<tr>
|
||||
<td class="align-right"><label for="media_id">Media</label></td>
|
||||
<td class="align-left">
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option <?= $item['media_id'] === $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
<?php include '_media-list.php' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -12,11 +12,11 @@
|
||||
</section>
|
||||
<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-authed.min.js') ?>"></script>
|
||||
<script type="module" src="<?= $urlGenerator->assetUrl('js/src/index-authed.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/scripts.min.js') ?>"></script>
|
||||
<script type="module" src="<?= $urlGenerator->assetUrl('js/src/index.js') ?>"></script>
|
||||
<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>
|
||||
</html>
|
||||
|
@ -6,11 +6,7 @@
|
||||
<meta http-equiv="cache-control" content="no-store" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=1" />
|
||||
<?php if ($config->get('theme') !== 'auto'): ?>
|
||||
<link rel="stylesheet" href="<?= $urlGenerator->assetUrl('css/app.min.css') ?>" />
|
||||
<?php elseif ($config->get('theme') === 'auto'): ?>
|
||||
<link rel="stylesheet" href="<?= $urlGenerator->assetUrl('css/dark-auto.min.css') ?>" />
|
||||
<?php endif ?>
|
||||
<link rel="stylesheet" href="<?= $urlGenerator->assetUrl('css/' . $config->get('theme') . '.min.css') ?>" />
|
||||
<link rel="<?= $config->get('theme') === 'dark' ? '' : 'alternate ' ?>stylesheet" title="Dark Theme" href="<?= $urlGenerator->assetUrl('css/dark.min.css') ?>" />
|
||||
<link rel="icon" href="<?= $urlGenerator->assetUrl('images/icons/favicon.ico') ?>" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-57x57.png') ?>">
|
||||
|
49
app/views/history.php
Normal file
49
app/views/history.php
Normal file
@ -0,0 +1,49 @@
|
||||
<main class="details fixed">
|
||||
<?php if (empty($items)): ?>
|
||||
<h3>No recent history.</h3>
|
||||
<?php else: ?>
|
||||
<section>
|
||||
<?php foreach ($items as $name => $item): ?>
|
||||
<article class="flex flex-no-wrap flex-justify-start">
|
||||
<section class="flex-self-center history-img">
|
||||
<a href="<?= $item['url'] ?>">
|
||||
<?= $helper->picture(
|
||||
$item['coverImg'],
|
||||
'jpg',
|
||||
['width' => '110px', 'height' => '156px'],
|
||||
['width' => '110px', 'height' => '156px']
|
||||
) ?>
|
||||
</a>
|
||||
</section>
|
||||
<section class="flex-self-center">
|
||||
<?= $helper->a($item['url'], $item['title']) ?>
|
||||
<br />
|
||||
<br />
|
||||
<?= $item['action'] ?>
|
||||
<br />
|
||||
<small>
|
||||
<?php if ( ! empty($item['dateRange'])):
|
||||
[$startDate, $endDate] = array_map(
|
||||
fn ($date) => $date->format('l, F d'),
|
||||
$item['dateRange']
|
||||
);
|
||||
[$startTime, $endTime] = array_map(
|
||||
fn ($date) => $date->format('h:i:s A'),
|
||||
$item['dateRange']
|
||||
);
|
||||
?>
|
||||
<?php if ($startDate === $endDate): ?>
|
||||
<?= "{$startDate}, {$startTime} – {$endTime}" ?>
|
||||
<?php else: ?>
|
||||
<?= "{$startDate} {$startTime} – {$endDate} {$endTime}" ?>
|
||||
<?php endif ?>
|
||||
<?php else: ?>
|
||||
<?= $item['updated']->format('l, F d h:i:s A') ?>
|
||||
<?php endif ?>
|
||||
</small>
|
||||
</section>
|
||||
</article>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
</main>
|
6
app/views/js-warning.php
Normal file
6
app/views/js-warning.php
Normal file
@ -0,0 +1,6 @@
|
||||
<noscript>
|
||||
<div class="message error">
|
||||
<span class="icon"></span>
|
||||
This feature requires Javascript to function :(
|
||||
</div>
|
||||
</noscript>
|
@ -79,10 +79,12 @@ $hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
|
||||
<nav>
|
||||
<?php if ($container->get('util')->isViewPage() && ($hasAnime || $hasManga)): ?>
|
||||
<?= $helper->menu($menu_name) ?>
|
||||
<?php if (stripos($_SERVER['REQUEST_URI'], 'history') === FALSE): ?>
|
||||
<br />
|
||||
<ul>
|
||||
<li class="<?= Util::isNotSelected('list', $lastSegment) ?>"><a href="<?= $urlGenerator->url($route_path) ?>">Cover View</a></li>
|
||||
<li class="<?= Util::isSelected('list', $lastSegment) ?>"><a href="<?= $urlGenerator->url("{$route_path}/list") ?>">List View</a></li>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</nav>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<main>
|
||||
<h2>Add Manga to your List</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
|
||||
<section>
|
||||
<div class="cssload-loader" hidden="hidden">
|
||||
<div class="cssload-inner cssload-one"></div>
|
||||
|
@ -3,13 +3,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
"config": {
|
||||
"lock": false,
|
||||
"platform": {
|
||||
"php": "7.3"
|
||||
"php": "7.4"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
@ -39,7 +39,7 @@
|
||||
"aura/router": "^3.0",
|
||||
"aura/session": "^2.0",
|
||||
"aviat/banker": "^2.0.0",
|
||||
"aviat/query": "^2.5.1",
|
||||
"aviat/query": "^3.0.0",
|
||||
"danielstjules/stringy": "^3.1.0",
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*",
|
||||
@ -50,7 +50,7 @@
|
||||
"laminas/laminas-httphandlerrunner": "^1.0",
|
||||
"maximebf/consolekit": "^1.0",
|
||||
"monolog/monolog": "^2.0.1",
|
||||
"php": "^7.3",
|
||||
"php": ">=7.4",
|
||||
"psr/container": "~1.0",
|
||||
"psr/http-message": "~1.0",
|
||||
"psr/log": "~1.0",
|
||||
@ -60,14 +60,14 @@
|
||||
"consolidation/robo": "^2.0.0",
|
||||
"filp/whoops": "^2.1",
|
||||
"pdepend/pdepend": "^2.2",
|
||||
"phploc/phploc": "^5.0",
|
||||
"phploc/phploc": "^6.0.2",
|
||||
"phpmd/phpmd": "^2.8",
|
||||
"phpstan/phpstan": "^0.12.0",
|
||||
"phpunit/phpunit": "^8.4.3",
|
||||
"phpunit/phpunit": "^9.1.1",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"robmorgan/phinx": "^0.10.6",
|
||||
"sebastian/phpcpd": "^4.1.0",
|
||||
"spatie/phpunit-snapshot-assertions": "^2.2.1",
|
||||
"sebastian/phpcpd": "^5.0.2",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.1.0",
|
||||
"squizlabs/php_codesniffer": "^3.2.2",
|
||||
"symfony/var-dumper": "^5",
|
||||
"theseer/phpdox": "*"
|
||||
|
4
console
4
console
@ -15,14 +15,12 @@ $_SERVER['HTTP_HOST'] = 'localhost';
|
||||
try
|
||||
{
|
||||
(new Console([
|
||||
'cache:clear' => Command\CacheClear::class,
|
||||
'cache:refresh' => Command\CachePrime::class,
|
||||
'clear:cache' => Command\CacheClear::class,
|
||||
'clear:thumbnails' => Command\ClearThumbnails::class,
|
||||
'refresh:cache' => Command\CachePrime::class,
|
||||
'refresh:thumbnails' => Command\UpdateThumbnails::class,
|
||||
'regenerate-thumbnails' => Command\UpdateThumbnails::class,
|
||||
'lists:sync' => Command\SyncLists::class,
|
||||
'sync:lists' => Command\SyncLists::class
|
||||
]))->run();
|
||||
}
|
||||
catch (\Exception $e)
|
||||
|
68
frontEndSrc/build-js.js
Normal file
68
frontEndSrc/build-js.js
Normal file
@ -0,0 +1,68 @@
|
||||
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,
|
||||
];
|
70
frontEndSrc/css.js
Normal file
70
frontEndSrc/css.js
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Script for optimizing css
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const postcss = require('postcss');
|
||||
const atImport = require('postcss-import');
|
||||
const cssNext = require('postcss-preset-env');
|
||||
const cssNano = require('cssnano');
|
||||
|
||||
const lightCss = fs.readFileSync('css/light.css', 'utf-8');
|
||||
const darkCss = fs.readFileSync('css/src/dark-override.css', 'utf-8');
|
||||
const fullDarkCss = fs.readFileSync('css/dark.css', 'utf-8');
|
||||
|
||||
const minOptions = {
|
||||
autoprefixer: false,
|
||||
colormin: false,
|
||||
minifyFontValues: false,
|
||||
options: {
|
||||
sourcemap: false
|
||||
}
|
||||
};
|
||||
|
||||
const processOptions = {
|
||||
browser: '> 0.5%',
|
||||
features: {
|
||||
'custom-properties': true,
|
||||
},
|
||||
stage: 0,
|
||||
};
|
||||
|
||||
try {
|
||||
(async () => {
|
||||
// Basic theme
|
||||
const lightMin = await postcss()
|
||||
.use(atImport())
|
||||
.use(cssNext(processOptions))
|
||||
.use(cssNano(minOptions))
|
||||
.process(lightCss, {
|
||||
from: 'css/light.css',
|
||||
to: '/public/css/light.min.css',
|
||||
}).catch(console.error);
|
||||
fs.writeFileSync('../public/css/light.min.css', lightMin.css);
|
||||
|
||||
// Dark theme
|
||||
const darkFullMin = await postcss()
|
||||
.use(atImport())
|
||||
.use(cssNext(processOptions))
|
||||
.use(cssNano(minOptions))
|
||||
.process(fullDarkCss, {
|
||||
from: 'css/dark.css',
|
||||
to: '/public/css/dark.min.css',
|
||||
});
|
||||
fs.writeFileSync('../public/css/dark.min.css', darkFullMin.css);
|
||||
|
||||
// Dark override
|
||||
const darkMin = await postcss()
|
||||
.use(atImport())
|
||||
.use(cssNext(processOptions))
|
||||
.use(cssNano(minOptions))
|
||||
.process(darkCss, {
|
||||
from: 'css/dark-override.css',
|
||||
to: '/public/css/dark.min.css',
|
||||
}).catch(console.error);
|
||||
const autoDarkCss = `${lightMin} @media (prefers-color-scheme: dark) { ${darkMin.css} }`
|
||||
fs.writeFileSync('../public/css/auto.min.css', autoDarkCss)
|
||||
|
||||
})();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
3
frontEndSrc/css/auto.css
Normal file
3
frontEndSrc/css/auto.css
Normal file
@ -0,0 +1,3 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@import "src/dark-override.css";
|
||||
}
|
5
frontEndSrc/css/dark.css
Normal file
5
frontEndSrc/css/dark.css
Normal file
@ -0,0 +1,5 @@
|
||||
@import "src/-marx-.css";
|
||||
@import "src/general.css";
|
||||
@import "src/components.css";
|
||||
@import "src/responsive.css";
|
||||
@import "src/dark-override.css";
|
4
frontEndSrc/css/light.css
Normal file
4
frontEndSrc/css/light.css
Normal file
@ -0,0 +1,4 @@
|
||||
@import "src/-marx-.css";
|
||||
@import "src/general.css";
|
||||
@import "src/components.css";
|
||||
@import "src/responsive.css";
|
@ -87,6 +87,10 @@ tbody > tr:nth-child(odd) {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
color: var(--link-hover-color)
|
||||
}
|
||||
@ -888,6 +892,11 @@ aside picture, aside img {
|
||||
filter: drop-shadow(0 -1px 4px #fff);
|
||||
}
|
||||
|
||||
.history-img {
|
||||
width: 110px;
|
||||
height: 156px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
Settings Form
|
||||
-----------------------------------------------------------------------------*/
|
@ -3,9 +3,9 @@
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const matches = (elm, selector) => {
|
||||
let matches = (elm.document || elm.ownerDocument).querySelectorAll(selector),
|
||||
i = matches.length;
|
||||
while (--i >= 0 && matches.item(i) !== elm) {};
|
||||
let m = (elm.document || elm.ownerDocument).querySelectorAll(selector);
|
||||
let i = matches.length;
|
||||
while (--i >= 0 && m.item(i) !== elm) {};
|
||||
return i > -1;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import _ from './base/AnimeClient.js'
|
||||
import _ from './anime-client.js'
|
||||
import { renderAnimeSearchResults } from './template-helpers.js'
|
||||
|
||||
const search = (query) => {
|
@ -1,4 +1,4 @@
|
||||
import './base/events.js';
|
||||
import './events.js';
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
@ -1,31 +1,61 @@
|
||||
import _ from './AnimeClient.js';
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
// Close event for messages
|
||||
_.on('header', 'click', '.message', (e) => {
|
||||
_.hide(e.target);
|
||||
});
|
||||
import _ from './anime-client.js';
|
||||
|
||||
// Confirm deleting of list or library items
|
||||
_.on('form.js-delete', 'submit', (event) => {
|
||||
// ----------------------------------------------------------------------------
|
||||
// Event subscriptions
|
||||
// ----------------------------------------------------------------------------
|
||||
_.on('header', 'click', '.message', hide);
|
||||
_.on('form.js-delete', 'submit', confirmDelete);
|
||||
_.on('.js-clear-cache', 'click', clearAPICache);
|
||||
_.on('.vertical-tabs input', 'change', scrollToSection);
|
||||
_.on('.media-filter', 'input', filterMedia);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Handler functions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Hide the html element attached to the event
|
||||
*
|
||||
* @param event
|
||||
* @return void
|
||||
*/
|
||||
function hide (event) {
|
||||
_.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
|
||||
_.on('.js-clear-cache', 'click', () => {
|
||||
/**
|
||||
* Clear the API cache, and show a message if the cache is cleared
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function clearAPICache () {
|
||||
_.get('/cache_purge', () => {
|
||||
_.showMessage('success', 'Successfully purged api cache');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Alleviate some page jumping
|
||||
_.on('.vertical-tabs input', 'change', (event) => {
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@ -35,10 +65,15 @@ _.on('.js-clear-cache', 'click', () => {
|
||||
top,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Filter the current page (cover view)
|
||||
_.on('.media-filter', 'input', (event) => {
|
||||
/**
|
||||
* Filter an anime or manga list
|
||||
*
|
||||
* @param event
|
||||
* @return void
|
||||
*/
|
||||
function filterMedia (event) {
|
||||
const rawFilter = event.target.value;
|
||||
const filter = new RegExp(rawFilter, 'i');
|
||||
|
||||
@ -72,4 +107,4 @@ _.on('.media-filter', 'input', (event) => {
|
||||
_.show('article.media');
|
||||
_.show('table.media-wrap tbody tr');
|
||||
}
|
||||
});
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import './index.js';
|
||||
import './anon.js';
|
||||
|
||||
import './anime.js';
|
||||
import './manga.js';
|
@ -1,4 +1,4 @@
|
||||
import _ from './base/AnimeClient.js'
|
||||
import _ from './anime-client.js'
|
||||
import { renderMangaSearchResults } from './template-helpers.js'
|
||||
|
||||
const search = (query) => {
|
@ -1,4 +1,4 @@
|
||||
import _ from './base/AnimeClient.js';
|
||||
import _ from './anime-client.js';
|
||||
|
||||
// Click on hidden MAL checkbox so
|
||||
// that MAL id is passed
|
||||
@ -12,9 +12,7 @@ export function renderAnimeSearchResults (data) {
|
||||
|
||||
data.forEach(x => {
|
||||
const item = x.attributes;
|
||||
const titles = item.titles.reduce((prev, current) => {
|
||||
return prev + `${current}<br />`;
|
||||
}, []);
|
||||
const titles = item.titles.join('<br />');
|
||||
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
@ -27,7 +25,6 @@ export function renderAnimeSearchResults (data) {
|
||||
<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>
|
||||
@ -53,9 +50,7 @@ export function renderMangaSearchResults (data) {
|
||||
|
||||
data.forEach(x => {
|
||||
const item = x.attributes;
|
||||
const titles = item.titles.reduce((prev, current) => {
|
||||
return prev + `${current}<br />`;
|
||||
}, []);
|
||||
const titles = item.titles.join('<br />');
|
||||
|
||||
results.push(`
|
||||
<article class="media search">
|
21
frontEndSrc/package.json
Normal file
21
frontEndSrc/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "npm run build:css && npm run build:js",
|
||||
"build:css": "node ./css.js",
|
||||
"build:js": "rollup -c ./build-js.js",
|
||||
"watch:css": "watch 'npm run build:css' --filter=./cssfilter.js",
|
||||
"watch:js": "watch 'npm run build:js' ./js",
|
||||
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ampproject/rollup-plugin-closure-compiler": "^0.25.2",
|
||||
"concurrently": "^5.1.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"postcss": "^7.0.27",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"rollup": "^2.4.0",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hummingbird AnimeClient Front-end Testsuite</title>
|
||||
<link rel="stylesheet" href="lib/mocha.css">
|
||||
<link rel="stylesheet" href='lib/mocha.css'>
|
||||
</head>
|
||||
<body>
|
||||
<section id="parentTest">
|
||||
@ -20,8 +20,8 @@
|
||||
</ul>
|
||||
<ul id="mocha-report"></ul>
|
||||
</div>
|
||||
<script src="../js/src/base/classList.js"></script>
|
||||
<script src="lib/testBundle.js"></script>
|
||||
<script src='../js/base/class-list.js'></script>
|
||||
<script src='lib/testBundle.js'></script>
|
||||
|
||||
<script>
|
||||
var expect = chai.expect;
|
||||
@ -29,11 +29,11 @@
|
||||
</script>
|
||||
|
||||
<!-- include source files here... -->
|
||||
<script src="../js/src/base/AnimeClient.js"></script>
|
||||
<script src='../js/anime-client.js'></script>
|
||||
|
||||
<!-- include test files here... -->
|
||||
<script src="tests/AnimeClient.js"></script>
|
||||
<script src="tests/ajax.js"></script>
|
||||
<script src='tests/AnimeClient.js'></script>
|
||||
<script src='tests/ajax.js'></script>
|
||||
|
||||
<script>
|
||||
mocha.checkLeaks();
|
File diff suppressed because it is too large
Load Diff
33
index.php
33
index.php
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -24,17 +24,10 @@ use function Aviat\Ion\_dir;
|
||||
|
||||
setlocale(LC_CTYPE, 'en_US');
|
||||
|
||||
// Work around the silly timezone error
|
||||
$timezone = ini_get('date.timezone');
|
||||
if ($timezone === '' || $timezone === FALSE)
|
||||
{
|
||||
ini_set('date.timezone', 'GMT');
|
||||
}
|
||||
|
||||
// Load composer autoloader
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// if (array_key_exists('ENV', $_ENV) && $_ENV['ENV'] === 'development')
|
||||
if (array_key_exists('ENV', $_SERVER) && $_SERVER['ENV'] === 'development')
|
||||
{
|
||||
$whoops = new Run;
|
||||
$whoops->pushHandler(new PrettyPageHandler);
|
||||
@ -61,7 +54,25 @@ $overrideConfig = file_exists($overrideFile)
|
||||
|
||||
$configArray = array_replace_recursive($baseConfig, $config, $overrideConfig);
|
||||
|
||||
$checkedConfig = (new ConfigType($configArray))->toArray();
|
||||
$checkedConfig = ConfigType::check($configArray);
|
||||
|
||||
// Set the timezone for date display
|
||||
// First look in app config, then PHP config, and at last
|
||||
// resort, just set to UTC.
|
||||
$timezone = ini_get('date.timezone');
|
||||
if (array_key_exists('timezone', $checkedConfig) && ! empty($checkedConfig['timezone']))
|
||||
{
|
||||
date_default_timezone_set($checkedConfig['timezone']);
|
||||
}
|
||||
else if ($timezone !== '')
|
||||
{
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
else
|
||||
{
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
$container = $di($checkedConfig);
|
||||
|
||||
// Unset 'constants'
|
||||
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
class AddMangaCollectionTables extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* Write your reversible migrations using this method.
|
||||
*
|
||||
* More information on writing migrations is available here:
|
||||
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||
*
|
||||
* The following commands can be used in this method and Phinx will
|
||||
* automatically reverse them when rolling back:
|
||||
*
|
||||
* createTable
|
||||
* renameTable
|
||||
* addColumn
|
||||
* renameColumn
|
||||
* addIndex
|
||||
* addForeignKey
|
||||
*
|
||||
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||
* with the Table class.
|
||||
*/
|
||||
public function change()
|
||||
{
|
||||
// Create manga_set table
|
||||
$manga_set = $this->table('manga_set', ['id' => FALSE, 'primary_key' => ['hummingbird_id']]);
|
||||
$manga_set->addColumn('hummingbird_id', 'biginteger')
|
||||
->addColumn('slug', 'string', ['comment' => "URL slug used for image caching and generating links"])
|
||||
->addColumn('title', 'string')
|
||||
->addColumn('alternate_title', 'string', ['null' => TRUE])
|
||||
->addColumn('media_id', 'integer', ['default' => 3, 'null' => TRUE])
|
||||
->addColumn('show_type', 'string', ['default' => 'TV', 'null' => TRUE, 'comment' => "TV Series/OVA/etc"])
|
||||
->addColumn('age_rating', 'string', ['default' => 'PG13', 'null' => TRUE])
|
||||
->addColumn('cover_image', 'string', ['null' => TRUE])
|
||||
->addColumn('episode_count', 'integer', ['null' => TRUE])
|
||||
->addColumn('episode_length', 'integer', ['null' => TRUE])
|
||||
->addColumn('notes', 'text', ['null' => TRUE])
|
||||
->addForeignKey('media_id', 'media', 'id')
|
||||
->create();
|
||||
|
||||
// Create genre_manga_set_link table
|
||||
$genre_manga_set_link = $this->table('genre_manga_set_link', ['id' => FALSE, 'primary_key' => ['hummingbird_id', 'genre_id']]);
|
||||
$genre_manga_set_link->addColumn('hummingbird_id', 'biginteger')
|
||||
->addColumn('genre_id', 'integer')
|
||||
->addForeignKey('hummingbird_id', 'manga_set', 'hummingbird_id')
|
||||
->addForeignKey('genre_id', 'genres', 'id')
|
||||
->create();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
class ReorganizeAnimeCollectionMedia extends AbstractMigration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
// Create the new link table
|
||||
if ( ! $this->hasTable('anime_set_media_link'))
|
||||
{
|
||||
$newLinkTable = $this->table('anime_set_media_link', [
|
||||
'id' => FALSE,
|
||||
'primary_key' => ['hummingbird_id', 'media_id']
|
||||
]);
|
||||
|
||||
$newLinkTable->addColumn('hummingbird_id', 'biginteger')
|
||||
->addColumn('media_id', 'biginteger')
|
||||
->addForeignKey('media_id', 'media', 'id')
|
||||
->addForeignKey('hummingbird_id', 'anime_set', 'hummingbird_id')
|
||||
->create();
|
||||
}
|
||||
|
||||
// Get the old link entries
|
||||
$insertRows = [];
|
||||
$rows = ($this->table('anime_set')->hasColumn('media_id'))
|
||||
? $this->fetchAll('SELECT hummingbird_id, media_id from anime_set')
|
||||
: [];
|
||||
|
||||
// Filter the numeric keys out of the row results
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$keys = array_keys($row);
|
||||
foreach ($keys as $k)
|
||||
{
|
||||
if (is_numeric($k))
|
||||
{
|
||||
unset($row[$k]);
|
||||
}
|
||||
}
|
||||
$insertRows[] = $row;
|
||||
}
|
||||
|
||||
// And put them in the new table
|
||||
$linkTable = $this->table('anime_set_media_link');
|
||||
$linkTable->insert($insertRows)->save();
|
||||
|
||||
// Get the rows where you have the combined media type (DVD & Bluray)
|
||||
// and replace those rows with the individual entries
|
||||
$linkRows = $this->fetchAll('SELECT hummingbird_id FROM anime_set_media_link WHERE media_id=1');
|
||||
$insertRows = [];
|
||||
foreach ($linkRows as $row)
|
||||
{
|
||||
$insertRows[] = [
|
||||
'hummingbird_id' => $row['hummingbird_id'],
|
||||
'media_id' => 2,
|
||||
];
|
||||
$insertRows[] = [
|
||||
'hummingbird_id' => $row['hummingbird_id'],
|
||||
'media_id' => 3,
|
||||
];
|
||||
}
|
||||
$linkTable->insert($insertRows)->save();
|
||||
|
||||
// Finally, delete the old combined media type rows
|
||||
$this->execute('DELETE FROM anime_set_media_link WHERE media_id=1');
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if ($this->hasTable('anime_set_media_link'))
|
||||
{
|
||||
$this->table('anime_set_media_link')->drop()->save();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
class AnimeCollectionRefactorCleanup extends AbstractMigration
|
||||
{
|
||||
protected array $newMediaTypes = [
|
||||
'LaserDisc',
|
||||
'VHS',
|
||||
'Digital',
|
||||
'Video CD',
|
||||
'Betamax',
|
||||
'UMD',
|
||||
'Other',
|
||||
];
|
||||
|
||||
public function up()
|
||||
{
|
||||
// Add some new media types
|
||||
$moreMediaTypes = [];
|
||||
foreach ($this->newMediaTypes as $id => $medium)
|
||||
{
|
||||
$moreMediaTypes[] = [
|
||||
'id' => $id + 5,
|
||||
'type' => $medium,
|
||||
];
|
||||
}
|
||||
$this->table('media')->insert($moreMediaTypes)->save();
|
||||
|
||||
// Remove foreign key and media_id column from anime_set
|
||||
$animeSet = $this->table('anime_set');
|
||||
if ($animeSet->hasColumn('media_id'))
|
||||
{
|
||||
$animeSet->dropForeignKey('media_id')->save();
|
||||
$animeSet->removeColumn('media_id')->save();
|
||||
}
|
||||
|
||||
// Cleanup existing media types a bit
|
||||
$this->execute("UPDATE media SET type='Bootleg' WHERE id=4");
|
||||
$this->execute('DELETE FROM media WHERE id=1');
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
// Restore the original values for existing media
|
||||
$this->execute("INSERT INTO media (id, type) VALUES (1, 'DVD & Blu-ray')");
|
||||
$this->execute("UPDATE media SET type='Bootleg DVD' WHERE id=4");
|
||||
|
||||
// Remove the new media types
|
||||
$values = array_map(fn ($medium) => "'{$medium}'", $this->newMediaTypes);
|
||||
$valueList = implode(',', $values);
|
||||
$this->execute("DELETE FROM media WHERE type IN ({$valueList})");
|
||||
}
|
||||
}
|
1
public/css/app.min.css
vendored
1
public/css/app.min.css
vendored
File diff suppressed because one or more lines are too long
1
public/css/auto.min.css
vendored
Normal file
1
public/css/auto.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/css/dark-auto.min.css
vendored
1
public/css/dark-auto.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/dark.min.css
vendored
2
public/css/dark.min.css
vendored
File diff suppressed because one or more lines are too long
1
public/css/light.min.css
vendored
Normal file
1
public/css/light.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +0,0 @@
|
||||
@import "./marx.css";
|
||||
@import "./general.css";
|
||||
@import "./components.css";
|
||||
@import "./responsive.css";
|
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);
|
||||
});
|
||||
}
|
716
public/es/scripts.js
Normal file
716
public/es/scripts.js
Normal file
@ -0,0 +1,716 @@
|
||||
// -------------------------------------------------------------------------
|
||||
// ! 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.join('<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.join('<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();
|
||||
}
|
||||
});
|
||||
});
|
14
public/js/anon.min.js
vendored
Normal file
14
public/js/anon.min.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
(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)};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
|
1
public/js/anon.min.js.map
Normal file
1
public/js/anon.min.js.map
Normal file
File diff suppressed because one or more lines are too long
26
public/js/scripts-authed.min.js
vendored
26
public/js/scripts-authed.min.js
vendored
@ -1,26 +0,0 @@
|
||||
(function(){var matches=function(elm,selector){var matches=(elm.document||elm.ownerDocument).querySelectorAll(selector),i=matches.length;while(--i>=0&&matches.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)};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",function(e){AnimeClient.hide(e.target)});AnimeClient.on("form.js-delete","submit",function(event){var proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();
|
||||
event.stopPropagation()}});AnimeClient.on(".js-clear-cache","click",function(){AnimeClient.get("/cache_purge",function(){AnimeClient.showMessage("success","Successfully purged api cache")})});AnimeClient.on(".vertical-tabs input","change",function(event){var el=event.currentTarget.parentElement;var rect=el.getBoundingClientRect();var top=rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})});AnimeClient.on(".media-filter","input",function(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)});AnimeClient.on("main","change",
|
||||
".big-check",function(e){var id=e.target.id;document.getElementById("mal_"+id).checked=true});function renderAnimeSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;var titles=item.titles.reduce(function(prev,current){return prev+(current+"<br />")},[]);results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" class="mal-check" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+
|
||||
item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/anime/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+
|
||||
titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/anime/details/'+item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});return results.join("")}function renderMangaSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;var titles=item.titles.reduce(function(prev,
|
||||
current){return prev+(current+"<br />")},[]);results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+
|
||||
x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/manga/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/manga/details/'+item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});
|
||||
return results.join("")}var search=function(query){AnimeClient.show(".cssload-loader");AnimeClient.get(AnimeClient.url("/anime-collection/search"),{query:query},function(searchResults,status){searchResults=JSON.parse(searchResults);AnimeClient.hide(".cssload-loader");AnimeClient.$("#series-list")[0].innerHTML=renderAnimeSearchResults(searchResults.data)})};if(AnimeClient.hasElement(".anime #search"))AnimeClient.on("#search","input",AnimeClient.throttle(250,function(e){var query=encodeURIComponent(e.target.value);
|
||||
if(query==="")return;search(query)}));AnimeClient.on("body.anime.list","click",".plus-one",function(e){var parentSel=AnimeClient.closestParent(e.target,"article");var watchedCount=parseInt(AnimeClient.$(".completed_number",parentSel)[0].textContent,10)||0;var totalCount=parseInt(AnimeClient.$(".total_number",parentSel)[0].textContent,10);var title=AnimeClient.$(".name a",parentSel)[0].textContent;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:watchedCount+1}};
|
||||
if(isNaN(watchedCount)||watchedCount===0)data.data.status="current";if(!isNaN(watchedCount)&&watchedCount+1===totalCount)data.data.status="completed";AnimeClient.show("#loading-shadow");AnimeClient.ajax(AnimeClient.url("/anime/increment"),{data:data,dataType:"json",type:"POST",success:function(res){var 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:function(){AnimeClient.hide("#loading-shadow");AnimeClient.showMessage("error","Failed to update "+title+". ");AnimeClient.scrollToTop()}})});var search$1=function(query){AnimeClient.show(".cssload-loader");AnimeClient.get(AnimeClient.url("/manga/search"),
|
||||
{query:query},function(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,function(e){var query=encodeURIComponent(e.target.value);if(query==="")return;search$1(query)}));AnimeClient.on(".manga.list","click",".edit-buttons button",function(e){var thisSel=e.target;
|
||||
var parentSel=AnimeClient.closestParent(e.target,"article");var type=thisSel.classList.contains("plus-one-chapter")?"chapter":"volume";var completed=parseInt(AnimeClient.$("."+type+"s_read",parentSel)[0].textContent,10)||0;var total=parseInt(AnimeClient.$("."+type+"_count",parentSel)[0].textContent,10);var mangaName=AnimeClient.$(".name",parentSel)[0].textContent;if(isNaN(completed))completed=0;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:completed}};if(isNaN(completed)||
|
||||
completed===0)data.data.status="current";if(!isNaN(completed)&&completed+1===total)data.data.status="completed";data.data.progress=++completed;AnimeClient.show("#loading-shadow");AnimeClient.ajax(AnimeClient.url("/manga/increment"),{data:data,dataType:"json",type:"POST",mimeType:"application/json",success:function(){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:function(){AnimeClient.hide("#loading-shadow");AnimeClient.showMessage("error","Failed to update "+mangaName);AnimeClient.scrollToTop()}})})})();
|
||||
//# sourceMappingURL=scripts-authed.min.js.map
|
File diff suppressed because one or more lines are too long
23
public/js/scripts.min.js
vendored
23
public/js/scripts.min.js
vendored
@ -1,4 +1,4 @@
|
||||
(function(){var matches=function(elm,selector){var matches=(elm.document||elm.ownerDocument).querySelectorAll(selector),i=matches.length;while(--i>=0&&matches.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]));
|
||||
(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-
|
||||
@ -6,8 +6,21 @@ undefined)return current.closest(parentSelector);while(current!==document.docume
|
||||
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)};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",function(e){AnimeClient.hide(e.target)});AnimeClient.on("form.js-delete","submit",function(event){var proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();
|
||||
event.stopPropagation()}});AnimeClient.on(".js-clear-cache","click",function(){AnimeClient.get("/cache_purge",function(){AnimeClient.showMessage("success","Successfully purged api cache")})});AnimeClient.on(".vertical-tabs input","change",function(event){var el=event.currentTarget.parentElement;var rect=el.getBoundingClientRect();var top=rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})});AnimeClient.on(".media-filter","input",function(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)})})();
|
||||
"GET")request.send(null);else request.send(config.data)};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)});AnimeClient.on("main","change",".big-check",function(e){var id=e.target.id;document.getElementById("mal_"+id).checked=true});function renderAnimeSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;var titles=item.titles.join("<br />");results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" class="mal-check" id="mal_'+
|
||||
item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/anime/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+
|
||||
item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/anime/details/'+item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});return results.join("")}function renderMangaSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;
|
||||
var titles=item.titles.join("<br />");results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+
|
||||
x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/manga/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/manga/details/'+item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});
|
||||
return results.join("")}var search=function(query){AnimeClient.show(".cssload-loader");AnimeClient.get(AnimeClient.url("/anime-collection/search"),{query:query},function(searchResults,status){searchResults=JSON.parse(searchResults);AnimeClient.hide(".cssload-loader");AnimeClient.$("#series-list")[0].innerHTML=renderAnimeSearchResults(searchResults.data)})};if(AnimeClient.hasElement(".anime #search"))AnimeClient.on("#search","input",AnimeClient.throttle(250,function(e){var query=encodeURIComponent(e.target.value);
|
||||
if(query==="")return;search(query)}));AnimeClient.on("body.anime.list","click",".plus-one",function(e){var parentSel=AnimeClient.closestParent(e.target,"article");var watchedCount=parseInt(AnimeClient.$(".completed_number",parentSel)[0].textContent,10)||0;var totalCount=parseInt(AnimeClient.$(".total_number",parentSel)[0].textContent,10);var title=AnimeClient.$(".name a",parentSel)[0].textContent;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:watchedCount+1}};
|
||||
if(isNaN(watchedCount)||watchedCount===0)data.data.status="current";if(!isNaN(watchedCount)&&watchedCount+1===totalCount)data.data.status="completed";AnimeClient.show("#loading-shadow");AnimeClient.ajax(AnimeClient.url("/anime/increment"),{data:data,dataType:"json",type:"POST",success:function(res){var 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:function(){AnimeClient.hide("#loading-shadow");AnimeClient.showMessage("error","Failed to update "+title+". ");AnimeClient.scrollToTop()}})});var search$1=function(query){AnimeClient.show(".cssload-loader");AnimeClient.get(AnimeClient.url("/manga/search"),
|
||||
{query:query},function(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,function(e){var query=encodeURIComponent(e.target.value);if(query==="")return;search$1(query)}));AnimeClient.on(".manga.list","click",".edit-buttons button",function(e){var thisSel=e.target;
|
||||
var parentSel=AnimeClient.closestParent(e.target,"article");var type=thisSel.classList.contains("plus-one-chapter")?"chapter":"volume";var completed=parseInt(AnimeClient.$("."+type+"s_read",parentSel)[0].textContent,10)||0;var total=parseInt(AnimeClient.$("."+type+"_count",parentSel)[0].textContent,10);var mangaName=AnimeClient.$(".name",parentSel)[0].textContent;if(isNaN(completed))completed=0;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:completed}};if(isNaN(completed)||
|
||||
completed===0)data.data.status="current";if(!isNaN(completed)&&completed+1===total)data.data.status="completed";data.data.progress=++completed;AnimeClient.show("#loading-shadow");AnimeClient.ajax(AnimeClient.url("/manga/increment"),{data:data,dataType:"json",type:"POST",mimeType:"application/json",success:function(){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:function(){AnimeClient.hide("#loading-shadow");AnimeClient.showMessage("error","Failed to update "+mangaName);AnimeClient.scrollToTop()}})})})()
|
||||
//# sourceMappingURL=scripts.min.js.map
|
||||
|
File diff suppressed because one or more lines are too long
2
public/js/tables.min.js
vendored
2
public/js/tables.min.js
vendored
@ -1,4 +1,4 @@
|
||||
(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=
|
||||
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()})();
|
||||
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
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"tables.min.js.map","sources":["src/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,MAAQ,EAET,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,"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;"}
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "npm run build:css && npm run build:js",
|
||||
"build:css": "node ./tools/css.js",
|
||||
"build:js": "rollup -c ./tools/build-js.js",
|
||||
"watch:css": "watch 'npm run build:css' --filter=./tools/cssfilter.js",
|
||||
"watch:js": "watch 'npm run build:js' ./js/src",
|
||||
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ampproject/rollup-plugin-closure-compiler": "^0.9.0",
|
||||
"concurrently": "^4.1.1",
|
||||
"cssnano": "^4.1.10",
|
||||
"postcss": "^7.0.17",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"rollup": "^1.16.7",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
@ -1,44 +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,
|
||||
}
|
||||
|
||||
export default [{
|
||||
input: './js/src/index.js',
|
||||
output: {
|
||||
...defaultOutput,
|
||||
file: './js/scripts.min.js',
|
||||
sourcemapFile: './js/scripts.min.js.map',
|
||||
},
|
||||
plugins,
|
||||
}, {
|
||||
input: './js/src/index-authed.js',
|
||||
output: {
|
||||
...defaultOutput,
|
||||
file: './js/scripts-authed.min.js',
|
||||
sourcemapFile: './js/scripts-authed.min.js.map',
|
||||
},
|
||||
plugins,
|
||||
}, {
|
||||
input: './js/src/base/sort_tables.js',
|
||||
output: {
|
||||
...defaultOutput,
|
||||
file: './js/tables.min.js',
|
||||
sourcemapFile: './js/tables.min.js.map',
|
||||
},
|
||||
plugins,
|
||||
}];
|
||||
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Script for optimizing css
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const postcss = require('postcss');
|
||||
const atImport = require('postcss-import');
|
||||
const cssNext = require('postcss-preset-env');
|
||||
const cssNano = require('cssnano');
|
||||
|
||||
const css = fs.readFileSync('css/src/all.css', 'utf-8');
|
||||
const darkCss = fs.readFileSync('css/src/dark-override.css', 'utf-8');
|
||||
|
||||
const minOptions = {
|
||||
autoprefixer: false,
|
||||
colormin: false,
|
||||
minifyFontValues: false,
|
||||
options: {
|
||||
sourcemap: false
|
||||
}
|
||||
};
|
||||
|
||||
const processOptions = {
|
||||
browser: '> 0.5%',
|
||||
features: {
|
||||
'custom-properties': true,
|
||||
},
|
||||
stage: 0,
|
||||
};
|
||||
|
||||
(async () => {
|
||||
// Basic theme
|
||||
const light = await postcss()
|
||||
.use(atImport())
|
||||
.use(cssNext(processOptions))
|
||||
.use(cssNano(minOptions))
|
||||
.process(css, {
|
||||
from: 'css/src/all.css',
|
||||
to: 'css/app.min.css',
|
||||
});
|
||||
fs.writeFileSync('css/app.min.css', light);
|
||||
|
||||
// Dark theme
|
||||
const dark = await postcss()
|
||||
.use(atImport())
|
||||
.use(cssNext(processOptions))
|
||||
.use(cssNano(minOptions))
|
||||
.process(darkCss, {
|
||||
from: 'css/dark-override.css',
|
||||
to: 'css/dark.min.css',
|
||||
});
|
||||
fs.writeFileSync('css/dark.min.css', dark);
|
||||
|
||||
const autoDarkCss = `${light} @media (prefers-color-scheme: dark) { ${dark} }`
|
||||
fs.writeFileSync('css/dark-auto.min.css', autoDarkCss)
|
||||
|
||||
})();
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -38,37 +38,37 @@ abstract class APIRequestBuilder {
|
||||
* Url prefix for making url requests
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUrl = '';
|
||||
protected string $baseUrl = '';
|
||||
|
||||
/**
|
||||
* Url path of the request
|
||||
* @var string
|
||||
*/
|
||||
protected $path = '';
|
||||
protected string $path = '';
|
||||
|
||||
/**
|
||||
* Query string for the request
|
||||
* @var string
|
||||
*/
|
||||
protected $query = '';
|
||||
protected string $query = '';
|
||||
|
||||
/**
|
||||
* Default request headers
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultHeaders = [];
|
||||
protected array $defaultHeaders = [];
|
||||
|
||||
/**
|
||||
* Valid HTTP request methods
|
||||
* @var array
|
||||
*/
|
||||
protected $validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
||||
protected array $validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
||||
|
||||
/**
|
||||
* The current request
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
protected Request $request;
|
||||
|
||||
/**
|
||||
* Do a basic minimal GET request
|
||||
@ -78,8 +78,10 @@ abstract class APIRequestBuilder {
|
||||
*/
|
||||
public static function simpleRequest(string $uri): Request
|
||||
{
|
||||
return (new Request($uri))
|
||||
->setHeader('User-Agent', USER_AGENT);
|
||||
$request = (new Request($uri));
|
||||
$request->setHeader('User-Agent', USER_AGENT);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -26,20 +26,20 @@ final class AnilistRequestBuilder extends APIRequestBuilder {
|
||||
* The base url for api requests
|
||||
* @var string $base_url
|
||||
*/
|
||||
protected $baseUrl = 'https://graphql.anilist.co';
|
||||
protected string $baseUrl = 'https://graphql.anilist.co';
|
||||
|
||||
/**
|
||||
* Valid HTTP request methods
|
||||
* @var array
|
||||
*/
|
||||
protected $validMethods = ['POST'];
|
||||
protected array $validMethods = ['POST'];
|
||||
|
||||
/**
|
||||
* HTTP headers to send with every request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultHeaders = [
|
||||
protected array $defaultHeaders = [
|
||||
'User-Agent' => USER_AGENT,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -38,20 +38,20 @@ trait AnilistTrait {
|
||||
* The request builder for the Anilist API
|
||||
* @var AnilistRequestBuilder
|
||||
*/
|
||||
protected $requestBuilder;
|
||||
protected AnilistRequestBuilder $requestBuilder;
|
||||
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string $base_url
|
||||
*/
|
||||
protected $baseUrl = Anilist::BASE_URL;
|
||||
protected string $baseUrl = Anilist::BASE_URL;
|
||||
|
||||
/**
|
||||
* HTTP headers to send with every request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultHeaders = [
|
||||
protected array $defaultHeaders = [
|
||||
'Accept' => 'application/json',
|
||||
'Accept-Encoding' => 'gzip',
|
||||
'Content-type' => 'application/json',
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -37,7 +37,7 @@ final class ListItem extends AbstractListItem {
|
||||
*/
|
||||
public function create(array $data): Request
|
||||
{
|
||||
$checkedData = (new Types\MediaListEntry($data))->toArray();
|
||||
$checkedData = Types\MediaListEntry::check($data);
|
||||
return $this->mutateRequest('CreateMediaListEntry', $checkedData);
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ final class ListItem extends AbstractListItem {
|
||||
*/
|
||||
public function createFull(array $data): Request
|
||||
{
|
||||
$checkedData = (new Types\MediaListEntry($data))->toArray();
|
||||
$checkedData = Types\MediaListEntry::check($data);
|
||||
return $this->mutateRequest('CreateFullMediaListEntry', $checkedData);
|
||||
}
|
||||
|
||||
@ -85,10 +85,10 @@ final class ListItem extends AbstractListItem {
|
||||
*/
|
||||
public function increment(string $id, FormItemData $data): Request
|
||||
{
|
||||
$checkedData = (new Types\MediaListEntry([
|
||||
$checkedData = Types\MediaListEntry::check([
|
||||
'id' => $id,
|
||||
'progress' => $data->progress,
|
||||
]))->toArray();
|
||||
]);
|
||||
|
||||
return $this->mutateRequest('IncrementMediaListEntry', $checkedData);
|
||||
}
|
||||
@ -110,7 +110,7 @@ final class ListItem extends AbstractListItem {
|
||||
? AnilistStatus::REPEATING
|
||||
: AnimeWatchingStatus::KITSU_TO_ANILIST[$data->status];
|
||||
|
||||
$updateData = (new Types\MediaListEntry([
|
||||
$updateData = Types\MediaListEntry::check([
|
||||
'id' => (int)$id,
|
||||
'status' => $status,
|
||||
'score' => $rating * 5,
|
||||
@ -118,7 +118,7 @@ final class ListItem extends AbstractListItem {
|
||||
'repeat' => (int)$data['reconsumeCount'],
|
||||
'private' => $private,
|
||||
'notes' => $notes,
|
||||
]))->toArray();
|
||||
]);
|
||||
|
||||
return $this->mutateRequest('UpdateMediaListEntry', $updateData);
|
||||
}
|
||||
|
@ -4,16 +4,18 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\API\Anilist;
|
||||
|
||||
class MissingIdException extends \InvalidArgumentException {}
|
||||
use InvalidArgumentException;
|
||||
|
||||
class MissingIdException extends InvalidArgumentException {}
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -39,7 +39,7 @@ final class Model
|
||||
/**
|
||||
* @var ListItem
|
||||
*/
|
||||
private $listItem;
|
||||
private ListItem $listItem;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -42,7 +42,7 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
{
|
||||
$reconsuming = $item['status'] === AnilistStatus::REPEATING;
|
||||
|
||||
return new FormItem([
|
||||
return FormItem::from([
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $item['media']['idMal'],
|
||||
'data' => [
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -29,7 +29,7 @@ class MangaListTransformer extends AbstractTransformer {
|
||||
|
||||
public function transform($item)
|
||||
{
|
||||
return new MangaListItem([]);
|
||||
return MangaListItem::from([]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +40,7 @@ class MangaListTransformer extends AbstractTransformer {
|
||||
*/
|
||||
public function untransform(array $item): FormItem
|
||||
{
|
||||
return new FormItem([
|
||||
return FormItem::from([
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $item['media']['idMal'],
|
||||
'data' => [
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -26,7 +26,7 @@ trait CacheTrait {
|
||||
/**
|
||||
* @var Pool
|
||||
*/
|
||||
protected $cache;
|
||||
protected Pool $cache;
|
||||
|
||||
/**
|
||||
* Inject the cache object
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,18 +4,20 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\API;
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class encapsulating Json API data structure for a request or response
|
||||
*/
|
||||
@ -105,7 +107,7 @@ final class JsonAPI {
|
||||
$relationship =& $item['relationships'][$relType];
|
||||
unset($relationship['data']);
|
||||
|
||||
if (\in_array($relType, $singular, TRUE))
|
||||
if (in_array($relType, $singular, TRUE))
|
||||
{
|
||||
$relationship = $included[$dataType][$idKey];
|
||||
continue;
|
||||
@ -202,11 +204,11 @@ final class JsonAPI {
|
||||
{
|
||||
foreach($items as $id => $item)
|
||||
{
|
||||
if (array_key_exists('relationships', $item) && \is_array($item['relationships']))
|
||||
if (array_key_exists('relationships', $item) && is_array($item['relationships']))
|
||||
{
|
||||
foreach($item['relationships'] as $relType => $props)
|
||||
{
|
||||
if (array_key_exists('data', $props) && \is_array($props['data']) && array_key_exists('id', $props['data']))
|
||||
if (array_key_exists('data', $props) && is_array($props['data']) && array_key_exists('id', $props['data']))
|
||||
{
|
||||
$idKey = $props['data']['id'];
|
||||
$dataType = $props['data']['type'];
|
||||
@ -340,7 +342,7 @@ final class JsonAPI {
|
||||
|
||||
foreach ($data['data'] as $item)
|
||||
{
|
||||
if (\is_array($item) && array_key_exists('id', $item))
|
||||
if (is_array($item) && array_key_exists('id', $item))
|
||||
{
|
||||
$organized[$key][] = $item['id'];
|
||||
}
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -164,9 +164,7 @@ final class Kitsu {
|
||||
];
|
||||
}
|
||||
|
||||
usort($links, function ($a, $b) {
|
||||
return $a['meta']['name'] <=> $b['meta']['name'];
|
||||
});
|
||||
usort($links, fn ($a, $b) => $a['meta']['name'] <=> $b['meta']['name']);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -42,14 +42,14 @@ final class Auth {
|
||||
*
|
||||
* @var Model
|
||||
*/
|
||||
private $model;
|
||||
private Model $model;
|
||||
|
||||
/**
|
||||
* Session object
|
||||
*
|
||||
* @var Segment
|
||||
*/
|
||||
private $segment;
|
||||
private Segment $segment;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -91,9 +91,9 @@ final class Auth {
|
||||
$cacheItem->save();
|
||||
|
||||
// Set the token expiration in the cache
|
||||
$expire_time = $auth['created_at'] + $auth['expires_in'];
|
||||
$expireTime = $auth['created_at'] + $auth['expires_in'];
|
||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_EXP_CACHE_KEY);
|
||||
$cacheItem->set($expire_time);
|
||||
$cacheItem->set($expireTime);
|
||||
$cacheItem->save();
|
||||
|
||||
// Set the refresh token in the cache
|
||||
@ -103,7 +103,7 @@ final class Auth {
|
||||
|
||||
// Set the session values
|
||||
$this->segment->set('auth_token', $auth['access_token']);
|
||||
$this->segment->set('auth_token_expires', $expire_time);
|
||||
$this->segment->set('auth_token_expires', $expireTime);
|
||||
$this->segment->set('refresh_token', $auth['refresh_token']);
|
||||
|
||||
return TRUE;
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -25,14 +25,14 @@ final class KitsuRequestBuilder extends APIRequestBuilder {
|
||||
* The base url for api requests
|
||||
* @var string $base_url
|
||||
*/
|
||||
protected $baseUrl = 'https://kitsu.io/api/edge/';
|
||||
protected string $baseUrl = 'https://kitsu.io/api/edge/';
|
||||
|
||||
/**
|
||||
* HTTP headers to send with every request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultHeaders = [
|
||||
protected array $defaultHeaders = [
|
||||
'User-Agent' => USER_AGENT,
|
||||
'Accept' => 'application/vnd.api+json',
|
||||
'Content-Type' => 'application/vnd.api+json',
|
||||
|
@ -4,13 +4,13 @@
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.3
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.2
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
@ -38,7 +38,7 @@ trait KitsuTrait {
|
||||
* The request builder for the Kitsu API
|
||||
* @var KitsuRequestBuilder
|
||||
*/
|
||||
protected $requestBuilder;
|
||||
protected KitsuRequestBuilder $requestBuilder;
|
||||
|
||||
/**
|
||||
* Set the request builder object
|
||||
@ -176,7 +176,7 @@ trait KitsuTrait {
|
||||
$logger->warning('Non 200 response for api call', (array)$response);
|
||||
}
|
||||
|
||||
throw new FailedResponseException('Failed to get the proper response from the API');
|
||||
// throw new FailedResponseException('Failed to get the proper response from the API');
|
||||
}
|
||||
|
||||
try
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user