Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
0f4383563f
8
Jenkinsfile
vendored
8
Jenkinsfile
vendored
@ -18,7 +18,8 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
sh 'apk add --no-cache git'
|
sh 'apk add --no-cache git icu-dev'
|
||||||
|
sh 'docker-php-ext-configure intl && docker-php-ext-install intl'
|
||||||
sh 'php ./vendor/bin/phpunit --colors=never'
|
sh 'php ./vendor/bin/phpunit --colors=never'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,14 +31,15 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
sh 'apk add --no-cache git'
|
sh 'apk add --no-cache git icu-dev'
|
||||||
|
sh 'docker-php-ext-configure intl && docker-php-ext-install intl'
|
||||||
sh 'php ./vendor/bin/phpunit --colors=never'
|
sh 'php ./vendor/bin/phpunit --colors=never'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Code Cleanliness') {
|
stage('Code Cleanliness') {
|
||||||
agent any
|
agent any
|
||||||
steps {
|
steps {
|
||||||
sh "php8 ./vendor/bin/phpstan analyse -c phpstan.neon -n --no-progress --no-ansi --error-format=checkstyle | awk '{\$1=\$1;print}' > build/logs/phpstan.log"
|
sh "php ./vendor/bin/phpstan analyse -c phpstan.neon -n --no-progress --no-ansi --error-format=checkstyle | awk '{\$1=\$1;print}' > build/logs/phpstan.log"
|
||||||
recordIssues(
|
recordIssues(
|
||||||
failOnError: false,
|
failOnError: false,
|
||||||
tools: [phpStan(reportEncoding: 'UTF-8', pattern: 'build/logs/phpstan.log')]
|
tools: [phpStan(reportEncoding: 'UTF-8', pattern: 'build/logs/phpstan.log')]
|
||||||
|
316
RoboFile.php
316
RoboFile.php
@ -1,316 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Robo\Tasks;
|
|
||||||
|
|
||||||
if ( ! function_exists('glob_recursive'))
|
|
||||||
{
|
|
||||||
// Does not support flag GLOB_BRACE
|
|
||||||
function glob_recursive($pattern, $flags = 0)
|
|
||||||
{
|
|
||||||
$files = glob($pattern, $flags);
|
|
||||||
|
|
||||||
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
|
|
||||||
{
|
|
||||||
$files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $files;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is project's console commands configuration for Robo task runner.
|
|
||||||
*
|
|
||||||
* @see http://robo.li/
|
|
||||||
*/
|
|
||||||
class RoboFile extends Tasks {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directories used by analysis tools
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected array $taskDirs = [
|
|
||||||
'build/logs',
|
|
||||||
'build/pdepend',
|
|
||||||
'build/phpdox',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directories to remove with the clean task
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected array $cleanDirs = [
|
|
||||||
'coverage',
|
|
||||||
'docs',
|
|
||||||
'phpdoc',
|
|
||||||
'build/logs',
|
|
||||||
'build/phpdox',
|
|
||||||
'build/pdepend'
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do static analysis tasks
|
|
||||||
*/
|
|
||||||
public function analyze(): void
|
|
||||||
{
|
|
||||||
$this->prepare();
|
|
||||||
$this->lint();
|
|
||||||
$this->phploc(TRUE);
|
|
||||||
$this->phpcs(TRUE);
|
|
||||||
$this->phpmd(TRUE);
|
|
||||||
$this->dependencyReport();
|
|
||||||
$this->phpcpdReport();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run all tests, generate coverage, generate docs, generate code statistics
|
|
||||||
*/
|
|
||||||
public function build(): void
|
|
||||||
{
|
|
||||||
$this->analyze();
|
|
||||||
$this->coverage();
|
|
||||||
$this->docs();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleanup temporary files
|
|
||||||
*/
|
|
||||||
public function clean(): void
|
|
||||||
{
|
|
||||||
$cleanFiles = [
|
|
||||||
'build/humbug.json',
|
|
||||||
'build/humbug-log.txt',
|
|
||||||
];
|
|
||||||
array_map(static function ($file) {
|
|
||||||
@unlink($file);
|
|
||||||
}, $cleanFiles);
|
|
||||||
|
|
||||||
// So the task doesn't complain,
|
|
||||||
// make any 'missing' dirs to cleanup
|
|
||||||
array_map(static function ($dir) {
|
|
||||||
if ( ! is_dir($dir))
|
|
||||||
{
|
|
||||||
`mkdir -p {$dir}`;
|
|
||||||
}
|
|
||||||
}, $this->cleanDirs);
|
|
||||||
|
|
||||||
$this->_cleanDir($this->cleanDirs);
|
|
||||||
$this->_deleteDir($this->cleanDirs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run unit tests and generate coverage reports
|
|
||||||
*/
|
|
||||||
public function coverage(): void
|
|
||||||
{
|
|
||||||
$this->_run(['phpdbg -qrr -- vendor/bin/phpunit -c build']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate documentation with phpdox
|
|
||||||
*/
|
|
||||||
public function docs(): void
|
|
||||||
{
|
|
||||||
$cmd_parts = [
|
|
||||||
'vendor/bin/phpdox',
|
|
||||||
];
|
|
||||||
$this->_run($cmd_parts, ' && ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify that source files are valid
|
|
||||||
*/
|
|
||||||
public function lint(): void
|
|
||||||
{
|
|
||||||
$files = $this->getAllSourceFiles();
|
|
||||||
|
|
||||||
$chunks = array_chunk($files, (int)shell_exec('getconf _NPROCESSORS_ONLN'));
|
|
||||||
|
|
||||||
foreach($chunks as $chunk)
|
|
||||||
{
|
|
||||||
$this->parallelLint($chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the phpcs tool
|
|
||||||
*
|
|
||||||
* @param bool $report - if true, generates reports instead of direct output
|
|
||||||
*/
|
|
||||||
public function phpcs($report = FALSE): void
|
|
||||||
{
|
|
||||||
$report_cmd_parts = [
|
|
||||||
'vendor/bin/phpcs',
|
|
||||||
'--standard=./build/phpcs.xml',
|
|
||||||
'--report-checkstyle=./build/logs/phpcs.xml',
|
|
||||||
];
|
|
||||||
|
|
||||||
$normal_cmd_parts = [
|
|
||||||
'vendor/bin/phpcs',
|
|
||||||
'--standard=./build/phpcs.xml',
|
|
||||||
];
|
|
||||||
|
|
||||||
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
|
|
||||||
|
|
||||||
$this->_run($cmd_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function phpmd($report = FALSE): void
|
|
||||||
{
|
|
||||||
$report_cmd_parts = [
|
|
||||||
'vendor/bin/phpmd',
|
|
||||||
'./src',
|
|
||||||
'xml',
|
|
||||||
'cleancode,codesize,controversial,design,naming,unusedcode',
|
|
||||||
'--exclude ParallelAPIRequest',
|
|
||||||
'--reportfile ./build/logs/phpmd.xml'
|
|
||||||
];
|
|
||||||
|
|
||||||
$normal_cmd_parts = [
|
|
||||||
'vendor/bin/phpmd',
|
|
||||||
'./src',
|
|
||||||
'ansi',
|
|
||||||
'cleancode,codesize,controversial,design,naming,unusedcode',
|
|
||||||
'--exclude ParallelAPIRequest'
|
|
||||||
];
|
|
||||||
|
|
||||||
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
|
|
||||||
|
|
||||||
$this->_run($cmd_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the phploc tool
|
|
||||||
*
|
|
||||||
* @param bool $report - if true, generates reports instead of direct output
|
|
||||||
*/
|
|
||||||
public function phploc($report = FALSE): void
|
|
||||||
{
|
|
||||||
// Command for generating reports
|
|
||||||
$report_cmd_parts = [
|
|
||||||
'vendor/bin/phploc',
|
|
||||||
'--count-tests',
|
|
||||||
'--log-csv=build/logs/phploc.csv',
|
|
||||||
'--log-xml=build/logs/phploc.xml',
|
|
||||||
'src',
|
|
||||||
'tests'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Command for generating direct output
|
|
||||||
$normal_cmd_parts = [
|
|
||||||
'vendor/bin/phploc',
|
|
||||||
'--count-tests',
|
|
||||||
'src',
|
|
||||||
'tests'
|
|
||||||
];
|
|
||||||
|
|
||||||
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
|
|
||||||
|
|
||||||
$this->_run($cmd_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create temporary directories
|
|
||||||
*/
|
|
||||||
public function prepare(): void
|
|
||||||
{
|
|
||||||
array_map([$this, '_mkdir'], $this->taskDirs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lint php files and run unit tests
|
|
||||||
*/
|
|
||||||
public function test(): void
|
|
||||||
{
|
|
||||||
$this->lint();
|
|
||||||
|
|
||||||
$this->_run(['vendor/bin/phpunit']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create pdepend reports
|
|
||||||
*/
|
|
||||||
protected function dependencyReport(): void
|
|
||||||
{
|
|
||||||
$cmd_parts = [
|
|
||||||
'vendor/bin/pdepend',
|
|
||||||
'--jdepend-xml=build/logs/jdepend.xml',
|
|
||||||
'--jdepend-chart=build/pdepend/dependencies.svg',
|
|
||||||
'--overview-pyramid=build/pdepend/overview-pyramid.svg',
|
|
||||||
'src'
|
|
||||||
];
|
|
||||||
$this->_run($cmd_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total list of source files, including tests
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getAllSourceFiles(): array
|
|
||||||
{
|
|
||||||
$files = array_merge(
|
|
||||||
glob_recursive('build/*.php'),
|
|
||||||
glob_recursive('src/*.php'),
|
|
||||||
glob_recursive('src/**/*.php'),
|
|
||||||
glob_recursive('tests/*.php'),
|
|
||||||
glob_recursive('tests/**/*.php'),
|
|
||||||
glob('*.php')
|
|
||||||
);
|
|
||||||
|
|
||||||
$files = array_filter($files, static function(string $value) {
|
|
||||||
return strpos($value, '__snapshots__') === FALSE;
|
|
||||||
});
|
|
||||||
|
|
||||||
sort($files);
|
|
||||||
|
|
||||||
return $files;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run php's linter in one parallel task for the passed chunk
|
|
||||||
*
|
|
||||||
* @param array $chunk
|
|
||||||
*/
|
|
||||||
protected function parallelLint(array $chunk): void
|
|
||||||
{
|
|
||||||
$task = $this->taskParallelExec()
|
|
||||||
->timeout(5)
|
|
||||||
->printed(FALSE);
|
|
||||||
|
|
||||||
foreach($chunk as $file)
|
|
||||||
{
|
|
||||||
$task = $task->process("php -l {$file}");
|
|
||||||
}
|
|
||||||
|
|
||||||
$task->run();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate copy paste detector report
|
|
||||||
*/
|
|
||||||
protected function phpcpdReport(): void
|
|
||||||
{
|
|
||||||
$cmd_parts = [
|
|
||||||
'vendor/bin/phpcpd',
|
|
||||||
'--log-pmd build/logs/pmd-cpd.xml',
|
|
||||||
'src'
|
|
||||||
];
|
|
||||||
$this->_run($cmd_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for joining an array of command arguments
|
|
||||||
* and then running it
|
|
||||||
*
|
|
||||||
* @param array $cmd_parts - command arguments
|
|
||||||
* @param string $join_on - what to join the command arguments with
|
|
||||||
*/
|
|
||||||
protected function _run(array $cmd_parts, $join_on = ' '): void
|
|
||||||
{
|
|
||||||
$this->taskExec(implode($join_on, $cmd_parts))->run();
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,16 +2,16 @@
|
|||||||
/**
|
/**
|
||||||
* Hummingbird Anime List Client
|
* Hummingbird Anime List Client
|
||||||
*
|
*
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
*
|
*
|
||||||
* PHP version 7
|
* PHP version 8
|
||||||
*
|
*
|
||||||
* @package HummingbirdAnimeClient
|
* @package HummingbirdAnimeClient
|
||||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
* @copyright 2015 - 2017 Timothy J. Warren
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
* @version 4.0
|
* @version 5.2
|
||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use function Aviat\AnimeClient\loadConfig;
|
use function Aviat\AnimeClient\loadConfig;
|
||||||
@ -21,12 +21,13 @@ use function Aviat\AnimeClient\loadConfig;
|
|||||||
//
|
//
|
||||||
// You shouldn't generally need to change anything below this line
|
// You shouldn't generally need to change anything below this line
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
$APP_DIR = realpath(__DIR__ . '/../');
|
$APP_DIR = dirname(__DIR__);
|
||||||
$ROOT_DIR = realpath("{$APP_DIR}/../");
|
$ROOT_DIR = dirname($APP_DIR);
|
||||||
|
|
||||||
$tomlConfig = loadConfig(__DIR__);
|
$tomlConfig = loadConfig(__DIR__);
|
||||||
|
|
||||||
return array_merge($tomlConfig, [
|
return array_merge($tomlConfig, [
|
||||||
|
'root' => $ROOT_DIR,
|
||||||
'asset_dir' => "{$ROOT_DIR}/public",
|
'asset_dir' => "{$ROOT_DIR}/public",
|
||||||
'base_config_dir' => __DIR__,
|
'base_config_dir' => __DIR__,
|
||||||
'config_dir' => "{$APP_DIR}/config",
|
'config_dir' => "{$APP_DIR}/config",
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
/**
|
/**
|
||||||
* Hummingbird Anime List Client
|
* Hummingbird Anime List Client
|
||||||
*
|
*
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
*
|
*
|
||||||
* PHP version 7
|
* PHP version 8
|
||||||
*
|
*
|
||||||
* @package HummingbirdAnimeClient
|
* @package HummingbirdAnimeClient
|
||||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
* @copyright 2015 - 2018 Timothy J. Warren
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
* @version 4.0
|
* @version 5.2
|
||||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -34,11 +34,11 @@ use Psr\SimpleCache\CacheInterface;
|
|||||||
|
|
||||||
use function Aviat\Ion\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
if ( ! defined('APP_DIR'))
|
if ( ! defined('HB_APP_DIR'))
|
||||||
{
|
{
|
||||||
define('APP_DIR', __DIR__);
|
define('HB_APP_DIR', __DIR__);
|
||||||
define('ROOT_DIR', dirname(APP_DIR));
|
define('ROOT_DIR', dirname(HB_APP_DIR));
|
||||||
define('TEMPLATE_DIR', _dir(APP_DIR, 'templates'));
|
define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -50,7 +50,7 @@ return static function (array $configArray = []): Container {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Logging
|
// Logging
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
$LOG_DIR = _dir(APP_DIR, 'logs');
|
$LOG_DIR = _dir(HB_APP_DIR, 'logs');
|
||||||
|
|
||||||
$appLogger = new Logger('animeclient');
|
$appLogger = new Logger('animeclient');
|
||||||
$appLogger->pushHandler(new RotatingFileHandler(_dir($LOG_DIR, 'app.log'), 2, Logger::WARNING));
|
$appLogger->pushHandler(new RotatingFileHandler(_dir($LOG_DIR, 'app.log'), 2, Logger::WARNING));
|
||||||
|
@ -11,12 +11,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
|
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<script async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
|
||||||
<script nomodule='nomodule' async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
|
|
||||||
<script type="module" src="<?= $urlGenerator->assetUrl('es/scripts.js') ?>"></script>
|
|
||||||
<?php else: ?>
|
|
||||||
<script nomodule="nomodule" async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/anon.min.js') ?>"></script>
|
|
||||||
<script type="module" src="<?= $urlGenerator->assetUrl('es/anon.js') ?>"></script>
|
|
||||||
<?php endif ?>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -5,13 +5,13 @@ namespace Aviat\AnimeClient;
|
|||||||
$whose = $config->get('whose_list') . "'s ";
|
$whose = $config->get('whose_list') . "'s ";
|
||||||
$lastSegment = $urlGenerator->lastSegment();
|
$lastSegment = $urlGenerator->lastSegment();
|
||||||
$extraSegment = $lastSegment === 'list' ? '/list' : '';
|
$extraSegment = $lastSegment === 'list' ? '/list' : '';
|
||||||
$hasAnime = stripos($GLOBALS['_SERVER']['REQUEST_URI'], 'anime') !== FALSE;
|
$hasAnime = str_contains($GLOBALS['_SERVER']['REQUEST_URI'], 'anime');
|
||||||
$hasManga = stripos($GLOBALS['_SERVER']['REQUEST_URI'], 'manga') !== FALSE;
|
$hasManga = str_contains($GLOBALS['_SERVER']['REQUEST_URI'], 'manga');
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div id="main-nav" class="flex flex-align-end flex-wrap">
|
<div id="main-nav" class="flex flex-align-end flex-wrap">
|
||||||
<span class="flex-no-wrap grow-1">
|
<span class="flex-no-wrap grow-1">
|
||||||
<?php if(strpos($route_path, 'collection') === FALSE): ?>
|
<?php if( ! str_contains($route_path, 'collection')): ?>
|
||||||
<?= $helper->a(
|
<?= $helper->a(
|
||||||
$urlGenerator->defaultUrl($url_type),
|
$urlGenerator->defaultUrl($url_type),
|
||||||
$whose . ucfirst($url_type) . ' List',
|
$whose . ucfirst($url_type) . ' List',
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
$file_patterns = [
|
$file_patterns = [
|
||||||
|
'app/appConf/*.php',
|
||||||
'app/bootstrap.php',
|
'app/bootstrap.php',
|
||||||
'migrations/*.php',
|
'migrations/*.php',
|
||||||
'src/**/*.php',
|
'src/**/*.php',
|
||||||
@ -16,7 +17,7 @@ if ( ! function_exists('glob_recursive'))
|
|||||||
{
|
{
|
||||||
// Does not support flag GLOB_BRACE
|
// Does not support flag GLOB_BRACE
|
||||||
|
|
||||||
function glob_recursive($pattern, $flags = 0)
|
function glob_recursive(string $pattern, int $flags = 0): array
|
||||||
{
|
{
|
||||||
$files = glob($pattern, $flags);
|
$files = glob($pattern, $flags);
|
||||||
|
|
||||||
@ -57,17 +58,21 @@ function get_text_to_replace(array $tokens): string
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_tokens($source): array
|
function get_tokens(string $source): array
|
||||||
{
|
{
|
||||||
return token_get_all($source);
|
return token_get_all($source);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replace_files(array $files, $template)
|
function replace_files(array $files, string $template): void
|
||||||
{
|
{
|
||||||
print_r($files);
|
print_r($files);
|
||||||
foreach ($files as $file)
|
foreach ($files as $file)
|
||||||
{
|
{
|
||||||
$source = file_get_contents($file);
|
$source = file_get_contents($file);
|
||||||
|
if ($source === FALSE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (stripos($source, 'namespace') === FALSE)
|
if (stripos($source, 'namespace') === FALSE)
|
||||||
{
|
{
|
||||||
|
@ -43,26 +43,25 @@
|
|||||||
"aviat/query": "^3.0.0",
|
"aviat/query": "^3.0.0",
|
||||||
"danielstjules/stringy": "^3.1.0",
|
"danielstjules/stringy": "^3.1.0",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-iconv": "*",
|
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"filp/whoops": "^2.1",
|
|
||||||
"laminas/laminas-diactoros": "^2.5.0",
|
"laminas/laminas-diactoros": "^2.5.0",
|
||||||
"laminas/laminas-httphandlerrunner": "^1.1.0",
|
"laminas/laminas-httphandlerrunner": "^1.1.0",
|
||||||
"maximebf/consolekit": "^1.0.3",
|
"maximebf/consolekit": "^1.0.3",
|
||||||
"monolog/monolog": "^2.0.2",
|
"monolog/monolog": "^2.0.2",
|
||||||
"php": "^8.0.0",
|
"php": ">= 8.0.0",
|
||||||
"psr/container": "^1.0.0",
|
"psr/container": "^1.0.0",
|
||||||
"psr/http-message": "^1.0.1",
|
"psr/http-message": "^1.0.1",
|
||||||
"psr/log": "^1.1.3",
|
"psr/log": "^1.1.3",
|
||||||
"robmorgan/phinx": "^0.12.4",
|
"robmorgan/phinx": "^0.12.4",
|
||||||
"symfony/var-dumper": "^5.0.7",
|
"symfony/polyfill-mbstring": "^1.0.0",
|
||||||
|
"symfony/polyfill-util": "^1.0.0",
|
||||||
|
"tracy/tracy": "^2.8.0",
|
||||||
"yosymfony/toml": "^1.0.4"
|
"yosymfony/toml": "^1.0.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"consolidation/robo": "^2.0.0",
|
|
||||||
"pdepend/pdepend": "^2.",
|
"pdepend/pdepend": "^2.",
|
||||||
"phploc/phploc": "^7.0.0",
|
"phploc/phploc": "^7.0.0",
|
||||||
"phpmd/phpmd": "^2.8.2",
|
"phpmd/phpmd": "^2.8.2",
|
||||||
|
2
console
2
console
@ -26,7 +26,7 @@ try
|
|||||||
'sync:lists' => Command\SyncLists::class
|
'sync:lists' => Command\SyncLists::class
|
||||||
]))->run();
|
]))->run();
|
||||||
}
|
}
|
||||||
catch (\Exception $e)
|
catch (\Throwable)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
import compiler from '@ampproject/rollup-plugin-closure-compiler';
|
|
||||||
|
|
||||||
const plugins = [
|
|
||||||
compiler({
|
|
||||||
assumeFunctionWrapper: true,
|
|
||||||
compilationLevel: 'WHITESPACE_ONLY', //'ADVANCED',
|
|
||||||
createSourceMap: true,
|
|
||||||
env: 'BROWSER',
|
|
||||||
languageIn: 'ECMASCRIPT_2018',
|
|
||||||
languageOut: 'ES3'
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
const defaultOutput = {
|
|
||||||
format: 'iife',
|
|
||||||
sourcemap: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const nonModules = [{
|
|
||||||
input: './js/anon.js',
|
|
||||||
output: {
|
|
||||||
...defaultOutput,
|
|
||||||
file: '../public/js/anon.min.js',
|
|
||||||
sourcemapFile: '../public/js/anon.min.js.map',
|
|
||||||
},
|
|
||||||
plugins,
|
|
||||||
}, {
|
|
||||||
input: './js/index.js',
|
|
||||||
output: {
|
|
||||||
...defaultOutput,
|
|
||||||
file: '../public/js/scripts.min.js',
|
|
||||||
sourcemapFile: '../public/js/scripts.min.js.map',
|
|
||||||
},
|
|
||||||
plugins,
|
|
||||||
}, {
|
|
||||||
input: './js/base/sort-tables.js',
|
|
||||||
output: {
|
|
||||||
...defaultOutput,
|
|
||||||
file: '../public/js/tables.min.js',
|
|
||||||
sourcemapFile: '../public/js/tables.min.js.map',
|
|
||||||
},
|
|
||||||
plugins,
|
|
||||||
}];
|
|
||||||
|
|
||||||
const moduleOutput = {
|
|
||||||
format: 'es',
|
|
||||||
sourcemap: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
let modules = [{
|
|
||||||
input: './js/anon.js',
|
|
||||||
output: {
|
|
||||||
...moduleOutput,
|
|
||||||
file: '../public/es/anon.js',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
input: './js/index.js',
|
|
||||||
output: {
|
|
||||||
...moduleOutput,
|
|
||||||
file: '../public/es/scripts.js',
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
|
|
||||||
// Return the config array for rollup
|
|
||||||
export default [
|
|
||||||
...nonModules,
|
|
||||||
...modules,
|
|
||||||
];
|
|
@ -9,7 +9,7 @@ const matches = (elm, selector) => {
|
|||||||
return i > -1;
|
return i > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AnimeClient = {
|
const AnimeClient = {
|
||||||
/**
|
/**
|
||||||
* Placeholder function
|
* Placeholder function
|
||||||
*/
|
*/
|
||||||
@ -18,8 +18,8 @@ export const AnimeClient = {
|
|||||||
* DOM selector
|
* DOM selector
|
||||||
*
|
*
|
||||||
* @param {string} selector - The dom selector string
|
* @param {string} selector - The dom selector string
|
||||||
* @param {object} [context]
|
* @param {Element} [context]
|
||||||
* @return {[HTMLElement]} - array of dom elements
|
* @return array of dom elements
|
||||||
*/
|
*/
|
||||||
$(selector, context = null) {
|
$(selector, context = null) {
|
||||||
if (typeof selector !== 'string') {
|
if (typeof selector !== 'string') {
|
||||||
@ -60,7 +60,7 @@ export const AnimeClient = {
|
|||||||
/**
|
/**
|
||||||
* Hide the selected element
|
* Hide the selected element
|
||||||
*
|
*
|
||||||
* @param {string|Element} sel - the selector of the element to hide
|
* @param {string|Element|Element[]} sel - the selector of the element to hide
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
hide (sel) {
|
hide (sel) {
|
||||||
@ -77,7 +77,7 @@ export const AnimeClient = {
|
|||||||
/**
|
/**
|
||||||
* UnHide the selected element
|
* UnHide the selected element
|
||||||
*
|
*
|
||||||
* @param {string|Element} sel - the selector of the element to hide
|
* @param {string|Element|Element[]} sel - the selector of the element to hide
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
show (sel) {
|
show (sel) {
|
||||||
@ -116,9 +116,9 @@ export const AnimeClient = {
|
|||||||
/**
|
/**
|
||||||
* Finds the closest parent element matching the passed selector
|
* Finds the closest parent element matching the passed selector
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} current - the current HTMLElement
|
* @param {Element} current - the current Element
|
||||||
* @param {string} parentSelector - selector for the parent element
|
* @param {string} parentSelector - selector for the parent element
|
||||||
* @return {HTMLElement|null} - the parent element
|
* @return {Element|null} - the parent element
|
||||||
*/
|
*/
|
||||||
closestParent (current, parentSelector) {
|
closestParent (current, parentSelector) {
|
||||||
if (Element.prototype.closest !== undefined) {
|
if (Element.prototype.closest !== undefined) {
|
||||||
@ -204,9 +204,9 @@ function delegateEvent(sel, target, event, listener) {
|
|||||||
/**
|
/**
|
||||||
* Add an event listener
|
* Add an event listener
|
||||||
*
|
*
|
||||||
* @param {string|HTMLElement} sel - the parent selector to bind to
|
* @param {string|Element} sel - the parent selector to bind to
|
||||||
* @param {string} event - event name(s) to bind
|
* @param {string} event - event name(s) to bind
|
||||||
* @param {string|HTMLElement|function} target - the element to directly bind the event to
|
* @param {string|Element|function} target - the element to directly bind the event to
|
||||||
* @param {function} [listener] - event listener callback
|
* @param {function} [listener] - event listener callback
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
|
@ -71,7 +71,7 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
|||||||
success: (res) => {
|
success: (res) => {
|
||||||
const resData = JSON.parse(res);
|
const resData = JSON.parse(res);
|
||||||
|
|
||||||
if (resData.errors) {
|
if (resData.error) {
|
||||||
_.hide('#loading-shadow');
|
_.hide('#loading-shadow');
|
||||||
_.showMessage('error', `Failed to update ${title}. `);
|
_.showMessage('error', `Failed to update ${title}. `);
|
||||||
_.scrollToTop();
|
_.scrollToTop();
|
||||||
|
@ -1,227 +0,0 @@
|
|||||||
/*
|
|
||||||
* classList.js: Cross-browser full element.classList implementation.
|
|
||||||
* 2014-07-23
|
|
||||||
*
|
|
||||||
* By Eli Grey, http://eligrey.com
|
|
||||||
* Public Domain.
|
|
||||||
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*global self, document, DOMException */
|
|
||||||
|
|
||||||
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
|
|
||||||
|
|
||||||
if ("document" in self) {
|
|
||||||
|
|
||||||
// Full polyfill for browsers with no classList support
|
|
||||||
if (!("classList" in document.createElement("_"))) {
|
|
||||||
|
|
||||||
(function(view) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
if (!('Element' in view)) return;
|
|
||||||
|
|
||||||
var
|
|
||||||
classListProp = "classList",
|
|
||||||
protoProp = "prototype",
|
|
||||||
elemCtrProto = view.Element[protoProp],
|
|
||||||
objCtr = Object,
|
|
||||||
strTrim = String[protoProp].trim || function() {
|
|
||||||
return this.replace(/^\s+|\s+$/g, "");
|
|
||||||
},
|
|
||||||
arrIndexOf = Array[protoProp].indexOf || function(item) {
|
|
||||||
var
|
|
||||||
i = 0,
|
|
||||||
len = this.length;
|
|
||||||
for (; i < len; i++) {
|
|
||||||
if (i in this && this[i] === item) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Vendors: please allow content code to instantiate DOMExceptions
|
|
||||||
,
|
|
||||||
DOMEx = function(type, message) {
|
|
||||||
this.name = type;
|
|
||||||
this.code = DOMException[type];
|
|
||||||
this.message = message;
|
|
||||||
},
|
|
||||||
checkTokenAndGetIndex = function(classList, token) {
|
|
||||||
if (token === "") {
|
|
||||||
throw new DOMEx(
|
|
||||||
"SYNTAX_ERR", "An invalid or illegal string was specified"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (/\s/.test(token)) {
|
|
||||||
throw new DOMEx(
|
|
||||||
"INVALID_CHARACTER_ERR", "String contains an invalid character"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return arrIndexOf.call(classList, token);
|
|
||||||
},
|
|
||||||
ClassList = function(elem) {
|
|
||||||
var
|
|
||||||
trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
|
|
||||||
classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
|
|
||||||
i = 0,
|
|
||||||
len = classes.length;
|
|
||||||
for (; i < len; i++) {
|
|
||||||
this.push(classes[i]);
|
|
||||||
}
|
|
||||||
this._updateClassName = function() {
|
|
||||||
elem.setAttribute("class", this.toString());
|
|
||||||
};
|
|
||||||
},
|
|
||||||
classListProto = ClassList[protoProp] = [],
|
|
||||||
classListGetter = function() {
|
|
||||||
return new ClassList(this);
|
|
||||||
};
|
|
||||||
// Most DOMException implementations don't allow calling DOMException's toString()
|
|
||||||
// on non-DOMExceptions. Error's toString() is sufficient here.
|
|
||||||
DOMEx[protoProp] = Error[protoProp];
|
|
||||||
classListProto.item = function(i) {
|
|
||||||
return this[i] || null;
|
|
||||||
};
|
|
||||||
classListProto.contains = function(token) {
|
|
||||||
token += "";
|
|
||||||
return checkTokenAndGetIndex(this, token) !== -1;
|
|
||||||
};
|
|
||||||
classListProto.add = function() {
|
|
||||||
var
|
|
||||||
tokens = arguments,
|
|
||||||
i = 0,
|
|
||||||
l = tokens.length,
|
|
||||||
token, updated = false;
|
|
||||||
do {
|
|
||||||
token = tokens[i] + "";
|
|
||||||
if (checkTokenAndGetIndex(this, token) === -1) {
|
|
||||||
this.push(token);
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (++i < l);
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
this._updateClassName();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
classListProto.remove = function() {
|
|
||||||
var
|
|
||||||
tokens = arguments,
|
|
||||||
i = 0,
|
|
||||||
l = tokens.length,
|
|
||||||
token, updated = false,
|
|
||||||
index;
|
|
||||||
do {
|
|
||||||
token = tokens[i] + "";
|
|
||||||
index = checkTokenAndGetIndex(this, token);
|
|
||||||
while (index !== -1) {
|
|
||||||
this.splice(index, 1);
|
|
||||||
updated = true;
|
|
||||||
index = checkTokenAndGetIndex(this, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (++i < l);
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
this._updateClassName();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
classListProto.toggle = function(token, force) {
|
|
||||||
token += "";
|
|
||||||
|
|
||||||
var
|
|
||||||
result = this.contains(token),
|
|
||||||
method = result ?
|
|
||||||
force !== true && "remove" :
|
|
||||||
force !== false && "add";
|
|
||||||
|
|
||||||
if (method) {
|
|
||||||
this[method](token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force === true || force === false) {
|
|
||||||
return force;
|
|
||||||
} else {
|
|
||||||
return !result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
classListProto.toString = function() {
|
|
||||||
return this.join(" ");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (objCtr.defineProperty) {
|
|
||||||
var classListPropDesc = {
|
|
||||||
get: classListGetter,
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
|
||||||
} catch (ex) { // IE 8 doesn't support enumerable:true
|
|
||||||
if (ex.number === -0x7FF5EC54) {
|
|
||||||
classListPropDesc.enumerable = false;
|
|
||||||
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (objCtr[protoProp].__defineGetter__) {
|
|
||||||
elemCtrProto.__defineGetter__(classListProp, classListGetter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}(self));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// There is full or partial native classList support, so just check if we need
|
|
||||||
// to normalize the add/remove and toggle APIs.
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var testElement = document.createElement("_");
|
|
||||||
|
|
||||||
testElement.classList.add("c1", "c2");
|
|
||||||
|
|
||||||
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
|
|
||||||
// classList.remove exist but support only one argument at a time.
|
|
||||||
if (!testElement.classList.contains("c2")) {
|
|
||||||
var createMethod = function(method) {
|
|
||||||
var original = DOMTokenList.prototype[method];
|
|
||||||
|
|
||||||
DOMTokenList.prototype[method] = function(token) {
|
|
||||||
var i, len = arguments.length;
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
token = arguments[i];
|
|
||||||
original.call(this, token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
createMethod('add');
|
|
||||||
createMethod('remove');
|
|
||||||
}
|
|
||||||
|
|
||||||
testElement.classList.toggle("c3", false);
|
|
||||||
|
|
||||||
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
|
|
||||||
// support the second argument.
|
|
||||||
if (testElement.classList.contains("c3")) {
|
|
||||||
var _toggle = DOMTokenList.prototype.toggle;
|
|
||||||
|
|
||||||
DOMTokenList.prototype.toggle = function(token, force) {
|
|
||||||
if (1 in arguments && !this.contains(token) === !force) {
|
|
||||||
return force;
|
|
||||||
} else {
|
|
||||||
return _toggle.call(this, token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
testElement = null;
|
|
||||||
}());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -16,7 +16,7 @@ _.on('.media-filter', 'input', filterMedia);
|
|||||||
/**
|
/**
|
||||||
* Hide the html element attached to the event
|
* Hide the html element attached to the event
|
||||||
*
|
*
|
||||||
* @param event
|
* @param {MouseEvent} event
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function hide (event) {
|
function hide (event) {
|
||||||
@ -26,7 +26,7 @@ function hide (event) {
|
|||||||
/**
|
/**
|
||||||
* Confirm deletion of an item
|
* Confirm deletion of an item
|
||||||
*
|
*
|
||||||
* @param event
|
* @param {MouseEvent} event
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function confirmDelete (event) {
|
function confirmDelete (event) {
|
||||||
@ -52,7 +52,7 @@ function clearAPICache () {
|
|||||||
/**
|
/**
|
||||||
* Scroll to the accordion/vertical tab section just opened
|
* Scroll to the accordion/vertical tab section just opened
|
||||||
*
|
*
|
||||||
* @param event
|
* @param {InputEvent} event
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function scrollToSection (event) {
|
function scrollToSection (event) {
|
||||||
@ -70,7 +70,7 @@ function scrollToSection (event) {
|
|||||||
/**
|
/**
|
||||||
* Filter an anime or manga list
|
* Filter an anime or manga list
|
||||||
*
|
*
|
||||||
* @param event
|
* @param {InputEvent} event
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function filterMedia (event) {
|
function filterMedia (event) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import './anon.js';
|
import './sw.js';
|
||||||
|
import './events.js';
|
||||||
import './session-check.js';
|
import './session-check.js';
|
||||||
import './anime.js';
|
import './anime.js';
|
||||||
import './manga.js';
|
import './manga.js';
|
||||||
|
@ -72,14 +72,22 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
mimeType: 'application/json',
|
mimeType: 'application/json',
|
||||||
success: () => {
|
success: (res) => {
|
||||||
|
const resData = JSON.parse(res)
|
||||||
|
if (resData.error) {
|
||||||
|
_.hide('#loading-shadow');
|
||||||
|
_.showMessage('error', `Failed to update ${mangaName}. `);
|
||||||
|
_.scrollToTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
|
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
|
||||||
_.hide(parentSel);
|
_.hide(parentSel);
|
||||||
}
|
}
|
||||||
|
|
||||||
_.hide('#loading-shadow');
|
_.hide('#loading-shadow');
|
||||||
|
|
||||||
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = completed;
|
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
||||||
_.showMessage('success', `Successfully updated ${mangaName}`);
|
_.showMessage('success', `Successfully updated ${mangaName}`);
|
||||||
_.scrollToTop();
|
_.scrollToTop();
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import _ from './anime-client.js';
|
import _ from './anime-client.js';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
// Var is intentional
|
let hidden = null;
|
||||||
var hidden = null;
|
let visibilityChange = null;
|
||||||
var visibilityChange = null;
|
|
||||||
|
|
||||||
if (typeof document.hidden !== "undefined") {
|
if (typeof document.hidden !== "undefined") {
|
||||||
hidden = "hidden";
|
hidden = "hidden";
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import './events.js';
|
// Start the service worker, if you can
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
navigator.serviceWorker.register('/sw.js').then(reg => {
|
||||||
console.log('Service worker registered', reg.scope);
|
console.log('Service worker registered', reg.scope);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('Failed to register service worker', error);
|
console.error('Failed to register service worker', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -8,12 +8,10 @@ _.on('main', 'change', '.big-check', (e) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function renderAnimeSearchResults (data) {
|
export function renderAnimeSearchResults (data) {
|
||||||
const results = [];
|
return data.map(item => {
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
const titles = item.titles.join('<br />');
|
const titles = item.titles.join('<br />');
|
||||||
|
|
||||||
results.push(`
|
return `
|
||||||
<article class="media search">
|
<article class="media search">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
|
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
|
||||||
@ -38,19 +36,14 @@ export function renderAnimeSearchResults (data) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`);
|
`;
|
||||||
});
|
}).join('');
|
||||||
|
|
||||||
return results.join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderMangaSearchResults (data) {
|
export function renderMangaSearchResults (data) {
|
||||||
const results = [];
|
return data.map(item => {
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
const titles = item.titles.join('<br />');
|
const titles = item.titles.join('<br />');
|
||||||
|
return `
|
||||||
results.push(`
|
|
||||||
<article class="media search">
|
<article class="media search">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
|
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
|
||||||
@ -75,8 +68,6 @@ export function renderMangaSearchResults (data) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`);
|
`;
|
||||||
});
|
}).join('');
|
||||||
|
|
||||||
return results.join('');
|
|
||||||
}
|
}
|
@ -3,19 +3,19 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:css && npm run build:js",
|
"build": "npm run build:css && npm run build:js",
|
||||||
"build:css": "node ./css.js",
|
"build:css": "node ./css.js",
|
||||||
"build:js": "rollup -c ./build-js.js",
|
"build:js": "spack",
|
||||||
"watch:css": "watch 'npm run build:css' --filter=./cssfilter.js",
|
"watch:css": "watch 'npm run build:css' --filter=./cssfilter.js",
|
||||||
"watch:js": "watch 'npm run build:js' ./js",
|
"watch:js": "watch 'npm run build:js' ./js",
|
||||||
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
|
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ampproject/rollup-plugin-closure-compiler": "^0.25.2",
|
"@swc/cli": "^0.1.39",
|
||||||
"concurrently": "^5.1.0",
|
"@swc/core": "^1.2.54",
|
||||||
"cssnano": "^4.1.10",
|
"concurrently": "^6.0.2",
|
||||||
"postcss": "^7.0.27",
|
"cssnano": "^5.0.1",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss": "^8.2.6",
|
||||||
|
"postcss-import": "^14.0.0",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"rollup": "^2.4.0",
|
|
||||||
"watch": "^1.0.2"
|
"watch": "^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
frontEndSrc/spack.config.js
Normal file
19
frontEndSrc/spack.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
'scripts.min': __dirname + '/js/index.js',
|
||||||
|
'tables.min': __dirname + '/js/base/sort-tables.js',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: '../public/js',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
jsc: {
|
||||||
|
target: 'es3',
|
||||||
|
loose: true,
|
||||||
|
},
|
||||||
|
minify: true,
|
||||||
|
module: {
|
||||||
|
type: 'es6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
13
index.php
13
index.php
@ -17,9 +17,7 @@
|
|||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
use Aviat\AnimeClient\Types\Config as ConfigType;
|
use Aviat\AnimeClient\Types\Config as ConfigType;
|
||||||
use Whoops\Handler\PrettyPageHandler;
|
use Tracy\Debugger;
|
||||||
use Whoops\Run;
|
|
||||||
|
|
||||||
use function Aviat\Ion\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
setlocale(LC_CTYPE, 'en_US');
|
setlocale(LC_CTYPE, 'en_US');
|
||||||
@ -27,12 +25,9 @@ setlocale(LC_CTYPE, 'en_US');
|
|||||||
// Load composer autoloader
|
// Load composer autoloader
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
if (file_exists('.is-dev'))
|
Debugger::$strictMode = true;
|
||||||
{
|
Debugger::$showBar = false;
|
||||||
$whoops = new Run;
|
Debugger::enable(Debugger::DEVELOPMENT, __DIR__ . '/app/logs');
|
||||||
$whoops->pushHandler(new PrettyPageHandler);
|
|
||||||
$whoops->register();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define base directories
|
// Define base directories
|
||||||
$APP_DIR = _dir(__DIR__, 'app');
|
$APP_DIR = _dir(__DIR__, 'app');
|
||||||
|
@ -4,6 +4,7 @@ parameters:
|
|||||||
inferPrivatePropertyTypeFromConstructor: true
|
inferPrivatePropertyTypeFromConstructor: true
|
||||||
level: 8
|
level: 8
|
||||||
paths:
|
paths:
|
||||||
|
- app/appConf
|
||||||
- src
|
- src
|
||||||
- ./console
|
- ./console
|
||||||
- index.php
|
- index.php
|
||||||
|
2
public/css/auto.min.css
vendored
2
public/css/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
2
public/css/light.min.css
vendored
2
public/css/light.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,463 +0,0 @@
|
|||||||
// -------------------------------------------------------------------------
|
|
||||||
// ! Base
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const matches = (elm, selector) => {
|
|
||||||
let m = (elm.document || elm.ownerDocument).querySelectorAll(selector);
|
|
||||||
let i = matches.length;
|
|
||||||
while (--i >= 0 && m.item(i) !== elm) {} return i > -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AnimeClient = {
|
|
||||||
/**
|
|
||||||
* Placeholder function
|
|
||||||
*/
|
|
||||||
noop: () => {},
|
|
||||||
/**
|
|
||||||
* DOM selector
|
|
||||||
*
|
|
||||||
* @param {string} selector - The dom selector string
|
|
||||||
* @param {object} [context]
|
|
||||||
* @return {[HTMLElement]} - array of dom elements
|
|
||||||
*/
|
|
||||||
$(selector, context = null) {
|
|
||||||
if (typeof selector !== 'string') {
|
|
||||||
return selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
context = (context !== null && context.nodeType === 1)
|
|
||||||
? context
|
|
||||||
: document;
|
|
||||||
|
|
||||||
let elements = [];
|
|
||||||
if (selector.match(/^#([\w]+$)/)) {
|
|
||||||
elements.push(document.getElementById(selector.split('#')[1]));
|
|
||||||
} else {
|
|
||||||
elements = [].slice.apply(context.querySelectorAll(selector));
|
|
||||||
}
|
|
||||||
|
|
||||||
return elements;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Does the selector exist on the current page?
|
|
||||||
*
|
|
||||||
* @param {string} selector
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
hasElement (selector) {
|
|
||||||
return AnimeClient.$(selector).length > 0;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Scroll to the top of the Page
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
scrollToTop () {
|
|
||||||
const el = AnimeClient.$('header')[0];
|
|
||||||
el.scrollIntoView(true);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Hide the selected element
|
|
||||||
*
|
|
||||||
* @param {string|Element} sel - the selector of the element to hide
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
hide (sel) {
|
|
||||||
if (typeof sel === 'string') {
|
|
||||||
sel = AnimeClient.$(sel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(sel)) {
|
|
||||||
sel.forEach(el => el.setAttribute('hidden', 'hidden'));
|
|
||||||
} else {
|
|
||||||
sel.setAttribute('hidden', 'hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* UnHide the selected element
|
|
||||||
*
|
|
||||||
* @param {string|Element} sel - the selector of the element to hide
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
show (sel) {
|
|
||||||
if (typeof sel === 'string') {
|
|
||||||
sel = AnimeClient.$(sel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(sel)) {
|
|
||||||
sel.forEach(el => el.removeAttribute('hidden'));
|
|
||||||
} else {
|
|
||||||
sel.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Display a message box
|
|
||||||
*
|
|
||||||
* @param {string} type - message type: info, error, success
|
|
||||||
* @param {string} message - the message itself
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
showMessage (type, message) {
|
|
||||||
let template =
|
|
||||||
`<div class='message ${type}'>
|
|
||||||
<span class='icon'></span>
|
|
||||||
${message}
|
|
||||||
<span class='close'></span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let sel = AnimeClient.$('.message');
|
|
||||||
if (sel[0] !== undefined) {
|
|
||||||
sel[0].remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeClient.$('header')[0].insertAdjacentHTML('beforeend', template);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Finds the closest parent element matching the passed selector
|
|
||||||
*
|
|
||||||
* @param {HTMLElement} current - the current HTMLElement
|
|
||||||
* @param {string} parentSelector - selector for the parent element
|
|
||||||
* @return {HTMLElement|null} - the parent element
|
|
||||||
*/
|
|
||||||
closestParent (current, parentSelector) {
|
|
||||||
if (Element.prototype.closest !== undefined) {
|
|
||||||
return current.closest(parentSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (current !== document.documentElement) {
|
|
||||||
if (matches(current, parentSelector)) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Generate a full url from a relative path
|
|
||||||
*
|
|
||||||
* @param {string} path - url path
|
|
||||||
* @return {string} - full url
|
|
||||||
*/
|
|
||||||
url (path) {
|
|
||||||
let uri = `//${document.location.host}`;
|
|
||||||
uri += (path.charAt(0) === '/') ? path : `/${path}`;
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Throttle execution of a function
|
|
||||||
*
|
|
||||||
* @see https://remysharp.com/2010/07/21/throttling-function-calls
|
|
||||||
* @see https://jsfiddle.net/jonathansampson/m7G64/
|
|
||||||
* @param {Number} interval - the minimum throttle time in ms
|
|
||||||
* @param {Function} fn - the function to throttle
|
|
||||||
* @param {Object} [scope] - the 'this' object for the function
|
|
||||||
* @return {Function}
|
|
||||||
*/
|
|
||||||
throttle (interval, fn, scope) {
|
|
||||||
let wait = false;
|
|
||||||
return function (...args) {
|
|
||||||
const context = scope || this;
|
|
||||||
|
|
||||||
if ( ! wait) {
|
|
||||||
fn.apply(context, args);
|
|
||||||
wait = true;
|
|
||||||
setTimeout(function() {
|
|
||||||
wait = false;
|
|
||||||
}, interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// ! Events
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function addEvent(sel, event, listener) {
|
|
||||||
// Recurse!
|
|
||||||
if (! event.match(/^([\w\-]+)$/)) {
|
|
||||||
event.split(' ').forEach((evt) => {
|
|
||||||
addEvent(sel, evt, listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sel.addEventListener(event, listener, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function delegateEvent(sel, target, event, listener) {
|
|
||||||
// Attach the listener to the parent
|
|
||||||
addEvent(sel, event, (e) => {
|
|
||||||
// Get live version of the target selector
|
|
||||||
AnimeClient.$(target, sel).forEach((element) => {
|
|
||||||
if(e.target == element) {
|
|
||||||
listener.call(element, e);
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an event listener
|
|
||||||
*
|
|
||||||
* @param {string|HTMLElement} sel - the parent selector to bind to
|
|
||||||
* @param {string} event - event name(s) to bind
|
|
||||||
* @param {string|HTMLElement|function} target - the element to directly bind the event to
|
|
||||||
* @param {function} [listener] - event listener callback
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
AnimeClient.on = (sel, event, target, listener) => {
|
|
||||||
if (listener === undefined) {
|
|
||||||
listener = target;
|
|
||||||
AnimeClient.$(sel).forEach((el) => {
|
|
||||||
addEvent(el, event, listener);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AnimeClient.$(sel).forEach((el) => {
|
|
||||||
delegateEvent(el, target, event, listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// ! Ajax
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Url encoding for non-get requests
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* @returns {string}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function ajaxSerialize(data) {
|
|
||||||
let pairs = [];
|
|
||||||
|
|
||||||
Object.keys(data).forEach((name) => {
|
|
||||||
let value = data[name].toString();
|
|
||||||
|
|
||||||
name = encodeURIComponent(name);
|
|
||||||
value = encodeURIComponent(value);
|
|
||||||
|
|
||||||
pairs.push(`${name}=${value}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return pairs.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make an ajax request
|
|
||||||
*
|
|
||||||
* Config:{
|
|
||||||
* data: // data to send with the request
|
|
||||||
* type: // http verb of the request, defaults to GET
|
|
||||||
* success: // success callback
|
|
||||||
* error: // error callback
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @param {string} url - the url to request
|
|
||||||
* @param {Object} config - the configuration object
|
|
||||||
* @return {XMLHttpRequest}
|
|
||||||
*/
|
|
||||||
AnimeClient.ajax = (url, config) => {
|
|
||||||
// Set some sane defaults
|
|
||||||
const defaultConfig = {
|
|
||||||
data: {},
|
|
||||||
type: 'GET',
|
|
||||||
dataType: '',
|
|
||||||
success: AnimeClient.noop,
|
|
||||||
mimeType: 'application/x-www-form-urlencoded',
|
|
||||||
error: AnimeClient.noop
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
...defaultConfig,
|
|
||||||
...config,
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = new XMLHttpRequest();
|
|
||||||
let method = String(config.type).toUpperCase();
|
|
||||||
|
|
||||||
if (method === 'GET') {
|
|
||||||
url += (url.match(/\?/))
|
|
||||||
? ajaxSerialize(config.data)
|
|
||||||
: `?${ajaxSerialize(config.data)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.open(method, url);
|
|
||||||
|
|
||||||
request.onreadystatechange = () => {
|
|
||||||
if (request.readyState === 4) {
|
|
||||||
let responseText = '';
|
|
||||||
|
|
||||||
if (request.responseType === 'json') {
|
|
||||||
responseText = JSON.parse(request.responseText);
|
|
||||||
} else {
|
|
||||||
responseText = request.responseText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.status > 299) {
|
|
||||||
config.error.call(null, request.status, responseText, request.response);
|
|
||||||
} else {
|
|
||||||
config.success.call(null, responseText, request.status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.dataType === 'json') {
|
|
||||||
config.data = JSON.stringify(config.data);
|
|
||||||
config.mimeType = 'application/json';
|
|
||||||
} else {
|
|
||||||
config.data = ajaxSerialize(config.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setRequestHeader('Content-Type', config.mimeType);
|
|
||||||
|
|
||||||
if (method === 'GET') {
|
|
||||||
request.send(null);
|
|
||||||
} else {
|
|
||||||
request.send(config.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return request
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do a get request
|
|
||||||
*
|
|
||||||
* @param {string} url
|
|
||||||
* @param {object|function} data
|
|
||||||
* @param {function} [callback]
|
|
||||||
* @return {XMLHttpRequest}
|
|
||||||
*/
|
|
||||||
AnimeClient.get = (url, data, callback = null) => {
|
|
||||||
if (callback === null) {
|
|
||||||
callback = data;
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnimeClient.ajax(url, {
|
|
||||||
data,
|
|
||||||
success: callback
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Event subscriptions
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
AnimeClient.on('header', 'click', '.message', hide);
|
|
||||||
AnimeClient.on('form.js-delete', 'submit', confirmDelete);
|
|
||||||
AnimeClient.on('.js-clear-cache', 'click', clearAPICache);
|
|
||||||
AnimeClient.on('.vertical-tabs input', 'change', scrollToSection);
|
|
||||||
AnimeClient.on('.media-filter', 'input', filterMedia);
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Handler functions
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the html element attached to the event
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function hide (event) {
|
|
||||||
AnimeClient.hide(event.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm deletion of an item
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function confirmDelete (event) {
|
|
||||||
const proceed = confirm('Are you ABSOLUTELY SURE you want to delete this item?');
|
|
||||||
|
|
||||||
if (proceed === false) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the API cache, and show a message if the cache is cleared
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function clearAPICache () {
|
|
||||||
AnimeClient.get('/cache_purge', () => {
|
|
||||||
AnimeClient.showMessage('success', 'Successfully purged api cache');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll to the accordion/vertical tab section just opened
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function scrollToSection (event) {
|
|
||||||
const el = event.currentTarget.parentElement;
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
|
|
||||||
const top = rect.top + window.pageYOffset;
|
|
||||||
|
|
||||||
window.scrollTo({
|
|
||||||
top,
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter an anime or manga list
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function filterMedia (event) {
|
|
||||||
const rawFilter = event.target.value;
|
|
||||||
const filter = new RegExp(rawFilter, 'i');
|
|
||||||
|
|
||||||
// console.log('Filtering items by: ', filter);
|
|
||||||
|
|
||||||
if (rawFilter !== '') {
|
|
||||||
// Filter the cover view
|
|
||||||
AnimeClient.$('article.media').forEach(article => {
|
|
||||||
const titleLink = AnimeClient.$('.name a', article)[0];
|
|
||||||
const title = String(titleLink.textContent).trim();
|
|
||||||
if ( ! filter.test(title)) {
|
|
||||||
AnimeClient.hide(article);
|
|
||||||
} else {
|
|
||||||
AnimeClient.show(article);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter the list view
|
|
||||||
AnimeClient.$('table.media-wrap tbody tr').forEach(tr => {
|
|
||||||
const titleCell = AnimeClient.$('td.align-left', tr)[0];
|
|
||||||
const titleLink = AnimeClient.$('a', titleCell)[0];
|
|
||||||
const linkTitle = String(titleLink.textContent).trim();
|
|
||||||
const textTitle = String(titleCell.textContent).trim();
|
|
||||||
if ( ! (filter.test(linkTitle) || filter.test(textTitle))) {
|
|
||||||
AnimeClient.hide(tr);
|
|
||||||
} else {
|
|
||||||
AnimeClient.show(tr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AnimeClient.show('article.media');
|
|
||||||
AnimeClient.show('table.media-wrap tbody tr');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
|
||||||
console.log('Service worker registered', reg.scope);
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Failed to register service worker', error);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,769 +0,0 @@
|
|||||||
// -------------------------------------------------------------------------
|
|
||||||
// ! Base
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const matches = (elm, selector) => {
|
|
||||||
let m = (elm.document || elm.ownerDocument).querySelectorAll(selector);
|
|
||||||
let i = matches.length;
|
|
||||||
while (--i >= 0 && m.item(i) !== elm) {} return i > -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AnimeClient = {
|
|
||||||
/**
|
|
||||||
* Placeholder function
|
|
||||||
*/
|
|
||||||
noop: () => {},
|
|
||||||
/**
|
|
||||||
* DOM selector
|
|
||||||
*
|
|
||||||
* @param {string} selector - The dom selector string
|
|
||||||
* @param {object} [context]
|
|
||||||
* @return {[HTMLElement]} - array of dom elements
|
|
||||||
*/
|
|
||||||
$(selector, context = null) {
|
|
||||||
if (typeof selector !== 'string') {
|
|
||||||
return selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
context = (context !== null && context.nodeType === 1)
|
|
||||||
? context
|
|
||||||
: document;
|
|
||||||
|
|
||||||
let elements = [];
|
|
||||||
if (selector.match(/^#([\w]+$)/)) {
|
|
||||||
elements.push(document.getElementById(selector.split('#')[1]));
|
|
||||||
} else {
|
|
||||||
elements = [].slice.apply(context.querySelectorAll(selector));
|
|
||||||
}
|
|
||||||
|
|
||||||
return elements;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Does the selector exist on the current page?
|
|
||||||
*
|
|
||||||
* @param {string} selector
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
hasElement (selector) {
|
|
||||||
return AnimeClient.$(selector).length > 0;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Scroll to the top of the Page
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
scrollToTop () {
|
|
||||||
const el = AnimeClient.$('header')[0];
|
|
||||||
el.scrollIntoView(true);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Hide the selected element
|
|
||||||
*
|
|
||||||
* @param {string|Element} sel - the selector of the element to hide
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
hide (sel) {
|
|
||||||
if (typeof sel === 'string') {
|
|
||||||
sel = AnimeClient.$(sel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(sel)) {
|
|
||||||
sel.forEach(el => el.setAttribute('hidden', 'hidden'));
|
|
||||||
} else {
|
|
||||||
sel.setAttribute('hidden', 'hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* UnHide the selected element
|
|
||||||
*
|
|
||||||
* @param {string|Element} sel - the selector of the element to hide
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
show (sel) {
|
|
||||||
if (typeof sel === 'string') {
|
|
||||||
sel = AnimeClient.$(sel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(sel)) {
|
|
||||||
sel.forEach(el => el.removeAttribute('hidden'));
|
|
||||||
} else {
|
|
||||||
sel.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Display a message box
|
|
||||||
*
|
|
||||||
* @param {string} type - message type: info, error, success
|
|
||||||
* @param {string} message - the message itself
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
showMessage (type, message) {
|
|
||||||
let template =
|
|
||||||
`<div class='message ${type}'>
|
|
||||||
<span class='icon'></span>
|
|
||||||
${message}
|
|
||||||
<span class='close'></span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let sel = AnimeClient.$('.message');
|
|
||||||
if (sel[0] !== undefined) {
|
|
||||||
sel[0].remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeClient.$('header')[0].insertAdjacentHTML('beforeend', template);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Finds the closest parent element matching the passed selector
|
|
||||||
*
|
|
||||||
* @param {HTMLElement} current - the current HTMLElement
|
|
||||||
* @param {string} parentSelector - selector for the parent element
|
|
||||||
* @return {HTMLElement|null} - the parent element
|
|
||||||
*/
|
|
||||||
closestParent (current, parentSelector) {
|
|
||||||
if (Element.prototype.closest !== undefined) {
|
|
||||||
return current.closest(parentSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (current !== document.documentElement) {
|
|
||||||
if (matches(current, parentSelector)) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Generate a full url from a relative path
|
|
||||||
*
|
|
||||||
* @param {string} path - url path
|
|
||||||
* @return {string} - full url
|
|
||||||
*/
|
|
||||||
url (path) {
|
|
||||||
let uri = `//${document.location.host}`;
|
|
||||||
uri += (path.charAt(0) === '/') ? path : `/${path}`;
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Throttle execution of a function
|
|
||||||
*
|
|
||||||
* @see https://remysharp.com/2010/07/21/throttling-function-calls
|
|
||||||
* @see https://jsfiddle.net/jonathansampson/m7G64/
|
|
||||||
* @param {Number} interval - the minimum throttle time in ms
|
|
||||||
* @param {Function} fn - the function to throttle
|
|
||||||
* @param {Object} [scope] - the 'this' object for the function
|
|
||||||
* @return {Function}
|
|
||||||
*/
|
|
||||||
throttle (interval, fn, scope) {
|
|
||||||
let wait = false;
|
|
||||||
return function (...args) {
|
|
||||||
const context = scope || this;
|
|
||||||
|
|
||||||
if ( ! wait) {
|
|
||||||
fn.apply(context, args);
|
|
||||||
wait = true;
|
|
||||||
setTimeout(function() {
|
|
||||||
wait = false;
|
|
||||||
}, interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// ! Events
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function addEvent(sel, event, listener) {
|
|
||||||
// Recurse!
|
|
||||||
if (! event.match(/^([\w\-]+)$/)) {
|
|
||||||
event.split(' ').forEach((evt) => {
|
|
||||||
addEvent(sel, evt, listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sel.addEventListener(event, listener, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function delegateEvent(sel, target, event, listener) {
|
|
||||||
// Attach the listener to the parent
|
|
||||||
addEvent(sel, event, (e) => {
|
|
||||||
// Get live version of the target selector
|
|
||||||
AnimeClient.$(target, sel).forEach((element) => {
|
|
||||||
if(e.target == element) {
|
|
||||||
listener.call(element, e);
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an event listener
|
|
||||||
*
|
|
||||||
* @param {string|HTMLElement} sel - the parent selector to bind to
|
|
||||||
* @param {string} event - event name(s) to bind
|
|
||||||
* @param {string|HTMLElement|function} target - the element to directly bind the event to
|
|
||||||
* @param {function} [listener] - event listener callback
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
AnimeClient.on = (sel, event, target, listener) => {
|
|
||||||
if (listener === undefined) {
|
|
||||||
listener = target;
|
|
||||||
AnimeClient.$(sel).forEach((el) => {
|
|
||||||
addEvent(el, event, listener);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AnimeClient.$(sel).forEach((el) => {
|
|
||||||
delegateEvent(el, target, event, listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// ! Ajax
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Url encoding for non-get requests
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* @returns {string}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function ajaxSerialize(data) {
|
|
||||||
let pairs = [];
|
|
||||||
|
|
||||||
Object.keys(data).forEach((name) => {
|
|
||||||
let value = data[name].toString();
|
|
||||||
|
|
||||||
name = encodeURIComponent(name);
|
|
||||||
value = encodeURIComponent(value);
|
|
||||||
|
|
||||||
pairs.push(`${name}=${value}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return pairs.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make an ajax request
|
|
||||||
*
|
|
||||||
* Config:{
|
|
||||||
* data: // data to send with the request
|
|
||||||
* type: // http verb of the request, defaults to GET
|
|
||||||
* success: // success callback
|
|
||||||
* error: // error callback
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @param {string} url - the url to request
|
|
||||||
* @param {Object} config - the configuration object
|
|
||||||
* @return {XMLHttpRequest}
|
|
||||||
*/
|
|
||||||
AnimeClient.ajax = (url, config) => {
|
|
||||||
// Set some sane defaults
|
|
||||||
const defaultConfig = {
|
|
||||||
data: {},
|
|
||||||
type: 'GET',
|
|
||||||
dataType: '',
|
|
||||||
success: AnimeClient.noop,
|
|
||||||
mimeType: 'application/x-www-form-urlencoded',
|
|
||||||
error: AnimeClient.noop
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
...defaultConfig,
|
|
||||||
...config,
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = new XMLHttpRequest();
|
|
||||||
let method = String(config.type).toUpperCase();
|
|
||||||
|
|
||||||
if (method === 'GET') {
|
|
||||||
url += (url.match(/\?/))
|
|
||||||
? ajaxSerialize(config.data)
|
|
||||||
: `?${ajaxSerialize(config.data)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.open(method, url);
|
|
||||||
|
|
||||||
request.onreadystatechange = () => {
|
|
||||||
if (request.readyState === 4) {
|
|
||||||
let responseText = '';
|
|
||||||
|
|
||||||
if (request.responseType === 'json') {
|
|
||||||
responseText = JSON.parse(request.responseText);
|
|
||||||
} else {
|
|
||||||
responseText = request.responseText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.status > 299) {
|
|
||||||
config.error.call(null, request.status, responseText, request.response);
|
|
||||||
} else {
|
|
||||||
config.success.call(null, responseText, request.status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.dataType === 'json') {
|
|
||||||
config.data = JSON.stringify(config.data);
|
|
||||||
config.mimeType = 'application/json';
|
|
||||||
} else {
|
|
||||||
config.data = ajaxSerialize(config.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setRequestHeader('Content-Type', config.mimeType);
|
|
||||||
|
|
||||||
if (method === 'GET') {
|
|
||||||
request.send(null);
|
|
||||||
} else {
|
|
||||||
request.send(config.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return request
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do a get request
|
|
||||||
*
|
|
||||||
* @param {string} url
|
|
||||||
* @param {object|function} data
|
|
||||||
* @param {function} [callback]
|
|
||||||
* @return {XMLHttpRequest}
|
|
||||||
*/
|
|
||||||
AnimeClient.get = (url, data, callback = null) => {
|
|
||||||
if (callback === null) {
|
|
||||||
callback = data;
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnimeClient.ajax(url, {
|
|
||||||
data,
|
|
||||||
success: callback
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Event subscriptions
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
AnimeClient.on('header', 'click', '.message', hide);
|
|
||||||
AnimeClient.on('form.js-delete', 'submit', confirmDelete);
|
|
||||||
AnimeClient.on('.js-clear-cache', 'click', clearAPICache);
|
|
||||||
AnimeClient.on('.vertical-tabs input', 'change', scrollToSection);
|
|
||||||
AnimeClient.on('.media-filter', 'input', filterMedia);
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Handler functions
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the html element attached to the event
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function hide (event) {
|
|
||||||
AnimeClient.hide(event.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm deletion of an item
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function confirmDelete (event) {
|
|
||||||
const proceed = confirm('Are you ABSOLUTELY SURE you want to delete this item?');
|
|
||||||
|
|
||||||
if (proceed === false) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the API cache, and show a message if the cache is cleared
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function clearAPICache () {
|
|
||||||
AnimeClient.get('/cache_purge', () => {
|
|
||||||
AnimeClient.showMessage('success', 'Successfully purged api cache');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll to the accordion/vertical tab section just opened
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function scrollToSection (event) {
|
|
||||||
const el = event.currentTarget.parentElement;
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
|
|
||||||
const top = rect.top + window.pageYOffset;
|
|
||||||
|
|
||||||
window.scrollTo({
|
|
||||||
top,
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter an anime or manga list
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function filterMedia (event) {
|
|
||||||
const rawFilter = event.target.value;
|
|
||||||
const filter = new RegExp(rawFilter, 'i');
|
|
||||||
|
|
||||||
// console.log('Filtering items by: ', filter);
|
|
||||||
|
|
||||||
if (rawFilter !== '') {
|
|
||||||
// Filter the cover view
|
|
||||||
AnimeClient.$('article.media').forEach(article => {
|
|
||||||
const titleLink = AnimeClient.$('.name a', article)[0];
|
|
||||||
const title = String(titleLink.textContent).trim();
|
|
||||||
if ( ! filter.test(title)) {
|
|
||||||
AnimeClient.hide(article);
|
|
||||||
} else {
|
|
||||||
AnimeClient.show(article);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter the list view
|
|
||||||
AnimeClient.$('table.media-wrap tbody tr').forEach(tr => {
|
|
||||||
const titleCell = AnimeClient.$('td.align-left', tr)[0];
|
|
||||||
const titleLink = AnimeClient.$('a', titleCell)[0];
|
|
||||||
const linkTitle = String(titleLink.textContent).trim();
|
|
||||||
const textTitle = String(titleCell.textContent).trim();
|
|
||||||
if ( ! (filter.test(linkTitle) || filter.test(textTitle))) {
|
|
||||||
AnimeClient.hide(tr);
|
|
||||||
} else {
|
|
||||||
AnimeClient.show(tr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
AnimeClient.show('article.media');
|
|
||||||
AnimeClient.show('table.media-wrap tbody tr');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
|
||||||
console.log('Service worker registered', reg.scope);
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Failed to register service worker', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
// Var is intentional
|
|
||||||
var hidden = null;
|
|
||||||
var visibilityChange = null;
|
|
||||||
|
|
||||||
if (typeof document.hidden !== "undefined") {
|
|
||||||
hidden = "hidden";
|
|
||||||
visibilityChange = "visibilitychange";
|
|
||||||
} else if (typeof document.msHidden !== "undefined") {
|
|
||||||
hidden = "msHidden";
|
|
||||||
visibilityChange = "msvisibilitychange";
|
|
||||||
} else if (typeof document.webkitHidden !== "undefined") {
|
|
||||||
hidden = "webkitHidden";
|
|
||||||
visibilityChange = "webkitvisibilitychange";
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleVisibilityChange() {
|
|
||||||
// Check the user's session to see if they are currently logged-in
|
|
||||||
// when the page becomes visible
|
|
||||||
if ( ! document[hidden]) {
|
|
||||||
AnimeClient.get('/heartbeat', (beat) => {
|
|
||||||
const status = JSON.parse(beat);
|
|
||||||
|
|
||||||
// If the session is expired, immediately reload so that
|
|
||||||
// you can't attempt to do an action that requires authentication
|
|
||||||
if (status.hasAuth !== true) {
|
|
||||||
document.removeEventListener(visibilityChange, handleVisibilityChange, false);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hidden === null) {
|
|
||||||
console.info('Page visibility API not supported, JS session check will not work');
|
|
||||||
} else {
|
|
||||||
document.addEventListener(visibilityChange, handleVisibilityChange, false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Click on hidden MAL checkbox so
|
|
||||||
// that MAL id is passed
|
|
||||||
AnimeClient.on('main', 'change', '.big-check', (e) => {
|
|
||||||
const id = e.target.id;
|
|
||||||
document.getElementById(`mal_${id}`).checked = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
function renderAnimeSearchResults (data) {
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
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="${item.mal_id}" />
|
|
||||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" />
|
|
||||||
<label for="${item.slug}">
|
|
||||||
<picture width="220">
|
|
||||||
<source srcset="/public/images/anime/${item.id}.webp" type="image/webp" />
|
|
||||||
<source srcset="/public/images/anime/${item.id}.jpg" type="image/jpeg" />
|
|
||||||
<img src="/public/images/anime/${item.id}.jpg" alt="" width="220" />
|
|
||||||
</picture>
|
|
||||||
<span class="name">
|
|
||||||
${item.canonicalTitle}<br />
|
|
||||||
<small>${titles}</small>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<span class="edit">
|
|
||||||
<a class="bracketed" href="/anime/details/${item.slug}">Info Page</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return results.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMangaSearchResults (data) {
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
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="${item.mal_id}" />
|
|
||||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" />
|
|
||||||
<label for="${item.slug}">
|
|
||||||
<picture width="220">
|
|
||||||
<source srcset="/public/images/manga/${item.id}.webp" type="image/webp" />
|
|
||||||
<source srcset="/public/images/manga/${item.id}.jpg" type="image/jpeg" />
|
|
||||||
<img src="/public/images/manga/${item.id}.jpg" alt="" width="220" />
|
|
||||||
</picture>
|
|
||||||
<span class="name">
|
|
||||||
${item.canonicalTitle}<br />
|
|
||||||
<small>${titles}</small>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<span class="edit">
|
|
||||||
<a class="bracketed" href="/manga/details/${item.slug}">Info Page</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return results.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
const search = (query) => {
|
|
||||||
// Show the loader
|
|
||||||
AnimeClient.show('.cssload-loader');
|
|
||||||
|
|
||||||
// Do the api search
|
|
||||||
return AnimeClient.get(AnimeClient.url('/anime-collection/search'), { query }, (searchResults, status) => {
|
|
||||||
searchResults = JSON.parse(searchResults);
|
|
||||||
|
|
||||||
// Hide the loader
|
|
||||||
AnimeClient.hide('.cssload-loader');
|
|
||||||
|
|
||||||
// Show the results
|
|
||||||
AnimeClient.$('#series-list')[ 0 ].innerHTML = renderAnimeSearchResults(searchResults);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (AnimeClient.hasElement('.anime #search')) {
|
|
||||||
let prevRequest = null;
|
|
||||||
|
|
||||||
AnimeClient.on('#search', 'input', AnimeClient.throttle(250, (e) => {
|
|
||||||
const query = encodeURIComponent(e.target.value);
|
|
||||||
if (query === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevRequest !== null) {
|
|
||||||
prevRequest.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
prevRequest = search(query);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action to increment episode count
|
|
||||||
AnimeClient.on('body.anime.list', 'click', '.plus-one', (e) => {
|
|
||||||
let parentSel = AnimeClient.closestParent(e.target, 'article');
|
|
||||||
let watchedCount = parseInt(AnimeClient.$('.completed_number', parentSel)[ 0 ].textContent, 10) || 0;
|
|
||||||
let totalCount = parseInt(AnimeClient.$('.total_number', parentSel)[ 0 ].textContent, 10);
|
|
||||||
let title = AnimeClient.$('.name a', parentSel)[ 0 ].textContent;
|
|
||||||
|
|
||||||
// Setup the update data
|
|
||||||
let data = {
|
|
||||||
id: parentSel.dataset.kitsuId,
|
|
||||||
mal_id: parentSel.dataset.malId,
|
|
||||||
data: {
|
|
||||||
progress: watchedCount + 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the episode count is 0, and incremented,
|
|
||||||
// change status to currently watching
|
|
||||||
if (isNaN(watchedCount) || watchedCount === 0) {
|
|
||||||
data.data.status = 'CURRENT';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you increment at the last episode, mark as completed
|
|
||||||
if ((!isNaN(watchedCount)) && (watchedCount + 1) === totalCount) {
|
|
||||||
data.data.status = 'COMPLETED';
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeClient.show('#loading-shadow');
|
|
||||||
|
|
||||||
// okay, lets actually make some changes!
|
|
||||||
AnimeClient.ajax(AnimeClient.url('/anime/increment'), {
|
|
||||||
data,
|
|
||||||
dataType: 'json',
|
|
||||||
type: 'POST',
|
|
||||||
success: (res) => {
|
|
||||||
const resData = JSON.parse(res);
|
|
||||||
|
|
||||||
if (resData.errors) {
|
|
||||||
AnimeClient.hide('#loading-shadow');
|
|
||||||
AnimeClient.showMessage('error', `Failed to update ${title}. `);
|
|
||||||
AnimeClient.scrollToTop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
|
|
||||||
AnimeClient.hide(parentSel);
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeClient.hide('#loading-shadow');
|
|
||||||
|
|
||||||
AnimeClient.showMessage('success', `Successfully updated ${title}`);
|
|
||||||
AnimeClient.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
|
||||||
AnimeClient.scrollToTop();
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
AnimeClient.hide('#loading-shadow');
|
|
||||||
AnimeClient.showMessage('error', `Failed to update ${title}. `);
|
|
||||||
AnimeClient.scrollToTop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const search$1 = (query) => {
|
|
||||||
AnimeClient.show('.cssload-loader');
|
|
||||||
return AnimeClient.get(AnimeClient.url('/manga/search'), { query }, (searchResults, status) => {
|
|
||||||
searchResults = JSON.parse(searchResults);
|
|
||||||
AnimeClient.hide('.cssload-loader');
|
|
||||||
AnimeClient.$('#series-list')[ 0 ].innerHTML = renderMangaSearchResults(searchResults);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (AnimeClient.hasElement('.manga #search')) {
|
|
||||||
let prevRequest = null;
|
|
||||||
|
|
||||||
AnimeClient.on('#search', 'input', AnimeClient.throttle(250, (e) => {
|
|
||||||
let query = encodeURIComponent(e.target.value);
|
|
||||||
if (query === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevRequest !== null) {
|
|
||||||
prevRequest.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
prevRequest = search$1(query);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Javascript for editing manga, if logged in
|
|
||||||
*/
|
|
||||||
AnimeClient.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|
||||||
let thisSel = e.target;
|
|
||||||
let parentSel = AnimeClient.closestParent(e.target, 'article');
|
|
||||||
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
|
||||||
let completed = parseInt(AnimeClient.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
|
||||||
let total = parseInt(AnimeClient.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
|
||||||
let mangaName = AnimeClient.$('.name', parentSel)[ 0 ].textContent;
|
|
||||||
|
|
||||||
if (isNaN(completed)) {
|
|
||||||
completed = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the update data
|
|
||||||
let data = {
|
|
||||||
id: parentSel.dataset.kitsuId,
|
|
||||||
mal_id: parentSel.dataset.malId,
|
|
||||||
data: {
|
|
||||||
progress: completed
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the episode count is 0, and incremented,
|
|
||||||
// change status to currently reading
|
|
||||||
if (isNaN(completed) || completed === 0) {
|
|
||||||
data.data.status = 'CURRENT';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you increment at the last chapter, mark as completed
|
|
||||||
if ((!isNaN(completed)) && (completed + 1) === total) {
|
|
||||||
data.data.status = 'COMPLETED';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the total count
|
|
||||||
data.data.progress = ++completed;
|
|
||||||
|
|
||||||
AnimeClient.show('#loading-shadow');
|
|
||||||
|
|
||||||
AnimeClient.ajax(AnimeClient.url('/manga/increment'), {
|
|
||||||
data,
|
|
||||||
dataType: 'json',
|
|
||||||
type: 'POST',
|
|
||||||
mimeType: 'application/json',
|
|
||||||
success: () => {
|
|
||||||
if (String(data.data.status).toUpperCase() === '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
14
public/js/anon.min.js
vendored
@ -1,14 +0,0 @@
|
|||||||
(function(){var matches=function(elm,selector){var m=(elm.document||elm.ownerDocument).querySelectorAll(selector);var i=matches.length;while(--i>=0&&m.item(i)!==elm);return i>-1};var AnimeClient={noop:function(){},$:function(selector,context){context=context===undefined?null:context;if(typeof selector!=="string")return selector;context=context!==null&&context.nodeType===1?context:document;var elements=[];if(selector.match(/^#([\w]+$)/))elements.push(document.getElementById(selector.split("#")[1]));
|
|
||||||
else elements=[].slice.apply(context.querySelectorAll(selector));return elements},hasElement:function(selector){return AnimeClient.$(selector).length>0},scrollToTop:function(){var el=AnimeClient.$("header")[0];el.scrollIntoView(true)},hide:function(sel){if(typeof sel==="string")sel=AnimeClient.$(sel);if(Array.isArray(sel))sel.forEach(function(el){return el.setAttribute("hidden","hidden")});else sel.setAttribute("hidden","hidden")},show:function(sel){if(typeof sel==="string")sel=AnimeClient.$(sel);
|
|
||||||
if(Array.isArray(sel))sel.forEach(function(el){return el.removeAttribute("hidden")});else sel.removeAttribute("hidden")},showMessage:function(type,message){var template="<div class='message "+type+"'>\n\t\t\t\t<span class='icon'></span>\n\t\t\t\t"+message+"\n\t\t\t\t<span class='close'></span>\n\t\t\t</div>";var sel=AnimeClient.$(".message");if(sel[0]!==undefined)sel[0].remove();AnimeClient.$("header")[0].insertAdjacentHTML("beforeend",template)},closestParent:function(current,parentSelector){if(Element.prototype.closest!==
|
|
||||||
undefined)return current.closest(parentSelector);while(current!==document.documentElement){if(matches(current,parentSelector))return current;current=current.parentElement}return null},url:function(path){var uri="//"+document.location.host;uri+=path.charAt(0)==="/"?path:"/"+path;return uri},throttle:function(interval,fn,scope){var wait=false;return function(args){var $jscomp$restParams=[];for(var $jscomp$restIndex=0;$jscomp$restIndex<arguments.length;++$jscomp$restIndex)$jscomp$restParams[$jscomp$restIndex-
|
|
||||||
0]=arguments[$jscomp$restIndex];{var args$0=$jscomp$restParams;var context=scope||this;if(!wait){fn.apply(context,args$0);wait=true;setTimeout(function(){wait=false},interval)}}}}};function addEvent(sel,event,listener){if(!event.match(/^([\w\-]+)$/))event.split(" ").forEach(function(evt){addEvent(sel,evt,listener)});sel.addEventListener(event,listener,false)}function delegateEvent(sel,target,event,listener){addEvent(sel,event,function(e){AnimeClient.$(target,sel).forEach(function(element){if(e.target==
|
|
||||||
element){listener.call(element,e);e.stopPropagation()}})})}AnimeClient.on=function(sel,event,target,listener){if(listener===undefined){listener=target;AnimeClient.$(sel).forEach(function(el){addEvent(el,event,listener)})}else AnimeClient.$(sel).forEach(function(el){delegateEvent(el,target,event,listener)})};function ajaxSerialize(data){var pairs=[];Object.keys(data).forEach(function(name){var value=data[name].toString();name=encodeURIComponent(name);value=encodeURIComponent(value);pairs.push(name+
|
|
||||||
"="+value)});return pairs.join("&")}AnimeClient.ajax=function(url,config){var defaultConfig={data:{},type:"GET",dataType:"",success:AnimeClient.noop,mimeType:"application/x-www-form-urlencoded",error:AnimeClient.noop};config=Object.assign({},defaultConfig,config);var request=new XMLHttpRequest;var method=String(config.type).toUpperCase();if(method==="GET")url+=url.match(/\?/)?ajaxSerialize(config.data):"?"+ajaxSerialize(config.data);request.open(method,url);request.onreadystatechange=function(){if(request.readyState===
|
|
||||||
4){var responseText="";if(request.responseType==="json")responseText=JSON.parse(request.responseText);else responseText=request.responseText;if(request.status>299)config.error.call(null,request.status,responseText,request.response);else config.success.call(null,responseText,request.status)}};if(config.dataType==="json"){config.data=JSON.stringify(config.data);config.mimeType="application/json"}else config.data=ajaxSerialize(config.data);request.setRequestHeader("Content-Type",config.mimeType);if(method===
|
|
||||||
"GET")request.send(null);else request.send(config.data);return request};AnimeClient.get=function(url,data,callback){callback=callback===undefined?null:callback;if(callback===null){callback=data;data={}}return AnimeClient.ajax(url,{data:data,success:callback})};AnimeClient.on("header","click",".message",hide);AnimeClient.on("form.js-delete","submit",confirmDelete);AnimeClient.on(".js-clear-cache","click",clearAPICache);AnimeClient.on(".vertical-tabs input","change",scrollToSection);AnimeClient.on(".media-filter",
|
|
||||||
"input",filterMedia);function hide(event){AnimeClient.hide(event.target)}function confirmDelete(event){var proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();event.stopPropagation()}}function clearAPICache(){AnimeClient.get("/cache_purge",function(){AnimeClient.showMessage("success","Successfully purged api cache")})}function scrollToSection(event){var el=event.currentTarget.parentElement;var rect=el.getBoundingClientRect();var top=
|
|
||||||
rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})}function filterMedia(event){var rawFilter=event.target.value;var filter=new RegExp(rawFilter,"i");if(rawFilter!==""){AnimeClient.$("article.media").forEach(function(article){var titleLink=AnimeClient.$(".name a",article)[0];var title=String(titleLink.textContent).trim();if(!filter.test(title))AnimeClient.hide(article);else AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(function(tr){var titleCell=
|
|
||||||
AnimeClient.$("td.align-left",tr)[0];var titleLink=AnimeClient.$("a",titleCell)[0];var linkTitle=String(titleLink.textContent).trim();var textTitle=String(titleCell.textContent).trim();if(!(filter.test(linkTitle)||filter.test(textTitle)))AnimeClient.hide(tr);else AnimeClient.show(tr)})}else{AnimeClient.show("article.media");AnimeClient.show("table.media-wrap tbody tr")}}if("serviceWorker"in navigator)navigator.serviceWorker.register("/sw.js").then(function(reg){console.log("Service worker registered",
|
|
||||||
reg.scope)})["catch"](function(error){console.error("Failed to register service worker",error)})})()
|
|
||||||
//# sourceMappingURL=anon.min.js.map
|
|
File diff suppressed because one or more lines are too long
28
public/js/scripts.min.js
vendored
28
public/js/scripts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5
public/js/tables.min.js
vendored
5
public/js/tables.min.js
vendored
@ -1,4 +1 @@
|
|||||||
(function(){var LightTableSorter=function(){var th=null;var cellIndex=null;var order="";var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);var n=parseInt(textA,10);if(n){textA=n;textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return-1;return 0};var toggle=function(){var c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=
|
var LightTableSorter=function(){var th=null;var cellIndex=null;var order='';var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase();};var sort=function(a,b){var textA=text(a);var textB=text(b);var n=parseInt(textA,10);if(n){textA=n;textB=parseInt(textB,10);}if(textA>textB)return 1;if(textA<textB)return -1;return 0;};var toggle=function(){var c=order!=='sorting-asc'?'sorting-asc':'sorting-desc';th.className=(th.className.replace(order,'')+' '+c).trim();return order=c;};var reset=function(){th.classList.remove('sorting-asc','sorting-desc');th.classList.add('sorting');return order='';};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==='th'){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName('tbody')[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==='sorting-asc')rows.reverse();toggle();tbody.innerHtml='';rows.forEach(function(row){tbody.appendChild(row);});}}};return {init:function(){var ths=document.getElementsByTagName('th');var results=[];for(var i=0,len=ths.length;i<len;i++){var th1=ths[i];th1.classList.add('sorting');results.push(th1.onclick=onClickEvent);}return results;}};}();LightTableSorter.init();
|
||||||
c};var reset=function(){th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName("tbody")[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(function(row){tbody.appendChild(row)})}}};
|
|
||||||
return{init:function(){var ths=document.getElementsByTagName("th");var results=[];for(var i=0,len=ths.length;i<len;i++){var th$0=ths[i];th$0.classList.add("sorting");results.push(th$0.onclick=onClickEvent)}return results}}}();LightTableSorter.init()})()
|
|
||||||
//# sourceMappingURL=tables.min.js.map
|
|
@ -1 +1 @@
|
|||||||
{"version":3,"file":"tables.min.js.map","sources":["../../frontEndSrc/js/base/sort-tables.js"],"sourcesContent":["const LightTableSorter = (() => {\n\tlet th = null;\n\tlet cellIndex = null;\n\tlet order = '';\n\tconst text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();\n\tconst sort = (a, b) => {\n\t\tlet textA = text(a);\n\t\tlet textB = text(b);\n\t\tconst n = parseInt(textA, 10);\n\t\tif (n) {\n\t\t\ttextA = n;\n\t\t\ttextB = parseInt(textB, 10);\n\t\t}\n\t\tif (textA > textB) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (textA < textB) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t};\n\tconst toggle = () => {\n\t\tconst c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';\n\t\tth.className = (th.className.replace(order, '') + ' ' + c).trim();\n\t\treturn order = c;\n\t};\n\tconst reset = () => {\n\t\tth.classList.remove('sorting-asc', 'sorting-desc');\n\t\tth.classList.add('sorting');\n\t\treturn order = '';\n\t};\n\tconst onClickEvent = (e) => {\n\t\tif (th && (cellIndex !== e.target.cellIndex)) {\n\t\t\treset();\n\t\t}\n\t\tth = e.target;\n\t\tif (th.nodeName.toLowerCase() === 'th') {\n\t\t\tcellIndex = th.cellIndex;\n\t\t\tconst tbody = th.offsetParent.getElementsByTagName('tbody')[0];\n\t\t\tlet rows = Array.from(tbody.rows);\n\t\t\tif (rows) {\n\t\t\t\trows.sort(sort);\n\t\t\t\tif (order === 'sorting-asc') {\n\t\t\t\t\trows.reverse();\n\t\t\t\t}\n\t\t\t\ttoggle();\n\t\t\t\ttbody.innerHtml = '';\n\n\t\t\t\trows.forEach(row => {\n\t\t\t\t\ttbody.appendChild(row);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\treturn {\n\t\tinit: () => {\n\t\t\tlet ths = document.getElementsByTagName('th');\n\t\t\tlet results = [];\n\t\t\tfor (let i = 0, len = ths.length; i < len; i++) {\n\t\t\t\tlet th = ths[i];\n\t\t\t\tth.classList.add('sorting');\n\t\t\t\tresults.push(th.onclick = onClickEvent);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t};\n})();\n\nLightTableSorter.init();"],"names":["th","cellIndex","order","text","row","cells","item","textContent","toLowerCase","sort","a","b","textA","textB","n","parseInt","toggle","c","className","trim","replace","reset","classList","remove","add","onClickEvent","e","target","nodeName","tbody","offsetParent","getElementsByTagName","rows","Array","from","reverse","innerHtml","forEach","appendChild","init","ths","document","results","i","len","length","push","onclick","LightTableSorter"],"mappings":"YAAA,gCACC,IAAIA,GAAK,IACT,KAAIC,UAAY,IAChB,KAAIC,MAAQ,EACZ,KAAMC,KAAOA,QAAA,CAACC,GAAD,CAAS,CAAA,MAAAA,IAAAC,MAAAC,KAAA,CAAeL,SAAf,CAAAM,YAAAC,YAAA,EAAA,CACtB,KAAMC,KAAOA,QAAA,CAACC,CAAD,CAAIC,CAAJ,CAAU,CACtB,IAAIC,MAAQT,IAAA,CAAKO,CAAL,CACZ,KAAIG,MAAQV,IAAA,CAAKQ,CAAL,CACZ,KAAMG,EAAIC,QAAA,CAASH,KAAT,CAAgB,EAAhB,CACV,IAAIE,CAAJ,CAAO,CACNF,KAAA,CAAQE,CACRD,MAAA,CAAQE,QAAA,CAASF,KAAT,CAAgB,EAAhB,CAFF,CAIP,GAAID,KAAJ,CAAYC,KAAZ,CACC,MAAO,EAER,IAAID,KAAJ,CAAYC,KAAZ,CACC,MAAO,EAER,OAAO,EAde,CAgBvB,KAAMG,OAASA,QAAA,EAAM,CACpB,IAAMC,EAAIf,KAAA,GAAU,aAAV,CAA0B,aAA1B,CAA0C,cACpDF,GAAAkB,UAAA,CAAeC,CAACnB,EAAAkB,UAAAE,QAAA,CAAqBlB,KAArB,CAA4B,EAA5B,CAADiB,CAAmC,GAAnCA,CAAyCF,CAAzCE,MAAA,EACf,OAAOjB,MAAP;AAAee,CAHK,CAKrB,KAAMI,MAAQA,QAAA,EAAM,CACnBrB,EAAAsB,UAAAC,OAAA,CAAoB,aAApB,CAAmC,cAAnC,CACAvB,GAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACA,OAAOtB,MAAP,CAAe,EAHI,CAKpB,KAAMuB,aAAeA,QAAA,CAACC,CAAD,CAAO,CAC3B,GAAI1B,EAAJ,EAAWC,SAAX,GAAyByB,CAAAC,OAAA1B,UAAzB,CACCoB,KAAA,EAEDrB,GAAA,CAAK0B,CAAAC,OACL,IAAI3B,EAAA4B,SAAApB,YAAA,EAAJ,GAAkC,IAAlC,CAAwC,CACvCP,SAAA,CAAYD,EAAAC,UACZ,KAAM4B,MAAQ7B,EAAA8B,aAAAC,qBAAA,CAAqC,OAArC,CAAA,CAA8C,CAA9C,CACd,KAAIC,KAAOC,KAAAC,KAAA,CAAWL,KAAAG,KAAX,CACX,IAAIA,IAAJ,CAAU,CACTA,IAAAvB,KAAA,CAAUA,IAAV,CACA,IAAIP,KAAJ,GAAc,aAAd,CACC8B,IAAAG,QAAA,EAEDnB,OAAA,EACAa,MAAAO,UAAA,CAAkB,EAElBJ,KAAAK,QAAA,CAAa,QAAA,CAAAjC,GAAA,CAAO,CACnByB,KAAAS,YAAA,CAAkBlC,GAAlB,CADmB,CAApB,CARS,CAJ6B,CALb,CAuB5B;MAAO,CACNmC,KAAMA,QAAA,EAAM,CACX,IAAIC,IAAMC,QAAAV,qBAAA,CAA8B,IAA9B,CACV,KAAIW,QAAU,EACd,KAAK,IAAIC,EAAI,CAAR,CAAWC,IAAMJ,GAAAK,OAAtB,CAAkCF,CAAlC,CAAsCC,GAAtC,CAA2CD,CAAA,EAA3C,CAAgD,CAC/C,IAAI3C,KAAKwC,GAAA,CAAIG,CAAJ,CACT3C,KAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACAkB,QAAAI,KAAA,CAAa9C,IAAA+C,QAAb,CAA0BtB,YAA1B,CAH+C,CAKhD,MAAOiB,QARI,CADN,IAcRM,iBAAAT,KAAA;"}
|
{"version":3,"sources":["/var/www/htdocs/github.timshomepage.net/animeclient/frontEndSrc/js/base/sort-tables.js"],"sourcesContent":["const LightTableSorter = (() => {\n\tlet th = null;\n\tlet cellIndex = null;\n\tlet order = '';\n\tconst text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();\n\tconst sort = (a, b) => {\n\t\tlet textA = text(a);\n\t\tlet textB = text(b);\n\t\tconst n = parseInt(textA, 10);\n\t\tif (n) {\n\t\t\ttextA = n;\n\t\t\ttextB = parseInt(textB, 10);\n\t\t}\n\t\tif (textA > textB) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (textA < textB) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t};\n\tconst toggle = () => {\n\t\tconst c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';\n\t\tth.className = (th.className.replace(order, '') + ' ' + c).trim();\n\t\treturn order = c;\n\t};\n\tconst reset = () => {\n\t\tth.classList.remove('sorting-asc', 'sorting-desc');\n\t\tth.classList.add('sorting');\n\t\treturn order = '';\n\t};\n\tconst onClickEvent = (e) => {\n\t\tif (th && (cellIndex !== e.target.cellIndex)) {\n\t\t\treset();\n\t\t}\n\t\tth = e.target;\n\t\tif (th.nodeName.toLowerCase() === 'th') {\n\t\t\tcellIndex = th.cellIndex;\n\t\t\tconst tbody = th.offsetParent.getElementsByTagName('tbody')[0];\n\t\t\tlet rows = Array.from(tbody.rows);\n\t\t\tif (rows) {\n\t\t\t\trows.sort(sort);\n\t\t\t\tif (order === 'sorting-asc') {\n\t\t\t\t\trows.reverse();\n\t\t\t\t}\n\t\t\t\ttoggle();\n\t\t\t\ttbody.innerHtml = '';\n\n\t\t\t\trows.forEach(row => {\n\t\t\t\t\ttbody.appendChild(row);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\treturn {\n\t\tinit: () => {\n\t\t\tlet ths = document.getElementsByTagName('th');\n\t\t\tlet results = [];\n\t\t\tfor (let i = 0, len = ths.length; i < len; i++) {\n\t\t\t\tlet th = ths[i];\n\t\t\t\tth.classList.add('sorting');\n\t\t\t\tresults.push(th.onclick = onClickEvent);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t};\n})();\n\nLightTableSorter.init();"],"names":[],"mappings":"IAAM,gBAAgB,gBACjB,EAAE,CAAG,IAAI,KACT,SAAS,CAAG,IAAI,KAChB,KAAK,QACH,IAAI,UAAI,GAAG,SAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,WAAW,SACjE,IAAI,UAAI,CAAC,CAAE,CAAC,MACb,KAAK,CAAG,IAAI,CAAC,CAAC,MACd,KAAK,CAAG,IAAI,CAAC,CAAC,MACZ,CAAC,CAAG,QAAQ,CAAC,KAAK,CAAE,EAAE,KACxB,CAAC,EACJ,KAAK,CAAG,CAAC,CACT,KAAK,CAAG,QAAQ,CAAC,KAAK,CAAE,EAAE,MAEvB,KAAK,CAAG,KAAK,QACT,CAAC,IAEL,KAAK,CAAG,KAAK,QACT,EAAE,QAEH,CAAC,OAEH,MAAM,gBACL,CAAC,CAAG,KAAK,IAAK,WAAa,GAAG,WAAa,GAAG,YAAc,EAClE,EAAE,CAAC,SAAS,EAAI,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,MAAQ,CAAG,EAAG,CAAC,EAAE,IAAI,UACxD,KAAK,CAAG,CAAC,OAEX,KAAK,YACV,EAAE,CAAC,SAAS,CAAC,MAAM,EAAC,WAAa,GAAE,YAAc,GACjD,EAAE,CAAC,SAAS,CAAC,GAAG,EAAC,OAAS,UACnB,KAAK,UAEP,YAAY,UAAI,CAAC,KAClB,EAAE,EAAK,SAAS,GAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAC1C,KAAK,GAEN,EAAE,CAAG,CAAC,CAAC,MAAM,IACT,EAAE,CAAC,QAAQ,CAAC,WAAW,MAAO,EAAI,GACrC,SAAS,CAAG,EAAE,CAAC,SAAS,KAClB,KAAK,CAAG,EAAE,CAAC,YAAY,CAAC,oBAAoB,EAAC,KAAO,GAAE,CAAC,MACzD,IAAI,CAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAC5B,IAAI,EACP,IAAI,CAAC,IAAI,CAAC,IAAI,KACV,KAAK,IAAK,WAAa,EAC1B,IAAI,CAAC,OAAO,GAEb,MAAM,GACN,KAAK,CAAC,SAAS,IAEf,IAAI,CAAC,OAAO,UAAC,GAAG,EACf,KAAK,CAAC,WAAW,CAAC,GAAG,iBAMxB,IAAI,gBACC,GAAG,CAAG,QAAQ,CAAC,oBAAoB,EAAC,EAAI,OACxC,OAAO,YACF,CAAC,CAAG,CAAC,CAAE,GAAG,CAAG,GAAG,CAAC,MAAM,CAAE,CAAC,CAAG,GAAG,CAAE,CAAC,QACvC,GAAE,CAAG,GAAG,CAAC,CAAC,EACd,GAAE,CAAC,SAAS,CAAC,GAAG,EAAC,OAAS,GAC1B,OAAO,CAAC,IAAI,CAAC,GAAE,CAAC,OAAO,CAAG,YAAY,UAEhC,OAAO,QAKjB,gBAAgB,CAAC,IAAI"}
|
@ -272,7 +272,7 @@ abstract class APIRequestBuilder {
|
|||||||
throw new InvalidArgumentException('Invalid HTTP method');
|
throw new InvalidArgumentException('Invalid HTTP method');
|
||||||
}
|
}
|
||||||
|
|
||||||
$realUrl = (strpos($uri, '//') !== FALSE)
|
$realUrl = (str_contains($uri, '//'))
|
||||||
? $uri
|
? $uri
|
||||||
: $this->baseUrl . $uri;
|
: $this->baseUrl . $uri;
|
||||||
|
|
||||||
@ -297,7 +297,7 @@ abstract class APIRequestBuilder {
|
|||||||
*/
|
*/
|
||||||
private function buildUri(): Request
|
private function buildUri(): Request
|
||||||
{
|
{
|
||||||
$url = (strpos($this->path, '//') !== FALSE)
|
$url = (str_contains($this->path, '//'))
|
||||||
? $this->path
|
? $this->path
|
||||||
: $this->baseUrl . $this->path;
|
: $this->baseUrl . $this->path;
|
||||||
|
|
||||||
@ -314,11 +314,11 @@ abstract class APIRequestBuilder {
|
|||||||
/**
|
/**
|
||||||
* Reset the class state for a new request
|
* Reset the class state for a new request
|
||||||
*
|
*
|
||||||
* @param string $url
|
* @param string|null $url
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function resetState($url, $type = 'GET'): void
|
private function resetState(?string $url, $type = 'GET'): void
|
||||||
{
|
{
|
||||||
$requestUrl = $url ?: $this->baseUrl;
|
$requestUrl = $url ?: $this->baseUrl;
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ type AiringProgression {
|
|||||||
watching: Int
|
watching: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
"Media Airing Schedule"
|
"Media Airing Schedule. NOTE: We only aim to guarantee that FUTURE airing data is present and accurate."
|
||||||
type AiringSchedule {
|
type AiringSchedule {
|
||||||
"The time the episode airs at"
|
"The time the episode airs at"
|
||||||
airingAt: Int!
|
airingAt: Int!
|
||||||
@ -225,6 +225,10 @@ type AniChartUser {
|
|||||||
|
|
||||||
"A character that features in an anime or manga"
|
"A character that features in an anime or manga"
|
||||||
type Character {
|
type Character {
|
||||||
|
"The character's age. Note this is a string, not an int, it may contain further text and additional ages."
|
||||||
|
age: String
|
||||||
|
"The character's birth date"
|
||||||
|
dateOfBirth: FuzzyDate
|
||||||
"A general description of the character"
|
"A general description of the character"
|
||||||
description(
|
description(
|
||||||
"Return the string in pre-parsed html instead of markdown"
|
"Return the string in pre-parsed html instead of markdown"
|
||||||
@ -232,14 +236,19 @@ type Character {
|
|||||||
): String
|
): String
|
||||||
"The amount of user's who have favourited the character"
|
"The amount of user's who have favourited the character"
|
||||||
favourites: Int
|
favourites: Int
|
||||||
|
"The character's gender. Usually Male, Female, or Non-binary but can be any string."
|
||||||
|
gender: String
|
||||||
"The id of the character"
|
"The id of the character"
|
||||||
id: Int!
|
id: Int!
|
||||||
"Character images"
|
"Character images"
|
||||||
image: CharacterImage
|
image: CharacterImage
|
||||||
"If the character is marked as favourite by the currently authenticated user"
|
"If the character is marked as favourite by the currently authenticated user"
|
||||||
isFavourite: Boolean!
|
isFavourite: Boolean!
|
||||||
|
"If the character is blocked from being added to favourites"
|
||||||
|
isFavouriteBlocked: Boolean!
|
||||||
"Media that includes the character"
|
"Media that includes the character"
|
||||||
media(
|
media(
|
||||||
|
onList: Boolean,
|
||||||
"The page"
|
"The page"
|
||||||
page: Int,
|
page: Int,
|
||||||
"The amount of entries per page, max 25"
|
"The amount of entries per page, max 25"
|
||||||
@ -271,9 +280,13 @@ type CharacterEdge {
|
|||||||
id: Int
|
id: Int
|
||||||
"The media the character is in"
|
"The media the character is in"
|
||||||
media: [Media]
|
media: [Media]
|
||||||
|
"Media specific character name"
|
||||||
|
name: String
|
||||||
node: Character
|
node: Character
|
||||||
"The characters role in the media"
|
"The characters role in the media"
|
||||||
role: CharacterRole
|
role: CharacterRole
|
||||||
|
"The voice actors of the character with role date"
|
||||||
|
voiceActorRoles(language: StaffLanguage, sort: [StaffSort]): [StaffRoleType]
|
||||||
"The voice actors of the character"
|
"The voice actors of the character"
|
||||||
voiceActors(language: StaffLanguage, sort: [StaffSort]): [Staff]
|
voiceActors(language: StaffLanguage, sort: [StaffSort]): [Staff]
|
||||||
}
|
}
|
||||||
@ -289,12 +302,16 @@ type CharacterImage {
|
|||||||
type CharacterName {
|
type CharacterName {
|
||||||
"Other names the character might be referred to as"
|
"Other names the character might be referred to as"
|
||||||
alternative: [String]
|
alternative: [String]
|
||||||
|
"Other names the character might be referred to as but are spoilers"
|
||||||
|
alternativeSpoiler: [String]
|
||||||
"The character's given name"
|
"The character's given name"
|
||||||
first: String
|
first: String
|
||||||
"The character's full name"
|
"The character's first and last name"
|
||||||
full: String
|
full: String
|
||||||
"The character's surname"
|
"The character's surname"
|
||||||
last: String
|
last: String
|
||||||
|
"The character's middle name"
|
||||||
|
middle: String
|
||||||
"The character's full name in their native language"
|
"The character's full name in their native language"
|
||||||
native: String
|
native: String
|
||||||
}
|
}
|
||||||
@ -543,6 +560,8 @@ type InternalPage {
|
|||||||
id_not: Int,
|
id_not: Int,
|
||||||
"Filter by character id"
|
"Filter by character id"
|
||||||
id_not_in: [Int],
|
id_not_in: [Int],
|
||||||
|
"Filter by character by if its their birthday today"
|
||||||
|
isBirthday: Boolean,
|
||||||
"Filter by search query"
|
"Filter by search query"
|
||||||
search: String,
|
search: String,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
@ -855,7 +874,7 @@ type InternalPage {
|
|||||||
mediaType: MediaType,
|
mediaType: MediaType,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
sort: [ReviewSort],
|
sort: [ReviewSort],
|
||||||
"Filter by media id"
|
"Filter by user id"
|
||||||
userId: Int
|
userId: Int
|
||||||
): [Review]
|
): [Review]
|
||||||
revisionHistory(
|
revisionHistory(
|
||||||
@ -879,6 +898,8 @@ type InternalPage {
|
|||||||
id_not: Int,
|
id_not: Int,
|
||||||
"Filter by the staff id"
|
"Filter by the staff id"
|
||||||
id_not_in: [Int],
|
id_not_in: [Int],
|
||||||
|
"Filter by staff by if its their birthday today"
|
||||||
|
isBirthday: Boolean,
|
||||||
"Filter by search query"
|
"Filter by search query"
|
||||||
search: String,
|
search: String,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
@ -1114,7 +1135,10 @@ type Media {
|
|||||||
startDate: FuzzyDate
|
startDate: FuzzyDate
|
||||||
stats: MediaStats
|
stats: MediaStats
|
||||||
"The current releasing status of the media"
|
"The current releasing status of the media"
|
||||||
status: MediaStatus
|
status(
|
||||||
|
"Provide 2 to use new version 2 of sources enum"
|
||||||
|
version: Int
|
||||||
|
): MediaStatus
|
||||||
"Data and links to legal streaming episodes on external sites"
|
"Data and links to legal streaming episodes on external sites"
|
||||||
streamingEpisodes: [MediaStreamingEpisode]
|
streamingEpisodes: [MediaStreamingEpisode]
|
||||||
"The companies who produced the media"
|
"The companies who produced the media"
|
||||||
@ -1151,10 +1175,14 @@ type Media {
|
|||||||
type MediaCharacter {
|
type MediaCharacter {
|
||||||
"The characters in the media voiced by the parent actor"
|
"The characters in the media voiced by the parent actor"
|
||||||
character: Character
|
character: Character
|
||||||
|
"Media specific character name"
|
||||||
|
characterName: String
|
||||||
|
dubGroup: String
|
||||||
"The id of the connection"
|
"The id of the connection"
|
||||||
id: Int
|
id: Int
|
||||||
"The characters role in the media"
|
"The characters role in the media"
|
||||||
role: CharacterRole
|
role: CharacterRole
|
||||||
|
roleNotes: String
|
||||||
"The voice actor of the character"
|
"The voice actor of the character"
|
||||||
voiceActor: Staff
|
voiceActor: Staff
|
||||||
}
|
}
|
||||||
@ -1179,10 +1207,14 @@ type MediaCoverImage {
|
|||||||
|
|
||||||
"Media connection edge"
|
"Media connection edge"
|
||||||
type MediaEdge {
|
type MediaEdge {
|
||||||
|
"Media specific character name"
|
||||||
|
characterName: String
|
||||||
"The characters role in the media"
|
"The characters role in the media"
|
||||||
characterRole: CharacterRole
|
characterRole: CharacterRole
|
||||||
"The characters in the media voiced by the parent actor"
|
"The characters in the media voiced by the parent actor"
|
||||||
characters: [Character]
|
characters: [Character]
|
||||||
|
"Used for grouping roles where multiple dubs exist for the same language. Either dubbing company name or language variant."
|
||||||
|
dubGroup: String
|
||||||
"The order the media should be displayed from the users favourites"
|
"The order the media should be displayed from the users favourites"
|
||||||
favouriteOrder: Int
|
favouriteOrder: Int
|
||||||
"The id of the connection"
|
"The id of the connection"
|
||||||
@ -1195,8 +1227,12 @@ type MediaEdge {
|
|||||||
"Provide 2 to use new version 2 of relation enum"
|
"Provide 2 to use new version 2 of relation enum"
|
||||||
version: Int
|
version: Int
|
||||||
): MediaRelation
|
): MediaRelation
|
||||||
|
"Notes regarding the VA's role for the character"
|
||||||
|
roleNotes: String
|
||||||
"The role of the staff member in the production of the media"
|
"The role of the staff member in the production of the media"
|
||||||
staffRole: String
|
staffRole: String
|
||||||
|
"The voice actors of the character with role date"
|
||||||
|
voiceActorRoles(language: StaffLanguage, sort: [StaffSort]): [StaffRoleType]
|
||||||
"The voice actors of the character"
|
"The voice actors of the character"
|
||||||
voiceActors(language: StaffLanguage, sort: [StaffSort]): [Staff]
|
voiceActors(language: StaffLanguage, sort: [StaffSort]): [Staff]
|
||||||
}
|
}
|
||||||
@ -1297,8 +1333,7 @@ type MediaListOptions {
|
|||||||
sharedTheme: Json @deprecated(reason : "No longer used")
|
sharedTheme: Json @deprecated(reason : "No longer used")
|
||||||
"If the shared theme should be used instead of the individual list themes"
|
"If the shared theme should be used instead of the individual list themes"
|
||||||
sharedThemeEnabled: Boolean @deprecated(reason : "No longer used")
|
sharedThemeEnabled: Boolean @deprecated(reason : "No longer used")
|
||||||
"(Site only) If the user should be using legacy css-supporting list versions"
|
useLegacyLists: Boolean @deprecated(reason : "No longer used")
|
||||||
useLegacyLists: Boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"A user's list options for anime or manga lists"
|
"A user's list options for anime or manga lists"
|
||||||
@ -1388,12 +1423,15 @@ type MediaSubmissionComparison {
|
|||||||
|
|
||||||
type MediaSubmissionEdge {
|
type MediaSubmissionEdge {
|
||||||
character: Character
|
character: Character
|
||||||
|
characterName: String
|
||||||
characterRole: CharacterRole
|
characterRole: CharacterRole
|
||||||
characterSubmission: Character
|
characterSubmission: Character
|
||||||
|
dubGroup: String
|
||||||
"The id of the direct submission"
|
"The id of the direct submission"
|
||||||
id: Int
|
id: Int
|
||||||
isMain: Boolean
|
isMain: Boolean
|
||||||
media: Media
|
media: Media
|
||||||
|
roleNotes: String
|
||||||
staff: Staff
|
staff: Staff
|
||||||
staffRole: String
|
staffRole: String
|
||||||
staffSubmission: Staff
|
staffSubmission: Staff
|
||||||
@ -1814,6 +1852,8 @@ type Mutation {
|
|||||||
UpdateUser(
|
UpdateUser(
|
||||||
"User's about/bio text"
|
"User's about/bio text"
|
||||||
about: String,
|
about: String,
|
||||||
|
"Minutes between activity for them to be merged together. 0 is Never, Above 2 weeks (20160 mins) is Always."
|
||||||
|
activityMergeTime: Int,
|
||||||
"If the user should get notifications when a show they are watching aires"
|
"If the user should get notifications when a show they are watching aires"
|
||||||
airingNotifications: Boolean,
|
airingNotifications: Boolean,
|
||||||
"The user's anime list options"
|
"The user's anime list options"
|
||||||
@ -1832,6 +1872,8 @@ type Mutation {
|
|||||||
rowOrder: String,
|
rowOrder: String,
|
||||||
"The user's list scoring system"
|
"The user's list scoring system"
|
||||||
scoreFormat: ScoreFormat,
|
scoreFormat: ScoreFormat,
|
||||||
|
"Timezone offset format: -?HH:MM"
|
||||||
|
timezone: String,
|
||||||
"User's title language"
|
"User's title language"
|
||||||
titleLanguage: UserTitleLanguage
|
titleLanguage: UserTitleLanguage
|
||||||
): User
|
): User
|
||||||
@ -1958,6 +2000,8 @@ type Page {
|
|||||||
id_not: Int,
|
id_not: Int,
|
||||||
"Filter by character id"
|
"Filter by character id"
|
||||||
id_not_in: [Int],
|
id_not_in: [Int],
|
||||||
|
"Filter by character by if its their birthday today"
|
||||||
|
isBirthday: Boolean,
|
||||||
"Filter by search query"
|
"Filter by search query"
|
||||||
search: String,
|
search: String,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
@ -2258,7 +2302,7 @@ type Page {
|
|||||||
mediaType: MediaType,
|
mediaType: MediaType,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
sort: [ReviewSort],
|
sort: [ReviewSort],
|
||||||
"Filter by media id"
|
"Filter by user id"
|
||||||
userId: Int
|
userId: Int
|
||||||
): [Review]
|
): [Review]
|
||||||
staff(
|
staff(
|
||||||
@ -2270,6 +2314,8 @@ type Page {
|
|||||||
id_not: Int,
|
id_not: Int,
|
||||||
"Filter by the staff id"
|
"Filter by the staff id"
|
||||||
id_not_in: [Int],
|
id_not_in: [Int],
|
||||||
|
"Filter by staff by if its their birthday today"
|
||||||
|
isBirthday: Boolean,
|
||||||
"Filter by search query"
|
"Filter by search query"
|
||||||
search: String,
|
search: String,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
@ -2467,6 +2513,8 @@ type Query {
|
|||||||
id_not: Int,
|
id_not: Int,
|
||||||
"Filter by character id"
|
"Filter by character id"
|
||||||
id_not_in: [Int],
|
id_not_in: [Int],
|
||||||
|
"Filter by character by if its their birthday today"
|
||||||
|
isBirthday: Boolean,
|
||||||
"Filter by search query"
|
"Filter by search query"
|
||||||
search: String,
|
search: String,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
@ -2837,7 +2885,7 @@ type Query {
|
|||||||
mediaType: MediaType,
|
mediaType: MediaType,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
sort: [ReviewSort],
|
sort: [ReviewSort],
|
||||||
"Filter by media id"
|
"Filter by user id"
|
||||||
userId: Int
|
userId: Int
|
||||||
): Review
|
): Review
|
||||||
"Site statistics query"
|
"Site statistics query"
|
||||||
@ -2852,6 +2900,8 @@ type Query {
|
|||||||
id_not: Int,
|
id_not: Int,
|
||||||
"Filter by the staff id"
|
"Filter by the staff id"
|
||||||
id_not_in: [Int],
|
id_not_in: [Int],
|
||||||
|
"Filter by staff by if its their birthday today"
|
||||||
|
isBirthday: Boolean,
|
||||||
"Filter by search query"
|
"Filter by search query"
|
||||||
search: String,
|
search: String,
|
||||||
"The order the results will be returned in"
|
"The order the results will be returned in"
|
||||||
@ -3127,6 +3177,17 @@ type SiteTrendEdge {
|
|||||||
|
|
||||||
"Voice actors or production staff"
|
"Voice actors or production staff"
|
||||||
type Staff {
|
type Staff {
|
||||||
|
"The person's age in years"
|
||||||
|
age: Int
|
||||||
|
"Media the actor voiced characters in. (Same data as characters with media as node instead of characters)"
|
||||||
|
characterMedia(
|
||||||
|
onList: Boolean,
|
||||||
|
"The page"
|
||||||
|
page: Int,
|
||||||
|
"The amount of entries per page, max 25"
|
||||||
|
perPage: Int,
|
||||||
|
sort: [MediaSort]
|
||||||
|
): MediaConnection
|
||||||
"Characters voiced by the actor"
|
"Characters voiced by the actor"
|
||||||
characters(
|
characters(
|
||||||
"The page"
|
"The page"
|
||||||
@ -3135,6 +3196,8 @@ type Staff {
|
|||||||
perPage: Int,
|
perPage: Int,
|
||||||
sort: [CharacterSort]
|
sort: [CharacterSort]
|
||||||
): CharacterConnection
|
): CharacterConnection
|
||||||
|
dateOfBirth: FuzzyDate
|
||||||
|
dateOfDeath: FuzzyDate
|
||||||
"A general description of the staff member"
|
"A general description of the staff member"
|
||||||
description(
|
description(
|
||||||
"Return the string in pre-parsed html instead of markdown"
|
"Return the string in pre-parsed html instead of markdown"
|
||||||
@ -3142,24 +3205,35 @@ type Staff {
|
|||||||
): String
|
): String
|
||||||
"The amount of user's who have favourited the staff member"
|
"The amount of user's who have favourited the staff member"
|
||||||
favourites: Int
|
favourites: Int
|
||||||
|
"The staff's gender. Usually Male, Female, or Non-binary but can be any string."
|
||||||
|
gender: String
|
||||||
|
"The persons birthplace or hometown"
|
||||||
|
homeTown: String
|
||||||
"The id of the staff member"
|
"The id of the staff member"
|
||||||
id: Int!
|
id: Int!
|
||||||
"The staff images"
|
"The staff images"
|
||||||
image: StaffImage
|
image: StaffImage
|
||||||
"If the staff member is marked as favourite by the currently authenticated user"
|
"If the staff member is marked as favourite by the currently authenticated user"
|
||||||
isFavourite: Boolean!
|
isFavourite: Boolean!
|
||||||
"The primary language of the staff member"
|
"If the staff member is blocked from being added to favourites"
|
||||||
language: StaffLanguage
|
isFavouriteBlocked: Boolean!
|
||||||
|
"The primary language the staff member dub's in"
|
||||||
|
language: StaffLanguage @deprecated(reason : "Replaced with languageV2")
|
||||||
|
"The primary language of the staff member. Current values: Japanese, English, Korean, Italian, Spanish, Portuguese, French, German, Hebrew, Hungarian, Chinese, Arabic, Filipino, Catalan"
|
||||||
|
languageV2: String
|
||||||
"Notes for site moderators"
|
"Notes for site moderators"
|
||||||
modNotes: String
|
modNotes: String
|
||||||
"The names of the staff member"
|
"The names of the staff member"
|
||||||
name: StaffName
|
name: StaffName
|
||||||
|
"The person's primary occupations"
|
||||||
|
primaryOccupations: [String]
|
||||||
"The url for the staff page on the AniList website"
|
"The url for the staff page on the AniList website"
|
||||||
siteUrl: String
|
siteUrl: String
|
||||||
"Staff member that the submission is referencing"
|
"Staff member that the submission is referencing"
|
||||||
staff: Staff
|
staff: Staff
|
||||||
"Media where the staff member has a production role"
|
"Media where the staff member has a production role"
|
||||||
staffMedia(
|
staffMedia(
|
||||||
|
onList: Boolean,
|
||||||
"The page"
|
"The page"
|
||||||
page: Int,
|
page: Int,
|
||||||
"The amount of entries per page, max 25"
|
"The amount of entries per page, max 25"
|
||||||
@ -3174,6 +3248,8 @@ type Staff {
|
|||||||
"Submitter for the submission"
|
"Submitter for the submission"
|
||||||
submitter: User
|
submitter: User
|
||||||
updatedAt: Int @deprecated(reason : "No data available")
|
updatedAt: Int @deprecated(reason : "No data available")
|
||||||
|
"[startYear, endYear] (If the 2nd value is not present staff is still active)"
|
||||||
|
yearsActive: [Int]
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaffConnection {
|
type StaffConnection {
|
||||||
@ -3207,14 +3283,26 @@ type StaffName {
|
|||||||
alternative: [String]
|
alternative: [String]
|
||||||
"The person's given name"
|
"The person's given name"
|
||||||
first: String
|
first: String
|
||||||
"The person's full name"
|
"The person's first and last name"
|
||||||
full: String
|
full: String
|
||||||
"The person's surname"
|
"The person's surname"
|
||||||
last: String
|
last: String
|
||||||
|
"The person's middle name"
|
||||||
|
middle: String
|
||||||
"The person's full name in their native language"
|
"The person's full name in their native language"
|
||||||
native: String
|
native: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Voice actor role for a character"
|
||||||
|
type StaffRoleType {
|
||||||
|
"Used for grouping roles where multiple dubs exist for the same language. Either dubbing company name or language variant."
|
||||||
|
dubGroup: String
|
||||||
|
"Notes regarding the VA's role for the character"
|
||||||
|
roleNotes: String
|
||||||
|
"The voice actors of the character"
|
||||||
|
voiceActor: Staff
|
||||||
|
}
|
||||||
|
|
||||||
"User's staff statistics"
|
"User's staff statistics"
|
||||||
type StaffStats {
|
type StaffStats {
|
||||||
amount: Int
|
amount: Int
|
||||||
@ -3264,6 +3352,7 @@ type Studio {
|
|||||||
media(
|
media(
|
||||||
"If the studio was the primary animation studio of the media"
|
"If the studio was the primary animation studio of the media"
|
||||||
isMain: Boolean,
|
isMain: Boolean,
|
||||||
|
onList: Boolean,
|
||||||
"The page"
|
"The page"
|
||||||
page: Int,
|
page: Int,
|
||||||
"The amount of entries per page, max 25"
|
"The amount of entries per page, max 25"
|
||||||
@ -3663,6 +3752,8 @@ type UserModData {
|
|||||||
|
|
||||||
"A user's general options"
|
"A user's general options"
|
||||||
type UserOptions {
|
type UserOptions {
|
||||||
|
"Minutes between activity for them to be merged together. 0 is Never, Above 2 weeks (20160 mins) is Always."
|
||||||
|
activityMergeTime: Int
|
||||||
"Whether the user receives notifications when a show they are watching aires"
|
"Whether the user receives notifications when a show they are watching aires"
|
||||||
airingNotifications: Boolean
|
airingNotifications: Boolean
|
||||||
"Whether the user has enabled viewing of 18+ content"
|
"Whether the user has enabled viewing of 18+ content"
|
||||||
@ -3671,6 +3762,8 @@ type UserOptions {
|
|||||||
notificationOptions: [NotificationOption]
|
notificationOptions: [NotificationOption]
|
||||||
"Profile highlight color (blue, purple, pink, orange, red, green, gray)"
|
"Profile highlight color (blue, purple, pink, orange, red, green, gray)"
|
||||||
profileColor: String
|
profileColor: String
|
||||||
|
"The user's timezone offset (Auth user only)"
|
||||||
|
timezone: String
|
||||||
"The language the user wants to see media titles in"
|
"The language the user wants to see media titles in"
|
||||||
titleLanguage: UserTitleLanguage
|
titleLanguage: UserTitleLanguage
|
||||||
}
|
}
|
||||||
@ -3853,6 +3946,8 @@ enum CharacterSort {
|
|||||||
FAVOURITES_DESC
|
FAVOURITES_DESC
|
||||||
ID
|
ID
|
||||||
ID_DESC
|
ID_DESC
|
||||||
|
"Order manually decided by moderators"
|
||||||
|
RELEVANCE
|
||||||
ROLE
|
ROLE
|
||||||
ROLE_DESC
|
ROLE_DESC
|
||||||
SEARCH_MATCH
|
SEARCH_MATCH
|
||||||
@ -4058,6 +4153,8 @@ enum MediaStatus {
|
|||||||
CANCELLED
|
CANCELLED
|
||||||
"Has completed and is no longer being released"
|
"Has completed and is no longer being released"
|
||||||
FINISHED
|
FINISHED
|
||||||
|
"Version 2 only. Is currently paused from releasing and will resume at a later date"
|
||||||
|
HIATUS
|
||||||
"To be released at a later date"
|
"To be released at a later date"
|
||||||
NOT_YET_RELEASED
|
NOT_YET_RELEASED
|
||||||
"Currently releasing"
|
"Currently releasing"
|
||||||
@ -4231,6 +4328,8 @@ enum StaffSort {
|
|||||||
ID_DESC
|
ID_DESC
|
||||||
LANGUAGE
|
LANGUAGE
|
||||||
LANGUAGE_DESC
|
LANGUAGE_DESC
|
||||||
|
"Order manually decided by moderators"
|
||||||
|
RELEVANCE
|
||||||
ROLE
|
ROLE
|
||||||
ROLE_DESC
|
ROLE_DESC
|
||||||
SEARCH_MATCH
|
SEARCH_MATCH
|
||||||
@ -4343,10 +4442,14 @@ input AniChartHighlightInput {
|
|||||||
input CharacterNameInput {
|
input CharacterNameInput {
|
||||||
"Other names the character might be referred by"
|
"Other names the character might be referred by"
|
||||||
alternative: [String]
|
alternative: [String]
|
||||||
|
"Other names the character might be referred to as but are spoilers"
|
||||||
|
alternativeSpoiler: [String]
|
||||||
"The character's given name"
|
"The character's given name"
|
||||||
first: String
|
first: String
|
||||||
"The character's surname"
|
"The character's surname"
|
||||||
last: String
|
last: String
|
||||||
|
"The character's middle name"
|
||||||
|
middle: String
|
||||||
"The character's full name in their native language"
|
"The character's full name in their native language"
|
||||||
native: String
|
native: String
|
||||||
}
|
}
|
||||||
@ -4413,6 +4516,8 @@ input StaffNameInput {
|
|||||||
first: String
|
first: String
|
||||||
"The person's surname"
|
"The person's surname"
|
||||||
last: String
|
last: String
|
||||||
|
"The person's middle name"
|
||||||
|
middle: String
|
||||||
"The person's full name in their native language"
|
"The person's full name in their native language"
|
||||||
native: String
|
native: String
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
query ($slug: String!) {
|
query ($slug: String!) {
|
||||||
findProfileBySlug(slug: $slug) {
|
findProfileBySlug(slug: $slug) {
|
||||||
libraryEvents(first: 100) {
|
libraryEvents(first: 100, kind: [PROGRESSED, UPDATED]) {
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
changedData
|
changedData
|
||||||
|
@ -104,7 +104,7 @@ final class AnimeListTransformer extends AbstractTransformer {
|
|||||||
'notes' => $item['notes'],
|
'notes' => $item['notes'],
|
||||||
'rewatching' => (bool) $item['reconsuming'],
|
'rewatching' => (bool) $item['reconsuming'],
|
||||||
'rewatched' => (int) $item['reconsumeCount'],
|
'rewatched' => (int) $item['reconsumeCount'],
|
||||||
'user_rating' => $rating,
|
'user_rating' => (is_string($rating)) ? $rating : (int) $rating,
|
||||||
'private' => $item['private'] ?? FALSE,
|
'private' => $item['private'] ?? FALSE,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -20,16 +20,6 @@ interface AmountConsumed {
|
|||||||
units: Int!
|
units: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
"Generic error fields used by all errors."
|
|
||||||
interface Base {
|
|
||||||
"The error code."
|
|
||||||
code: String
|
|
||||||
"A description of the error"
|
|
||||||
message: String!
|
|
||||||
"Which input value this error came from"
|
|
||||||
path: [String!]
|
|
||||||
}
|
|
||||||
|
|
||||||
"Generic Category Breakdown based on Media"
|
"Generic Category Breakdown based on Media"
|
||||||
interface CategoryBreakdown {
|
interface CategoryBreakdown {
|
||||||
"A Map of category_id -> count for all categories present on the library entries"
|
"A Map of category_id -> count for all categories present on the library entries"
|
||||||
@ -65,6 +55,16 @@ interface Episodic {
|
|||||||
totalLength: Int
|
totalLength: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Generic error fields used by all errors."
|
||||||
|
interface Error {
|
||||||
|
"The error code."
|
||||||
|
code: String
|
||||||
|
"A description of the error"
|
||||||
|
message: String!
|
||||||
|
"Which input value this error came from"
|
||||||
|
path: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
"A media in the Kitsu database"
|
"A media in the Kitsu database"
|
||||||
interface Media {
|
interface Media {
|
||||||
"The recommended minimum age group for this media"
|
"The recommended minimum age group for this media"
|
||||||
@ -117,6 +117,8 @@ interface Media {
|
|||||||
): MappingConnection!
|
): MappingConnection!
|
||||||
"The time of the next release of this media"
|
"The time of the next release of this media"
|
||||||
nextRelease: ISO8601DateTime
|
nextRelease: ISO8601DateTime
|
||||||
|
"The country in which the media was primarily produced"
|
||||||
|
originalLocale: String
|
||||||
"The poster image of this media"
|
"The poster image of this media"
|
||||||
posterImage: Image!
|
posterImage: Image!
|
||||||
"The companies which helped to produce this media"
|
"The companies which helped to produce this media"
|
||||||
@ -318,6 +320,8 @@ type Anime implements Episodic & Media & WithTimestamps {
|
|||||||
): MappingConnection!
|
): MappingConnection!
|
||||||
"The time of the next release of this media"
|
"The time of the next release of this media"
|
||||||
nextRelease: ISO8601DateTime
|
nextRelease: ISO8601DateTime
|
||||||
|
"The country in which the media was primarily produced"
|
||||||
|
originalLocale: String
|
||||||
"The poster image of this media"
|
"The poster image of this media"
|
||||||
posterImage: Image!
|
posterImage: Image!
|
||||||
"The companies which helped to produce this media"
|
"The companies which helped to produce this media"
|
||||||
@ -445,15 +449,13 @@ type AnimeConnection {
|
|||||||
"Autogenerated return type of AnimeCreate"
|
"Autogenerated return type of AnimeCreate"
|
||||||
type AnimeCreatePayload {
|
type AnimeCreatePayload {
|
||||||
anime: Anime
|
anime: Anime
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of AnimeDelete"
|
"Autogenerated return type of AnimeDelete"
|
||||||
type AnimeDeletePayload {
|
type AnimeDeletePayload {
|
||||||
anime: GenericDelete
|
anime: GenericDelete
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"An edge in a connection."
|
"An edge in a connection."
|
||||||
@ -464,7 +466,7 @@ type AnimeEdge {
|
|||||||
node: Anime
|
node: Anime
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnimeMutation {
|
type AnimeMutations {
|
||||||
"Create an Anime."
|
"Create an Anime."
|
||||||
create(
|
create(
|
||||||
"Create an Anime."
|
"Create an Anime."
|
||||||
@ -485,8 +487,7 @@ type AnimeMutation {
|
|||||||
"Autogenerated return type of AnimeUpdate"
|
"Autogenerated return type of AnimeUpdate"
|
||||||
type AnimeUpdatePayload {
|
type AnimeUpdatePayload {
|
||||||
anime: Anime
|
anime: Anime
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Information about a specific Category"
|
"Information about a specific Category"
|
||||||
@ -651,7 +652,7 @@ type Comment implements WithTimestamps {
|
|||||||
contentFormatted: String!
|
contentFormatted: String!
|
||||||
createdAt: ISO8601DateTime!
|
createdAt: ISO8601DateTime!
|
||||||
id: ID!
|
id: ID!
|
||||||
"Users who liked this comment."
|
"Users who liked this comment"
|
||||||
likes(
|
likes(
|
||||||
"Returns the elements in the list that come after the specified cursor."
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
after: String,
|
after: String,
|
||||||
@ -660,13 +661,14 @@ type Comment implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [CommentLikeSortOption]
|
||||||
): ProfileConnection!
|
): ProfileConnection!
|
||||||
"The parent comment if this comment was a reply to another."
|
"The parent comment if this comment was a reply to another."
|
||||||
parent: Comment
|
parent: Comment
|
||||||
"The post that this comment is attached to."
|
"The post that this comment is attached to."
|
||||||
post: Post!
|
post: Post!
|
||||||
"All replies to a specific comment."
|
"Replies to this comment"
|
||||||
replies(
|
replies(
|
||||||
"Returns the elements in the list that come after the specified cursor."
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
after: String,
|
after: String,
|
||||||
@ -675,7 +677,8 @@ type Comment implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [CommentSortOption]
|
||||||
): CommentConnection!
|
): CommentConnection!
|
||||||
updatedAt: ISO8601DateTime!
|
updatedAt: ISO8601DateTime!
|
||||||
}
|
}
|
||||||
@ -736,15 +739,13 @@ type EpisodeConnection {
|
|||||||
"Autogenerated return type of EpisodeCreate"
|
"Autogenerated return type of EpisodeCreate"
|
||||||
type EpisodeCreatePayload {
|
type EpisodeCreatePayload {
|
||||||
episode: Episode
|
episode: Episode
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of EpisodeDelete"
|
"Autogenerated return type of EpisodeDelete"
|
||||||
type EpisodeDeletePayload {
|
type EpisodeDeletePayload {
|
||||||
episode: GenericDelete
|
episode: GenericDelete
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"An edge in a connection."
|
"An edge in a connection."
|
||||||
@ -755,7 +756,7 @@ type EpisodeEdge {
|
|||||||
node: Episode
|
node: Episode
|
||||||
}
|
}
|
||||||
|
|
||||||
type EpisodeMutation {
|
type EpisodeMutations {
|
||||||
"Create an Episode."
|
"Create an Episode."
|
||||||
create(
|
create(
|
||||||
"Create an Episode"
|
"Create an Episode"
|
||||||
@ -776,8 +777,7 @@ type EpisodeMutation {
|
|||||||
"Autogenerated return type of EpisodeUpdate"
|
"Autogenerated return type of EpisodeUpdate"
|
||||||
type EpisodeUpdatePayload {
|
type EpisodeUpdatePayload {
|
||||||
episode: Episode
|
episode: Episode
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Favorite media, characters, and people for a user"
|
"Favorite media, characters, and people for a user"
|
||||||
@ -811,7 +811,7 @@ type FavoriteEdge {
|
|||||||
node: Favorite
|
node: Favorite
|
||||||
}
|
}
|
||||||
|
|
||||||
type Generic implements Base {
|
type Generic implements Error {
|
||||||
"The error code."
|
"The error code."
|
||||||
code: String
|
code: String
|
||||||
"A description of the error"
|
"A description of the error"
|
||||||
@ -991,15 +991,13 @@ type LibraryEntryConnection {
|
|||||||
|
|
||||||
"Autogenerated return type of LibraryEntryCreate"
|
"Autogenerated return type of LibraryEntryCreate"
|
||||||
type LibraryEntryCreatePayload {
|
type LibraryEntryCreatePayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryDelete"
|
"Autogenerated return type of LibraryEntryDelete"
|
||||||
type LibraryEntryDeletePayload {
|
type LibraryEntryDeletePayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: GenericDelete
|
libraryEntry: GenericDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1011,7 +1009,7 @@ type LibraryEntryEdge {
|
|||||||
node: LibraryEntry
|
node: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
type LibraryEntryMutation {
|
type LibraryEntryMutations {
|
||||||
"Create a library entry"
|
"Create a library entry"
|
||||||
create(
|
create(
|
||||||
"Create a Library Entry"
|
"Create a Library Entry"
|
||||||
@ -1061,50 +1059,43 @@ type LibraryEntryMutation {
|
|||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdate"
|
"Autogenerated return type of LibraryEntryUpdate"
|
||||||
type LibraryEntryUpdatePayload {
|
type LibraryEntryUpdatePayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdateProgressById"
|
"Autogenerated return type of LibraryEntryUpdateProgressById"
|
||||||
type LibraryEntryUpdateProgressByIdPayload {
|
type LibraryEntryUpdateProgressByIdPayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdateProgressByMedia"
|
"Autogenerated return type of LibraryEntryUpdateProgressByMedia"
|
||||||
type LibraryEntryUpdateProgressByMediaPayload {
|
type LibraryEntryUpdateProgressByMediaPayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdateRatingById"
|
"Autogenerated return type of LibraryEntryUpdateRatingById"
|
||||||
type LibraryEntryUpdateRatingByIdPayload {
|
type LibraryEntryUpdateRatingByIdPayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdateRatingByMedia"
|
"Autogenerated return type of LibraryEntryUpdateRatingByMedia"
|
||||||
type LibraryEntryUpdateRatingByMediaPayload {
|
type LibraryEntryUpdateRatingByMediaPayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdateStatusById"
|
"Autogenerated return type of LibraryEntryUpdateStatusById"
|
||||||
type LibraryEntryUpdateStatusByIdPayload {
|
type LibraryEntryUpdateStatusByIdPayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LibraryEntryUpdateStatusByMedia"
|
"Autogenerated return type of LibraryEntryUpdateStatusByMedia"
|
||||||
type LibraryEntryUpdateStatusByMediaPayload {
|
type LibraryEntryUpdateStatusByMediaPayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
libraryEntry: LibraryEntry
|
libraryEntry: LibraryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1145,13 +1136,6 @@ type LibraryEventEdge {
|
|||||||
node: LibraryEvent
|
node: LibraryEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of LockPost"
|
|
||||||
type LockPostPayload {
|
|
||||||
"Graphql Errors"
|
|
||||||
errors: [Generic!]
|
|
||||||
post: Post
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manga implements Media & WithTimestamps {
|
type Manga implements Media & WithTimestamps {
|
||||||
"The recommended minimum age group for this media"
|
"The recommended minimum age group for this media"
|
||||||
ageRating: AgeRatingEnum
|
ageRating: AgeRatingEnum
|
||||||
@ -1219,6 +1203,8 @@ type Manga implements Media & WithTimestamps {
|
|||||||
): MappingConnection!
|
): MappingConnection!
|
||||||
"The time of the next release of this media"
|
"The time of the next release of this media"
|
||||||
nextRelease: ISO8601DateTime
|
nextRelease: ISO8601DateTime
|
||||||
|
"The country in which the media was primarily produced"
|
||||||
|
originalLocale: String
|
||||||
"The poster image of this media"
|
"The poster image of this media"
|
||||||
posterImage: Image!
|
posterImage: Image!
|
||||||
"The companies which helped to produce this media"
|
"The companies which helped to produce this media"
|
||||||
@ -1361,15 +1347,13 @@ type MappingConnection {
|
|||||||
|
|
||||||
"Autogenerated return type of MappingCreate"
|
"Autogenerated return type of MappingCreate"
|
||||||
type MappingCreatePayload {
|
type MappingCreatePayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
mapping: Mapping
|
mapping: Mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of MappingDelete"
|
"Autogenerated return type of MappingDelete"
|
||||||
type MappingDeletePayload {
|
type MappingDeletePayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
mapping: GenericDelete
|
mapping: GenericDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1381,7 +1365,7 @@ type MappingEdge {
|
|||||||
node: Mapping
|
node: Mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
type MappingMutation {
|
type MappingMutations {
|
||||||
"Create a Mapping"
|
"Create a Mapping"
|
||||||
create(
|
create(
|
||||||
"Create a Mapping"
|
"Create a Mapping"
|
||||||
@ -1401,8 +1385,7 @@ type MappingMutation {
|
|||||||
|
|
||||||
"Autogenerated return type of MappingUpdate"
|
"Autogenerated return type of MappingUpdate"
|
||||||
type MappingUpdatePayload {
|
type MappingUpdatePayload {
|
||||||
"Graphql Errors"
|
errors: [Error!]
|
||||||
errors: [Generic!]
|
|
||||||
mapping: Mapping
|
mapping: Mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1584,12 +1567,12 @@ type MediaStaffEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
anime: AnimeMutation
|
anime: AnimeMutations!
|
||||||
episode: EpisodeMutation
|
episode: EpisodeMutations!
|
||||||
libraryEntry: LibraryEntryMutation
|
libraryEntry: LibraryEntryMutations!
|
||||||
mapping: MappingMutation
|
mapping: MappingMutations!
|
||||||
post: PostMutation
|
post: PostMutations!
|
||||||
pro: ProMutation!
|
pro: ProMutations!
|
||||||
}
|
}
|
||||||
|
|
||||||
"Information about pagination in a connection."
|
"Information about pagination in a connection."
|
||||||
@ -1649,7 +1632,7 @@ type Person implements WithTimestamps {
|
|||||||
type Post implements WithTimestamps {
|
type Post implements WithTimestamps {
|
||||||
"The user who created this post."
|
"The user who created this post."
|
||||||
author: Profile!
|
author: Profile!
|
||||||
"All comments related to this post."
|
"All comments on this post"
|
||||||
comments(
|
comments(
|
||||||
"Returns the elements in the list that come after the specified cursor."
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
after: String,
|
after: String,
|
||||||
@ -1658,7 +1641,8 @@ type Post implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [CommentSortOption]
|
||||||
): CommentConnection!
|
): CommentConnection!
|
||||||
"Unmodified content."
|
"Unmodified content."
|
||||||
content: String!
|
content: String!
|
||||||
@ -1681,7 +1665,7 @@ type Post implements WithTimestamps {
|
|||||||
isNsfw: Boolean!
|
isNsfw: Boolean!
|
||||||
"If this post spoils the tagged media."
|
"If this post spoils the tagged media."
|
||||||
isSpoiler: Boolean!
|
isSpoiler: Boolean!
|
||||||
"Users that have liked this post."
|
"Users that have liked this post"
|
||||||
likes(
|
likes(
|
||||||
"Returns the elements in the list that come after the specified cursor."
|
"Returns the elements in the list that come after the specified cursor."
|
||||||
after: String,
|
after: String,
|
||||||
@ -1690,7 +1674,8 @@ type Post implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [PostLikeSortOption]
|
||||||
): ProfileConnection!
|
): ProfileConnection!
|
||||||
"When this post was locked."
|
"When this post was locked."
|
||||||
lockedAt: ISO8601DateTime
|
lockedAt: ISO8601DateTime
|
||||||
@ -1723,32 +1708,54 @@ type PostEdge {
|
|||||||
node: Post
|
node: Post
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostMutation {
|
"Autogenerated return type of PostLock"
|
||||||
|
type PostLockPayload {
|
||||||
|
errors: [Error!]
|
||||||
|
post: Post
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostMutations {
|
||||||
"Lock a Post."
|
"Lock a Post."
|
||||||
lock(
|
lock(
|
||||||
"Lock a Post."
|
"Lock a Post."
|
||||||
input: LockInput!
|
input: LockInput!
|
||||||
): LockPostPayload
|
): PostLockPayload
|
||||||
"Unlock a Post."
|
"Unlock a Post."
|
||||||
unlock(
|
unlock(
|
||||||
"Unlock a Post."
|
"Unlock a Post."
|
||||||
input: UnlockInput!
|
input: UnlockInput!
|
||||||
): UnlockPostPayload
|
): PostUnlockPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProMutation {
|
"Autogenerated return type of PostUnlock"
|
||||||
|
type PostUnlockPayload {
|
||||||
|
errors: [Error!]
|
||||||
|
post: Post
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProMutations {
|
||||||
"Set the user's discord tag"
|
"Set the user's discord tag"
|
||||||
setDiscord(
|
setDiscord(
|
||||||
"Your discord tag (Name#1234)"
|
"Your discord tag (Name#1234)"
|
||||||
discord: String!
|
discord: String!
|
||||||
): SetDiscordPayload
|
): ProSetDiscordPayload
|
||||||
"Set the user's Hall-of-Fame message"
|
"Set the user's Hall-of-Fame message"
|
||||||
setMessage(
|
setMessage(
|
||||||
"The message to set for your Hall of Fame entry"
|
"The message to set for your Hall of Fame entry"
|
||||||
message: String!
|
message: String!
|
||||||
): SetMessagePayload
|
): ProSetMessagePayload
|
||||||
"End the user's pro subscription"
|
"End the user's pro subscription"
|
||||||
unsubscribe: UnsubscribePayload
|
unsubscribe: ProUnsubscribePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
"Autogenerated return type of ProSetDiscord"
|
||||||
|
type ProSetDiscordPayload {
|
||||||
|
discord: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
"Autogenerated return type of ProSetMessage"
|
||||||
|
type ProSetMessagePayload {
|
||||||
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
"A subscription to Kitsu PRO"
|
"A subscription to Kitsu PRO"
|
||||||
@ -1763,6 +1770,11 @@ type ProSubscription implements WithTimestamps {
|
|||||||
updatedAt: ISO8601DateTime!
|
updatedAt: ISO8601DateTime!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Autogenerated return type of ProUnsubscribe"
|
||||||
|
type ProUnsubscribePayload {
|
||||||
|
expiresAt: ISO8601DateTime
|
||||||
|
}
|
||||||
|
|
||||||
"A company involved in the creation or localization of media"
|
"A company involved in the creation or localization of media"
|
||||||
type Producer implements WithTimestamps {
|
type Producer implements WithTimestamps {
|
||||||
createdAt: ISO8601DateTime!
|
createdAt: ISO8601DateTime!
|
||||||
@ -1814,7 +1826,8 @@ type Profile implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [FollowSortOption]
|
||||||
): ProfileConnection!
|
): ProfileConnection!
|
||||||
"People the user is following"
|
"People the user is following"
|
||||||
following(
|
following(
|
||||||
@ -1825,7 +1838,8 @@ type Profile implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [FollowSortOption]
|
||||||
): ProfileConnection!
|
): ProfileConnection!
|
||||||
"What the user identifies as"
|
"What the user identifies as"
|
||||||
gender: String
|
gender: String
|
||||||
@ -1870,7 +1884,8 @@ type Profile implements WithTimestamps {
|
|||||||
"Returns the first _n_ elements from the list."
|
"Returns the first _n_ elements from the list."
|
||||||
first: Int,
|
first: Int,
|
||||||
"Returns the last _n_ elements from the list."
|
"Returns the last _n_ elements from the list."
|
||||||
last: Int
|
last: Int,
|
||||||
|
sort: [PostSortOption]
|
||||||
): PostConnection!
|
): PostConnection!
|
||||||
"The message this user has submitted to the Hall of Fame"
|
"The message this user has submitted to the Hall of Fame"
|
||||||
proMessage: String
|
proMessage: String
|
||||||
@ -2223,20 +2238,6 @@ type Session {
|
|||||||
profile: Profile
|
profile: Profile
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of SetDiscord"
|
|
||||||
type SetDiscordPayload {
|
|
||||||
discord: String!
|
|
||||||
"Graphql Errors"
|
|
||||||
errors: [Generic!]
|
|
||||||
}
|
|
||||||
|
|
||||||
"Autogenerated return type of SetMessage"
|
|
||||||
type SetMessagePayload {
|
|
||||||
"Graphql Errors"
|
|
||||||
errors: [Generic!]
|
|
||||||
message: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
"A link to a user's profile on an external site."
|
"A link to a user's profile on an external site."
|
||||||
type SiteLink implements WithTimestamps {
|
type SiteLink implements WithTimestamps {
|
||||||
"The user profile the site is linked to."
|
"The user profile the site is linked to."
|
||||||
@ -2349,20 +2350,6 @@ type TitlesList {
|
|||||||
localized(locales: [String!]): Map!
|
localized(locales: [String!]): Map!
|
||||||
}
|
}
|
||||||
|
|
||||||
"Autogenerated return type of UnlockPost"
|
|
||||||
type UnlockPostPayload {
|
|
||||||
"Graphql Errors"
|
|
||||||
errors: [Generic!]
|
|
||||||
post: Post
|
|
||||||
}
|
|
||||||
|
|
||||||
"Autogenerated return type of Unsubscribe"
|
|
||||||
type UnsubscribePayload {
|
|
||||||
"Graphql Errors"
|
|
||||||
errors: [Generic!]
|
|
||||||
expiresAt: ISO8601DateTime
|
|
||||||
}
|
|
||||||
|
|
||||||
"The media video."
|
"The media video."
|
||||||
type Video implements Streamable & WithTimestamps {
|
type Video implements Streamable & WithTimestamps {
|
||||||
createdAt: ISO8601DateTime!
|
createdAt: ISO8601DateTime!
|
||||||
@ -2464,6 +2451,23 @@ enum CharacterRoleEnum {
|
|||||||
RECURRING
|
RECURRING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CommentLikeSortEnum {
|
||||||
|
CREATED_AT
|
||||||
|
FOLLOWING
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CommentSortEnum {
|
||||||
|
CREATED_AT
|
||||||
|
FOLLOWING
|
||||||
|
LIKES_COUNT
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FollowSortEnum {
|
||||||
|
CREATED_AT
|
||||||
|
FOLLOWING_FOLLOWED
|
||||||
|
FOLLOWING_FOLLOWER
|
||||||
|
}
|
||||||
|
|
||||||
enum LibraryEntryStatusEnum {
|
enum LibraryEntryStatusEnum {
|
||||||
"The user completed this media."
|
"The user completed this media."
|
||||||
COMPLETED
|
COMPLETED
|
||||||
@ -2554,6 +2558,15 @@ enum MediaTypeEnum {
|
|||||||
MANGA
|
MANGA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PostLikeSortEnum {
|
||||||
|
CREATED_AT
|
||||||
|
FOLLOWING
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PostSortEnum {
|
||||||
|
CREATED_AT
|
||||||
|
}
|
||||||
|
|
||||||
enum ProTierEnum {
|
enum ProTierEnum {
|
||||||
"Aozora Pro (only hides ads)"
|
"Aozora Pro (only hides ads)"
|
||||||
AO_PRO @deprecated(reason : "No longer for sale")
|
AO_PRO @deprecated(reason : "No longer for sale")
|
||||||
@ -2618,6 +2631,11 @@ enum SitePermissionEnum {
|
|||||||
DATABASE_MOD
|
DATABASE_MOD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SortDirection {
|
||||||
|
ASCENDING
|
||||||
|
DESCENDING
|
||||||
|
}
|
||||||
|
|
||||||
enum TitleLanguagePreferenceEnum {
|
enum TitleLanguagePreferenceEnum {
|
||||||
"Prefer the most commonly-used title for media"
|
"Prefer the most commonly-used title for media"
|
||||||
CANONICAL
|
CANONICAL
|
||||||
@ -2658,6 +2676,16 @@ input AnimeUpdateInput {
|
|||||||
youtubeTrailerVideoId: String
|
youtubeTrailerVideoId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CommentLikeSortOption {
|
||||||
|
direction: SortDirection!
|
||||||
|
on: CommentLikeSortEnum!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CommentSortOption {
|
||||||
|
direction: SortDirection!
|
||||||
|
on: CommentSortEnum!
|
||||||
|
}
|
||||||
|
|
||||||
input EpisodeCreateInput {
|
input EpisodeCreateInput {
|
||||||
description: Map
|
description: Map
|
||||||
length: Int
|
length: Int
|
||||||
@ -2679,6 +2707,11 @@ input EpisodeUpdateInput {
|
|||||||
titles: TitlesListInput
|
titles: TitlesListInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input FollowSortOption {
|
||||||
|
direction: SortDirection!
|
||||||
|
on: FollowSortEnum!
|
||||||
|
}
|
||||||
|
|
||||||
input GenericDeleteInput {
|
input GenericDeleteInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
@ -2768,8 +2801,19 @@ input MappingUpdateInput {
|
|||||||
itemType: MappingItemEnum
|
itemType: MappingItemEnum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input PostLikeSortOption {
|
||||||
|
direction: SortDirection!
|
||||||
|
on: PostLikeSortEnum!
|
||||||
|
}
|
||||||
|
|
||||||
|
input PostSortOption {
|
||||||
|
direction: SortDirection!
|
||||||
|
on: PostSortEnum!
|
||||||
|
}
|
||||||
|
|
||||||
input TitlesListInput {
|
input TitlesListInput {
|
||||||
alternatives: [String!]
|
alternatives: [String!]
|
||||||
|
canonical: String
|
||||||
canonicalLocale: String
|
canonicalLocale: String
|
||||||
localized: Map
|
localized: Map
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ function checkFolderPermissions(ConfigInterface $config): array
|
|||||||
$errors = [];
|
$errors = [];
|
||||||
$publicDir = $config->get('asset_dir');
|
$publicDir = $config->get('asset_dir');
|
||||||
|
|
||||||
$APP_DIR = _dir(dirname(__DIR__, 2), '/app');
|
$APP_DIR = _dir($config->get('root'), 'app');
|
||||||
|
|
||||||
$pathMap = [
|
$pathMap = [
|
||||||
'app/config' => "{$APP_DIR}/config",
|
'app/config' => "{$APP_DIR}/config",
|
||||||
@ -211,7 +211,9 @@ function checkFolderPermissions(ConfigInterface $config): array
|
|||||||
|
|
||||||
if ( ! $writable)
|
if ( ! $writable)
|
||||||
{
|
{
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$errors['writable'][] = $pretty;
|
$errors['writable'][] = $pretty;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,6 +294,7 @@ function getLocalImg (string $kitsuUrl, $webp = TRUE): string
|
|||||||
/**
|
/**
|
||||||
* Create a transparent placeholder image
|
* Create a transparent placeholder image
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @param int|null $width
|
* @param int|null $width
|
||||||
* @param int|null $height
|
* @param int|null $height
|
||||||
@ -378,7 +381,6 @@ function colNotEmpty(array $search, string $key): bool
|
|||||||
*
|
*
|
||||||
* @param CacheInterface $cache
|
* @param CacheInterface $cache
|
||||||
* @return bool
|
* @return bool
|
||||||
* @throws Throwable
|
|
||||||
*/
|
*/
|
||||||
function clearCache(CacheInterface $cache): bool
|
function clearCache(CacheInterface $cache): bool
|
||||||
{
|
{
|
||||||
@ -393,9 +395,7 @@ function clearCache(CacheInterface $cache): bool
|
|||||||
$userData = array_filter((array)$userData, static fn ($value) => $value !== NULL);
|
$userData = array_filter((array)$userData, static fn ($value) => $value !== NULL);
|
||||||
$cleared = $cache->clear();
|
$cleared = $cache->clear();
|
||||||
|
|
||||||
$saved = ( ! empty($userData))
|
$saved = ( ! empty($userData)) ? $cache->setMultiple($userData) : TRUE;
|
||||||
? $cache->setMultiple($userData)
|
|
||||||
: TRUE;
|
|
||||||
|
|
||||||
return $cleared && $saved;
|
return $cleared && $saved;
|
||||||
}
|
}
|
||||||
|
@ -61,14 +61,16 @@ abstract class BaseCommand extends Command {
|
|||||||
|
|
||||||
if ($fgColor !== NULL)
|
if ($fgColor !== NULL)
|
||||||
{
|
{
|
||||||
$fgColor = (string)$fgColor;
|
$fgColor = (int)$fgColor;
|
||||||
}
|
}
|
||||||
if ($bgColor !== NULL)
|
if ($bgColor !== NULL)
|
||||||
{
|
{
|
||||||
$bgColor = (string)$bgColor;
|
$bgColor = (int)$bgColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// color message
|
// Colorize the CLI output
|
||||||
|
// the documented type for the function is wrong
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
$message = Colors::colorize($message, $fgColor, $bgColor);
|
$message = Colors::colorize($message, $fgColor, $bgColor);
|
||||||
|
|
||||||
// create the box
|
// create the box
|
||||||
@ -142,13 +144,16 @@ abstract class BaseCommand extends Command {
|
|||||||
{
|
{
|
||||||
if ($fgColor !== NULL)
|
if ($fgColor !== NULL)
|
||||||
{
|
{
|
||||||
$fgColor = (string)$fgColor;
|
$fgColor = (int)$fgColor;
|
||||||
}
|
}
|
||||||
if ($bgColor !== NULL)
|
if ($bgColor !== NULL)
|
||||||
{
|
{
|
||||||
$bgColor = (string)$bgColor;
|
$bgColor = (int)$bgColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Colorize the CLI output
|
||||||
|
// the documented type for the function is wrong
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
$message = Colors::colorize($message, $fgColor, $bgColor);
|
$message = Colors::colorize($message, $fgColor, $bgColor);
|
||||||
$this->getConsole()->writeln($message);
|
$this->getConsole()->writeln($message);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Command;
|
namespace Aviat\AnimeClient\Command;
|
||||||
|
|
||||||
|
use Aviat\Ion\JsonException;
|
||||||
use ConsoleKit\Widgets;
|
use ConsoleKit\Widgets;
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\{
|
use Aviat\AnimeClient\API\{
|
||||||
@ -288,7 +289,15 @@ final class SyncLists extends BaseCommand {
|
|||||||
// This uses a static so I don't have to fetch this list twice for a count
|
// This uses a static so I don't have to fetch this list twice for a count
|
||||||
if ($list[$type] === NULL)
|
if ($list[$type] === NULL)
|
||||||
{
|
{
|
||||||
$list[$type] = $this->anilistModel->getSyncList(strtoupper($type));
|
try
|
||||||
|
{
|
||||||
|
$list[$type] = $this->anilistModel->getSyncList(strtoupper($type));
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
$this->echoErrorBox('Anlist API exception. Can not sync.');
|
||||||
|
die();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $list[$type];
|
return $list[$type];
|
||||||
@ -354,7 +363,7 @@ final class SyncLists extends BaseCommand {
|
|||||||
'progress' => $listItem['progress'],
|
'progress' => $listItem['progress'],
|
||||||
// Comparision is done on 1-10 scale,
|
// Comparision is done on 1-10 scale,
|
||||||
// Kitsu returns 1-20 scale.
|
// Kitsu returns 1-20 scale.
|
||||||
'rating' => $listItem['rating'] / 2,
|
'rating' => (int) $listItem['rating'] / 2,
|
||||||
'reconsumeCount' => $listItem['reconsumeCount'],
|
'reconsumeCount' => $listItem['reconsumeCount'],
|
||||||
'reconsuming' => $listItem['reconsuming'],
|
'reconsuming' => $listItem['reconsuming'],
|
||||||
'status' => strtolower($listItem['status']),
|
'status' => strtolower($listItem['status']),
|
||||||
@ -404,7 +413,7 @@ final class SyncLists extends BaseCommand {
|
|||||||
|
|
||||||
$malIds = array_keys($anilistList);
|
$malIds = array_keys($anilistList);
|
||||||
$kitsuMalIds = array_map('intval', array_column($kitsuList, 'malId'));
|
$kitsuMalIds = array_map('intval', array_column($kitsuList, 'malId'));
|
||||||
$missingMalIds = array_filter(array_diff($kitsuMalIds, $malIds), fn ($id) => ! in_array($id, $kitsuMalIds));
|
$missingMalIds = array_filter($malIds, fn ($id) => ! in_array($id, $kitsuMalIds));
|
||||||
|
|
||||||
// Add items on Anilist, but not Kitsu to Kitsu
|
// Add items on Anilist, but not Kitsu to Kitsu
|
||||||
foreach($missingMalIds as $mid)
|
foreach($missingMalIds as $mid)
|
||||||
@ -599,7 +608,7 @@ final class SyncLists extends BaseCommand {
|
|||||||
$kitsuItem['data']['ratingTwenty'] !== 0
|
$kitsuItem['data']['ratingTwenty'] !== 0
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$update['data']['ratingTwenty'] = $kitsuItem['data']['ratingTwenty'];
|
$update['data']['ratingTwenty'] = $kitsuItem['data']['rating'];
|
||||||
$return['updateType'][] = Enum\API::ANILIST;
|
$return['updateType'][] = Enum\API::ANILIST;
|
||||||
}
|
}
|
||||||
else if($dateDiff === self::ANILIST_GREATER && $anilistItem['data']['rating'] !== 0)
|
else if($dateDiff === self::ANILIST_GREATER && $anilistItem['data']['rating'] !== 0)
|
||||||
@ -683,7 +692,7 @@ final class SyncLists extends BaseCommand {
|
|||||||
// Anilist returns a rating between 1-100
|
// Anilist returns a rating between 1-100
|
||||||
// Kitsu expects a rating from 1-20
|
// Kitsu expects a rating from 1-20
|
||||||
'rating' => (((int)$anilistItem['data']['rating']) > 0)
|
'rating' => (((int)$anilistItem['data']['rating']) > 0)
|
||||||
? $anilistItem['data']['rating'] / 5
|
? (int) $anilistItem['data']['rating'] / 5
|
||||||
: 0,
|
: 0,
|
||||||
'reconsumeCount' => $anilistItem['data']['reconsumeCount'],
|
'reconsumeCount' => $anilistItem['data']['reconsumeCount'],
|
||||||
'reconsuming' => $anilistItem['data']['reconsuming'],
|
'reconsuming' => $anilistItem['data']['reconsuming'],
|
||||||
@ -738,7 +747,7 @@ final class SyncLists extends BaseCommand {
|
|||||||
$responseData = Json::decode($response);
|
$responseData = Json::decode($response);
|
||||||
|
|
||||||
$id = $itemsToUpdate[$key]['id'];
|
$id = $itemsToUpdate[$key]['id'];
|
||||||
$mal_id = $itemsToUpdate[$key]['mal_id'];
|
$mal_id = $itemsToUpdate[$key]['mal_id'] ?? NULL;
|
||||||
if ( ! array_key_exists('errors', $responseData))
|
if ( ! array_key_exists('errors', $responseData))
|
||||||
{
|
{
|
||||||
$verb = ($action === SyncAction::UPDATE) ? 'updated' : 'created';
|
$verb = ($action === SyncAction::UPDATE) ? 'updated' : 'created';
|
||||||
|
@ -126,6 +126,7 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Set the current url in the session as the target of a future redirect
|
* Set the current url in the session as the target of a future redirect
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param string|NULL $url
|
* @param string|NULL $url
|
||||||
* @throws ContainerException
|
* @throws ContainerException
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
@ -141,7 +142,7 @@ class Controller {
|
|||||||
|
|
||||||
$util = $this->container->get('util');
|
$util = $this->container->get('util');
|
||||||
$doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri();
|
$doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri();
|
||||||
$isLoginPage = (bool) strpos($serverParams['HTTP_REFERER'], 'login');
|
$isLoginPage = str_contains($serverParams['HTTP_REFERER'], 'login');
|
||||||
|
|
||||||
// Don't attempt to set the redirect url if
|
// Don't attempt to set the redirect url if
|
||||||
// the page is one of the form type pages,
|
// the page is one of the form type pages,
|
||||||
@ -166,6 +167,7 @@ class Controller {
|
|||||||
*
|
*
|
||||||
* If one is not set, redirect to default url
|
* If one is not set, redirect to default url
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -179,6 +181,7 @@ class Controller {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current user is authenticated, else error and exit
|
* Check if the current user is authenticated, else error and exit
|
||||||
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
protected function checkAuth(): void
|
protected function checkAuth(): void
|
||||||
{
|
{
|
||||||
@ -195,12 +198,10 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Get the string output of a partial template
|
* Get the string output of a partial template
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param HtmlView $view
|
* @param HtmlView $view
|
||||||
* @param string $template
|
* @param string $template
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @throws InvalidArgumentException
|
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function loadPartial(HtmlView $view, string $template, array $data = []): string
|
protected function loadPartial(HtmlView $view, string $template, array $data = []): string
|
||||||
@ -229,19 +230,18 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Render a template with header and footer
|
* Render a template with header and footer
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param HtmlView $view
|
* @param HtmlView $view
|
||||||
* @param string $template
|
* @param string $template
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @return HtmlView
|
* @return HtmlView
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
*/
|
*/
|
||||||
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
|
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
|
||||||
{
|
{
|
||||||
$csp = [
|
$csp = [
|
||||||
"default-src 'self'",
|
"default-src 'self'",
|
||||||
"object-src 'none'",
|
"object-src 'none'",
|
||||||
'frame-src *.youtube.com',
|
"child-src 'self' *.youtube.com polyfill.io",
|
||||||
];
|
];
|
||||||
|
|
||||||
$view->addHeader('Content-Security-Policy', implode('; ', $csp));
|
$view->addHeader('Content-Security-Policy', implode('; ', $csp));
|
||||||
@ -261,11 +261,10 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* 404 action
|
* 404 action
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param string $title
|
* @param string $title
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function notFound(
|
public function notFound(
|
||||||
@ -283,13 +282,12 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Display a generic error page
|
* Display a generic error page
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param int $httpCode
|
* @param int $httpCode
|
||||||
* @param string $title
|
* @param string $title
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @param string $longMessage
|
* @param string $longMessage
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void
|
public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void
|
||||||
@ -304,6 +302,7 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Redirect to the default controller/url from an empty path
|
* Redirect to the default controller/url from an empty path
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -317,6 +316,7 @@ class Controller {
|
|||||||
* Set a session flash variable to display a message on
|
* Set a session flash variable to display a message on
|
||||||
* next page load
|
* next page load
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @return void
|
* @return void
|
||||||
@ -352,12 +352,11 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Add a message box to the page
|
* Add a message box to the page
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param HtmlView $view
|
* @param HtmlView $view
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function showMessage(HtmlView $view, string $type, string $message): string
|
protected function showMessage(HtmlView $view, string $type, string $message): string
|
||||||
@ -371,13 +370,12 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Output a template to HTML, using the provided data
|
* Output a template to HTML, using the provided data
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param string $template
|
* @param string $template
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @param HtmlView|NULL $view
|
* @param HtmlView|NULL $view
|
||||||
* @param int $code
|
* @param int $code
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function outputHTML(string $template, array $data = [], $view = NULL, int $code = 200): void
|
protected function outputHTML(string $template, array $data = [], $view = NULL, int $code = 200): void
|
||||||
@ -394,6 +392,7 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Output a JSON Response
|
* Output a JSON Response
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param mixed $data
|
* @param mixed $data
|
||||||
* @param int $code - the http status code
|
* @param int $code - the http status code
|
||||||
* @throws DoubleRenderException
|
* @throws DoubleRenderException
|
||||||
@ -410,6 +409,7 @@ class Controller {
|
|||||||
/**
|
/**
|
||||||
* Redirect to the selected page
|
* Redirect to the selected page
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @param int $code
|
* @param int $code
|
||||||
* @return void
|
* @return void
|
||||||
|
@ -251,7 +251,7 @@ final class Anime extends BaseController {
|
|||||||
{
|
{
|
||||||
$this->checkAuth();
|
$this->checkAuth();
|
||||||
|
|
||||||
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
|
if (str_contains($this->request->getHeader('content-type')[0], 'application/json'))
|
||||||
{
|
{
|
||||||
$data = Json::decode((string)$this->request->getBody());
|
$data = Json::decode((string)$this->request->getBody());
|
||||||
}
|
}
|
||||||
@ -302,8 +302,6 @@ final class Anime extends BaseController {
|
|||||||
* View details of an anime
|
* View details of an anime
|
||||||
*
|
*
|
||||||
* @param string $id
|
* @param string $id
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
@ -64,8 +64,6 @@ final class Manga extends Controller {
|
|||||||
*
|
*
|
||||||
* @param string $status
|
* @param string $status
|
||||||
* @param string $view
|
* @param string $view
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -251,7 +249,7 @@ final class Manga extends Controller {
|
|||||||
{
|
{
|
||||||
$this->checkAuth();
|
$this->checkAuth();
|
||||||
|
|
||||||
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
|
if (str_contains($this->request->getHeader('content-type')[0], 'application/json'))
|
||||||
{
|
{
|
||||||
$data = Json::decode((string)$this->request->getBody());
|
$data = Json::decode((string)$this->request->getBody());
|
||||||
}
|
}
|
||||||
@ -298,8 +296,6 @@ final class Manga extends Controller {
|
|||||||
* View details of an manga
|
* View details of an manga
|
||||||
*
|
*
|
||||||
* @param string $id
|
* @param string $id
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
* @return void
|
* @return void
|
||||||
@ -331,8 +327,6 @@ final class Manga extends Controller {
|
|||||||
/**
|
/**
|
||||||
* View details of a random manga
|
* View details of a random manga
|
||||||
*
|
*
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
* @return void
|
* @return void
|
||||||
|
@ -314,10 +314,8 @@ final class Dispatcher extends RoutingBase {
|
|||||||
/**
|
/**
|
||||||
* Get the appropriate params for the error page
|
* Get the appropriate params for the error page
|
||||||
* passed on the failed route
|
* passed on the failed route
|
||||||
*
|
|
||||||
* @return array|false
|
|
||||||
*/
|
*/
|
||||||
protected function getErrorParams()
|
protected function getErrorParams(): array
|
||||||
{
|
{
|
||||||
$logger = $this->container->getLogger();
|
$logger = $this->container->getLogger();
|
||||||
$failure = $this->matcher->getFailedRoute();
|
$failure = $this->matcher->getFailedRoute();
|
||||||
|
@ -25,22 +25,6 @@ final class Picture {
|
|||||||
|
|
||||||
use ContainerAware;
|
use ContainerAware;
|
||||||
|
|
||||||
private const MIME_MAP = [
|
|
||||||
'apng' => 'image/vnd.mozilla.apng',
|
|
||||||
'bmp' => 'image/bmp',
|
|
||||||
'gif' => 'image/gif',
|
|
||||||
'ico' => 'image/x-icon',
|
|
||||||
'jpeg' => 'image/jpeg',
|
|
||||||
'jpf' => 'image/jpx',
|
|
||||||
'jpg' => 'image/jpeg',
|
|
||||||
'jpx' => 'image/jpx',
|
|
||||||
'png' => 'image/png',
|
|
||||||
'svg' => 'image/svg+xml',
|
|
||||||
'tif' => 'image/tiff',
|
|
||||||
'tiff' => 'image/tiff',
|
|
||||||
'webp' => 'image/webp',
|
|
||||||
];
|
|
||||||
|
|
||||||
private const SIMPLE_IMAGE_TYPES = [
|
private const SIMPLE_IMAGE_TYPES = [
|
||||||
'gif',
|
'gif',
|
||||||
'jpeg',
|
'jpeg',
|
||||||
@ -68,12 +52,12 @@ final class Picture {
|
|||||||
|
|
||||||
// If it is a placeholder image, make the
|
// If it is a placeholder image, make the
|
||||||
// fallback a png, not a jpg
|
// fallback a png, not a jpg
|
||||||
if (strpos($uri, 'placeholder') !== FALSE)
|
if (str_contains($uri, 'placeholder'))
|
||||||
{
|
{
|
||||||
$fallbackExt = 'png';
|
$fallbackExt = 'png';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($uri, '//') === FALSE)
|
if ( ! str_contains($uri, '//'))
|
||||||
{
|
{
|
||||||
$uri = $urlGenerator->assetUrl($uri);
|
$uri = $urlGenerator->assetUrl($uri);
|
||||||
}
|
}
|
||||||
@ -82,22 +66,34 @@ final class Picture {
|
|||||||
$ext = array_pop($urlParts);
|
$ext = array_pop($urlParts);
|
||||||
$path = implode('.', $urlParts);
|
$path = implode('.', $urlParts);
|
||||||
|
|
||||||
$mime = array_key_exists($ext, static::MIME_MAP)
|
$mime = match ($ext) {
|
||||||
? static::MIME_MAP[$ext]
|
'avif' => 'image/avif',
|
||||||
: 'image/jpeg';
|
'apng' => 'image/vnd.mozilla.apng',
|
||||||
|
'bmp' => 'image/bmp',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'ico' => 'image/x-icon',
|
||||||
|
'jpf', 'jpx' => 'image/jpx',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'svg' => 'image/svg+xml',
|
||||||
|
'tif', 'tiff' => 'image/tiff',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
default => 'image/jpeg',
|
||||||
|
};
|
||||||
|
|
||||||
$fallbackMime = array_key_exists($fallbackExt, static::MIME_MAP)
|
$fallbackMime = match ($fallbackExt) {
|
||||||
? static::MIME_MAP[$fallbackExt]
|
'gif' => 'image/gif',
|
||||||
: 'image/jpeg';
|
'png' => 'image/png',
|
||||||
|
default => 'image/jpeg',
|
||||||
|
};
|
||||||
|
|
||||||
// For image types that are well-established, just return a
|
// For image types that are well-established, just return a
|
||||||
// simple <img /> element instead
|
// simple <img /> element instead
|
||||||
if (
|
if (
|
||||||
$ext === $fallbackExt ||
|
$ext === $fallbackExt ||
|
||||||
\in_array($ext, static::SIMPLE_IMAGE_TYPES, TRUE)
|
\in_array($ext, Picture::SIMPLE_IMAGE_TYPES, TRUE)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$attrs = ( ! empty($imgAttrs))
|
$attrs = (count($imgAttrs) > 1)
|
||||||
? $imgAttrs
|
? $imgAttrs
|
||||||
: $picAttrs;
|
: $picAttrs;
|
||||||
|
|
||||||
|
@ -236,6 +236,9 @@ abstract class AbstractType implements ArrayAccess, Countable {
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
final protected function fromObject(mixed $parent = null): float|null|bool|int|array|string
|
final protected function fromObject(mixed $parent = null): float|null|bool|int|array|string
|
||||||
{
|
{
|
||||||
$object = $parent ?? $this;
|
$object = $parent ?? $this;
|
||||||
|
@ -34,9 +34,6 @@ class Anime extends AbstractType {
|
|||||||
|
|
||||||
public array $genres = [];
|
public array $genres = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $id = '';
|
public string $id = '';
|
||||||
|
|
||||||
public array $included = [];
|
public array $included = [];
|
||||||
|
@ -46,9 +46,6 @@ final class AnimeListItem extends AbstractType {
|
|||||||
|
|
||||||
public int $rewatched = 0;
|
public int $rewatched = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|int
|
|
||||||
*/
|
|
||||||
public string|int $user_rating = '';
|
public string|int $user_rating = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,9 +24,6 @@ final class Character extends AbstractType {
|
|||||||
|
|
||||||
public ?string $description;
|
public ?string $description;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $id;
|
public string $id;
|
||||||
|
|
||||||
public array $included = [];
|
public array $included = [];
|
||||||
|
@ -32,6 +32,8 @@ class Config extends AbstractType {
|
|||||||
// Settings in config.toml
|
// Settings in config.toml
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public string $root; // Path to app root
|
||||||
|
|
||||||
public ?string $asset_path; // Path to public folder for urls
|
public ?string $asset_path; // Path to public folder for urls
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,8 +64,6 @@ class Config extends AbstractType {
|
|||||||
/**
|
/**
|
||||||
* Default list view type
|
* Default list view type
|
||||||
* 'cover_view' or 'list_view'
|
* 'cover_view' or 'list_view'
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public ?string $default_view_type;
|
public ?string $default_view_type;
|
||||||
|
|
||||||
@ -71,21 +71,13 @@ class Config extends AbstractType {
|
|||||||
|
|
||||||
public bool $secure_urls = TRUE;
|
public bool $secure_urls = TRUE;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|bool
|
|
||||||
*/
|
|
||||||
public string|bool $show_anime_collection = FALSE;
|
public string|bool $show_anime_collection = FALSE;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|bool
|
|
||||||
*/
|
|
||||||
public string|bool $show_manga_collection = FALSE;
|
public string|bool $show_manga_collection = FALSE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CSS theme: light, dark, or auto-switching
|
* CSS theme: light, dark, or auto-switching
|
||||||
* 'auto', 'light', or 'dark'
|
* 'auto', 'light', or 'dark'
|
||||||
*
|
|
||||||
* @var string|null
|
|
||||||
*/
|
*/
|
||||||
public ?string $theme = 'auto';
|
public ?string $theme = 'auto';
|
||||||
|
|
||||||
|
@ -19,10 +19,7 @@ namespace Aviat\AnimeClient\Types\Config;
|
|||||||
use Aviat\AnimeClient\Types\AbstractType;
|
use Aviat\AnimeClient\Types\AbstractType;
|
||||||
|
|
||||||
class Anilist extends AbstractType {
|
class Anilist extends AbstractType {
|
||||||
/**
|
public bool|string $enabled = FALSE;
|
||||||
* @var bool|string
|
|
||||||
*/
|
|
||||||
public $enabled = FALSE;
|
|
||||||
|
|
||||||
public ?string $client_id;
|
public ?string $client_id;
|
||||||
|
|
||||||
@ -30,10 +27,7 @@ class Anilist extends AbstractType {
|
|||||||
|
|
||||||
public ?string $access_token;
|
public ?string $access_token;
|
||||||
|
|
||||||
/**
|
public int|string|null $access_token_expires;
|
||||||
* @var int|string|null
|
|
||||||
*/
|
|
||||||
public $access_token_expires;
|
|
||||||
|
|
||||||
public ?string $refresh_token;
|
public ?string $refresh_token;
|
||||||
|
|
||||||
|
@ -23,10 +23,7 @@ class Cache extends AbstractType {
|
|||||||
|
|
||||||
public ?string $host;
|
public ?string $host;
|
||||||
|
|
||||||
/**
|
public string|int|null $port;
|
||||||
* @var string|int|null
|
|
||||||
*/
|
|
||||||
public $port;
|
|
||||||
|
|
||||||
public ?string $database;
|
public ?string $database;
|
||||||
|
|
||||||
|
@ -19,38 +19,18 @@ namespace Aviat\AnimeClient\Types\Config;
|
|||||||
use Aviat\AnimeClient\Types\AbstractType;
|
use Aviat\AnimeClient\Types\AbstractType;
|
||||||
|
|
||||||
class Database extends AbstractType {
|
class Database extends AbstractType {
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $type = 'sqlite';
|
public string $type = 'sqlite';
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $host;
|
public ?string $host;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $user;
|
public ?string $user;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $pass;
|
public ?string $pass;
|
||||||
|
|
||||||
/**
|
public string|int|null $port;
|
||||||
* @var string|int|null
|
|
||||||
*/
|
|
||||||
public $port;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $database;
|
public ?string $database;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $file;
|
public ?string $file;
|
||||||
}
|
}
|
@ -20,14 +20,8 @@ namespace Aviat\AnimeClient\Types;
|
|||||||
* Type representing an Anime object for display
|
* Type representing an Anime object for display
|
||||||
*/
|
*/
|
||||||
class FormItem extends AbstractType {
|
class FormItem extends AbstractType {
|
||||||
/**
|
|
||||||
* @var string|int
|
|
||||||
*/
|
|
||||||
public string|int $id;
|
public string|int $id;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|int|null
|
|
||||||
*/
|
|
||||||
public string|int|null $mal_id;
|
public string|int|null $mal_id;
|
||||||
|
|
||||||
public ?FormItemData $data;
|
public ?FormItemData $data;
|
||||||
|
@ -24,32 +24,17 @@ class FormItemData extends AbstractType {
|
|||||||
|
|
||||||
public ?bool $private = FALSE;
|
public ?bool $private = FALSE;
|
||||||
|
|
||||||
/**
|
public ?int $progress = NULL;
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $progress;
|
|
||||||
|
|
||||||
/**
|
public ?int $rating;
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $rating;
|
|
||||||
|
|
||||||
/**
|
public ?int $ratingTwenty = NULL;
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $ratingTwenty;
|
|
||||||
|
|
||||||
/**
|
public string|int $reconsumeCount;
|
||||||
* @var string|int
|
|
||||||
*/
|
|
||||||
public $reconsumeCount;
|
|
||||||
|
|
||||||
public bool $reconsuming = FALSE;
|
public bool $reconsuming = FALSE;
|
||||||
|
|
||||||
/**
|
public string $status;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $status;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* W3C Format Date string
|
* W3C Format Date string
|
||||||
|
@ -42,7 +42,7 @@ class HistoryItem extends AbstractType {
|
|||||||
/**
|
/**
|
||||||
* The kind of history event
|
* The kind of history event
|
||||||
*/
|
*/
|
||||||
public string $kind = '';
|
public ?string $kind = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the item was last updated
|
* When the item was last updated
|
||||||
|
@ -20,60 +20,31 @@ namespace Aviat\AnimeClient\Types;
|
|||||||
* Type representing an Anime object for display
|
* Type representing an Anime object for display
|
||||||
*/
|
*/
|
||||||
final class MangaListItem extends AbstractType {
|
final class MangaListItem extends AbstractType {
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $id;
|
|
||||||
|
|
||||||
/**
|
public string $id;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $mal_id;
|
|
||||||
|
|
||||||
/**
|
public ?string $mal_id;
|
||||||
* @var array
|
|
||||||
*/
|
public array $chapters = [
|
||||||
public $chapters = [
|
|
||||||
'read' => 0,
|
'read' => 0,
|
||||||
'total' => 0,
|
'total' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
public array $volumes = [
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public $volumes = [
|
|
||||||
'read' => '-',
|
'read' => '-',
|
||||||
'total' => 0,
|
'total' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
public object $manga;
|
||||||
* @var object
|
|
||||||
*/
|
|
||||||
public $manga;
|
|
||||||
|
|
||||||
/**
|
public string $reading_status;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $reading_status;
|
|
||||||
|
|
||||||
/**
|
public ?string $notes;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $notes;
|
|
||||||
|
|
||||||
/**
|
public bool $rereading = false;
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
public $rereading;
|
|
||||||
|
|
||||||
/**
|
public ?int $reread;
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $reread;
|
|
||||||
|
|
||||||
/**
|
public string|int|null $user_rating;
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $user_rating;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,43 +20,19 @@ namespace Aviat\AnimeClient\Types;
|
|||||||
* Type representing the manga represented by the list item
|
* Type representing the manga represented by the list item
|
||||||
*/
|
*/
|
||||||
final class MangaListItemDetail extends AbstractType {
|
final class MangaListItemDetail extends AbstractType {
|
||||||
/**
|
public array $genres = [];
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public $genres;
|
|
||||||
|
|
||||||
/**
|
public string $id;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $id;
|
|
||||||
|
|
||||||
/**
|
public string $image;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $image;
|
|
||||||
|
|
||||||
/**
|
public string $slug;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $slug;
|
|
||||||
|
|
||||||
/**
|
public string $title;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $title;
|
|
||||||
|
|
||||||
/**
|
public array $titles;
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public $titles;
|
|
||||||
|
|
||||||
/**
|
public ?string $type;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $type;
|
|
||||||
|
|
||||||
/**
|
public string $url;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $url;
|
|
||||||
}
|
}
|
||||||
|
@ -22,74 +22,32 @@ use Aviat\AnimeClient\API\Kitsu\Enum\MangaPublishingStatus;
|
|||||||
* Type representing an Anime object for display
|
* Type representing an Anime object for display
|
||||||
*/
|
*/
|
||||||
final class MangaPage extends AbstractType {
|
final class MangaPage extends AbstractType {
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $age_rating;
|
public ?string $age_rating;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $age_rating_guide;
|
public ?string $age_rating_guide;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public array $characters;
|
public array $characters;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int|null
|
|
||||||
*/
|
|
||||||
public ?int $chapter_count;
|
public ?int $chapter_count;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null
|
|
||||||
*/
|
|
||||||
public ?string $cover_image;
|
public ?string $cover_image;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public array $genres;
|
public array $genres;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public array $links;
|
public array $links;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $id;
|
public string $id;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $manga_type;
|
public string $manga_type;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $status = MangaPublishingStatus::FINISHED;
|
public string $status = MangaPublishingStatus::FINISHED;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public array $staff;
|
public array $staff;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $synopsis;
|
public string $synopsis;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $title;
|
public string $title;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public array $titles;
|
public array $titles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,13 +55,7 @@ final class MangaPage extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
public array $titles_more;
|
public array $titles_more;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public string $url;
|
public string $url;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int|null
|
|
||||||
*/
|
|
||||||
public ?int $volume_count;
|
public ?int $volume_count;
|
||||||
}
|
}
|
||||||
|
@ -66,13 +66,8 @@ class Container implements ContainerInterface {
|
|||||||
*
|
*
|
||||||
* @return mixed Entry.
|
* @return mixed Entry.
|
||||||
*/
|
*/
|
||||||
public function get($id): mixed
|
public function get(string $id): mixed
|
||||||
{
|
{
|
||||||
if ( ! \is_string($id))
|
|
||||||
{
|
|
||||||
throw new ContainerException('Id must be a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->has($id))
|
if ($this->has($id))
|
||||||
{
|
{
|
||||||
// Return an object instance, if it already exists
|
// Return an object instance, if it already exists
|
||||||
@ -94,18 +89,13 @@ class Container implements ContainerInterface {
|
|||||||
* Get a new instance of the specified item
|
* Get a new instance of the specified item
|
||||||
*
|
*
|
||||||
* @param string $id - Identifier of the entry to look for.
|
* @param string $id - Identifier of the entry to look for.
|
||||||
* @param array $args - Optional arguments for the factory callable
|
* @param array|null $args - Optional arguments for the factory callable
|
||||||
* @throws NotFoundException - No entry was found for this identifier.
|
* @throws NotFoundException - No entry was found for this identifier.
|
||||||
* @throws ContainerException - Error while retrieving the entry.
|
* @throws ContainerException - Error while retrieving the entry.
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getNew($id, array $args = NULL): mixed
|
public function getNew(string $id, ?array $args = NULL): mixed
|
||||||
{
|
{
|
||||||
if ( ! \is_string($id))
|
|
||||||
{
|
|
||||||
throw new ContainerException('Id must be a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->has($id))
|
if ($this->has($id))
|
||||||
{
|
{
|
||||||
// By default, call a factory with the Container
|
// By default, call a factory with the Container
|
||||||
@ -159,7 +149,7 @@ class Container implements ContainerInterface {
|
|||||||
* @param string $id Identifier of the entry to look for.
|
* @param string $id Identifier of the entry to look for.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function has($id): bool
|
public function has(string $id): bool
|
||||||
{
|
{
|
||||||
return array_key_exists($id, $this->container);
|
return array_key_exists($id, $this->container);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ interface ContainerInterface {
|
|||||||
* @throws Exception\ContainerException Error while retrieving the entry.
|
* @throws Exception\ContainerException Error while retrieving the entry.
|
||||||
* @return mixed Entry.
|
* @return mixed Entry.
|
||||||
*/
|
*/
|
||||||
public function get($id);
|
public function get(string $id): mixed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the container can return an entry for the given identifier.
|
* Returns true if the container can return an entry for the given identifier.
|
||||||
@ -45,7 +45,7 @@ interface ContainerInterface {
|
|||||||
* @param string $id Identifier of the entry to look for.
|
* @param string $id Identifier of the entry to look for.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function has($id): bool;
|
public function has(string $id): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a factory to the container
|
* Add a factory to the container
|
||||||
@ -63,7 +63,7 @@ interface ContainerInterface {
|
|||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return ContainerInterface
|
* @return ContainerInterface
|
||||||
*/
|
*/
|
||||||
public function setInstance(string $id, $value): ContainerInterface;
|
public function setInstance(string $id, mixed $value): ContainerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a new instance of the specified item
|
* Get a new instance of the specified item
|
||||||
@ -71,7 +71,7 @@ interface ContainerInterface {
|
|||||||
* @param string $id
|
* @param string $id
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getNew($id);
|
public function getNew(string $id): mixed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether a logger channel is registered
|
* Determine whether a logger channel is registered
|
||||||
|
@ -21,10 +21,9 @@ use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
|||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
class AnimeListTransformerTest extends AnimeClientTestCase {
|
class AnimeListTransformerTest extends AnimeClientTestCase {
|
||||||
protected $dir;
|
protected string $dir;
|
||||||
protected $beforeTransform;
|
protected array $beforeTransform;
|
||||||
protected $afterTransform;
|
protected AnimeListTransformer $transformer;
|
||||||
protected $transformer;
|
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
@ -36,13 +35,13 @@ class AnimeListTransformerTest extends AnimeClientTestCase {
|
|||||||
$this->transformer = new AnimeListTransformer();
|
$this->transformer = new AnimeListTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransform()
|
public function testTransform(): void
|
||||||
{
|
{
|
||||||
$actual = $this->transformer->transform($this->beforeTransform);
|
$actual = $this->transformer->transform($this->beforeTransform);
|
||||||
$this->assertMatchesSnapshot($actual);
|
$this->assertMatchesSnapshot($actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataUntransform()
|
public function dataUntransform(): array
|
||||||
{
|
{
|
||||||
return [[
|
return [[
|
||||||
'input' => [
|
'input' => [
|
||||||
@ -85,8 +84,9 @@ class AnimeListTransformerTest extends AnimeClientTestCase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider dataUntransform
|
* @dataProvider dataUntransform
|
||||||
|
* @param array $input
|
||||||
*/
|
*/
|
||||||
public function testUntransform($input)
|
public function testUntransform(array $input): void
|
||||||
{
|
{
|
||||||
$actual = $this->transformer->untransform($input);
|
$actual = $this->transformer->untransform($input);
|
||||||
$this->assertMatchesSnapshot($actual);
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
|
class CharacterTransformerTest extends AnimeClientTestCase {
|
||||||
|
protected array $beforeTransform;
|
||||||
|
protected string $dir;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
$raw = Json::decodeFile("{$this->dir}/characterBeforeTransform.json");
|
||||||
|
$this->beforeTransform = $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransform(): void
|
||||||
|
{
|
||||||
|
$actual = (new CharacterTransformer())->transform($this->beforeTransform);
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeHistoryTransformer;
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaHistoryTransformer;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
|
class HistoryTransformerTest extends AnimeClientTestCase {
|
||||||
|
protected array $beforeTransform;
|
||||||
|
protected string $dir;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
$raw = Json::decodeFile("{$this->dir}/historyBeforeTransform.json");
|
||||||
|
$this->beforeTransform = $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnimeTransform(): void
|
||||||
|
{
|
||||||
|
$actual = (new AnimeHistoryTransformer())->transform($this->beforeTransform);
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMangaTransform(): void
|
||||||
|
{
|
||||||
|
$actual = (new MangaHistoryTransformer())->transform($this->beforeTransform);
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
|
class PersonTransformerTest extends AnimeClientTestCase {
|
||||||
|
protected array $beforeTransform;
|
||||||
|
protected string $dir;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
$raw = Json::decodeFile("{$this->dir}/personBeforeTransform.json");
|
||||||
|
$this->beforeTransform = $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransform(): void
|
||||||
|
{
|
||||||
|
$actual = (new PersonTransformer())->transform($this->beforeTransform);
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
|
class UserTransformerTest extends AnimeClientTestCase {
|
||||||
|
protected array $beforeTransform;
|
||||||
|
protected string $dir;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
$raw = Json::decodeFile("{$this->dir}/userBeforeTransform.json");
|
||||||
|
$this->beforeTransform = $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransform(): void
|
||||||
|
{
|
||||||
|
$actual = (new UserTransformer())->transform($this->beforeTransform);
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
{ }
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,20 @@
|
|||||||
|
empty: false
|
||||||
|
about: 'Web Developer, Anime Fan, Reader of VNs, and web comics.'
|
||||||
|
avatar: images/avatars/2644.gif
|
||||||
|
favorites:
|
||||||
|
anime: { 933073: { __typename: Anime, id: '14212', slug: hataraku-saibou-tv, posterImage: { original: { url: 'https://media.kitsu.io/anime/poster_images/14212/original.jpg?1597697195', height: 1050, width: 750 }, views: [{ url: 'https://media.kitsu.io/anime/poster_images/14212/tiny.jpg?1597697195', height: 156, width: 110 }, { url: 'https://media.kitsu.io/anime/poster_images/14212/small.jpg?1597697195', height: 402, width: 284 }, { url: 'https://media.kitsu.io/anime/poster_images/14212/medium.jpg?1597697195', height: 554, width: 390 }, { url: 'https://media.kitsu.io/anime/poster_images/14212/large.jpg?1597697195', height: 780, width: 550 }] }, titles: { canonical: 'Hataraku Saibou', localized: { en: 'Cells at Work!', en_jp: 'Hataraku Saibou', ja_jp: はたらく細胞 } } }, 586217: { __typename: Anime, id: '323', slug: fate-stay-night, posterImage: { original: { url: 'https://media.kitsu.io/anime/poster_images/323/original.jpg?1597698066', height: 1074, width: 760 }, views: [{ url: 'https://media.kitsu.io/anime/poster_images/323/tiny.jpg?1597698066', height: 156, width: 110 }, { url: 'https://media.kitsu.io/anime/poster_images/323/small.jpg?1597698066', height: 402, width: 284 }, { url: 'https://media.kitsu.io/anime/poster_images/323/medium.jpg?1597698066', height: 554, width: 390 }, { url: 'https://media.kitsu.io/anime/poster_images/323/large.jpg?1597698066', height: 780, width: 550 }] }, titles: { canonical: 'Fate/stay night', localized: { en: 'Fate/stay night', en_jp: 'Fate/stay night', en_us: 'Fate/stay night', ja_jp: 'Fate/stay night' } } }, 607473: { __typename: Anime, id: '310', slug: tsukuyomi-moon-phase, posterImage: { original: { url: 'https://media.kitsu.io/anime/poster_images/310/original.jpg?1597690591', height: 320, width: 225 }, views: [{ url: 'https://media.kitsu.io/anime/poster_images/310/tiny.jpg?1597690591', height: 156, width: 110 }, { url: 'https://media.kitsu.io/anime/poster_images/310/small.jpg?1597690591', height: 402, width: 284 }, { url: 'https://media.kitsu.io/anime/poster_images/310/medium.jpg?1597690591', height: 554, width: 390 }, { url: 'https://media.kitsu.io/anime/poster_images/310/large.jpg?1597690591', height: 780, width: 550 }] }, titles: { canonical: 'Tsukuyomi: Moon Phase', localized: { en: 'Tsukuyomi: Moon Phase', en_jp: 'Tsukuyomi: Moon Phase', en_us: 'Tsukuyomi: Moon Phase', ja_jp: '月詠 −MOON PHASE−' } } }, 607472: { __typename: Anime, id: '5992', slug: carnival-phantasm, posterImage: { original: { url: 'https://media.kitsu.io/anime/poster_images/5992/original.jpg?1597697878', height: 693, width: 533 }, views: [{ url: 'https://media.kitsu.io/anime/poster_images/5992/tiny.jpg?1597697878', height: 156, width: 110 }, { url: 'https://media.kitsu.io/anime/poster_images/5992/small.jpg?1597697878', height: 402, width: 284 }, { url: 'https://media.kitsu.io/anime/poster_images/5992/medium.jpg?1597697878', height: 554, width: 390 }, { url: 'https://media.kitsu.io/anime/poster_images/5992/large.jpg?1597697878', height: 780, width: 550 }] }, titles: { canonical: 'Carnival Phantasm', localized: { en_jp: 'Carnival Phantasm', ja_jp: カーニバル・ファンタズム } } }, 636892: { __typename: Anime, id: '6062', slug: nichijou, posterImage: { original: { url: 'https://media.kitsu.io/anime/poster_images/6062/original.jpg?1597696783', height: 2292, width: 1610 }, views: [{ url: 'https://media.kitsu.io/anime/poster_images/6062/tiny.jpg?1597696783', height: 156, width: 110 }, { url: 'https://media.kitsu.io/anime/poster_images/6062/small.jpg?1597696783', height: 402, width: 284 }, { url: 'https://media.kitsu.io/anime/poster_images/6062/medium.jpg?1597696783', height: 554, width: 390 }, { url: 'https://media.kitsu.io/anime/poster_images/6062/large.jpg?1597696783', height: 780, width: 550 }] }, titles: { canonical: Nichijou, localized: { en: 'Nichijou - My Ordinary Life', en_jp: Nichijou, en_us: 'Nichijou - My Ordinary Life', ja_jp: 日常 } } } }
|
||||||
|
character: { 586219: { __typename: Character, id: '6553', slug: saber, image: { original: { url: 'https://media.kitsu.io/characters/images/6553/original.jpg?1483096805' } }, names: { alternatives: ['King of Knights'], canonical: Saber, canonicalLocale: null, localized: { en: Saber, ja_jp: セイバー } } }, 586218: { __typename: Character, id: '6556', slug: rin-tohsaka, image: { original: { url: 'https://media.kitsu.io/characters/images/6556/original.jpg?1483096805' } }, names: { alternatives: { }, canonical: 'Rin Toosaka', canonicalLocale: null, localized: { en: 'Rin Toosaka', ja_jp: '遠坂 凛' } } }, 611365: { __typename: Character, id: '32035', slug: nano-shinonome, image: { original: { url: 'https://media.kitsu.io/characters/images/32035/original.jpg?1483096805' } }, names: { alternatives: { }, canonical: 'Nano Shinonome', canonicalLocale: null, localized: { en: 'Nano Shinonome', ja_jp: '東雲 なの' } } }, 611364: { __typename: Character, id: '32034', slug: mio-naganohara, image: { original: { url: 'https://media.kitsu.io/characters/images/32034/original.jpg?1483096805' } }, names: { alternatives: { }, canonical: 'Mio Naganohara', canonicalLocale: null, localized: { en: 'Mio Naganohara', ja_jp: 長野原みお } } }, 636590: { __typename: Character, id: '31851', slug: aria-holmes-kanzaki, image: { original: { url: 'https://media.kitsu.io/characters/images/31851/original.jpg?1483096805' } }, names: { alternatives: ['Quadra Aria'], canonical: 'Aria Holmes Kanzaki', canonicalLocale: null, localized: { en: 'Aria Holmes Kanzaki', ja_jp: 神崎・H・アリア } } }, 636591: { __typename: Character, id: '25930', slug: taiga-aisaka, image: { original: { url: 'https://media.kitsu.io/characters/images/25930/original.jpg?1483096805' } }, names: { alternatives: ['Palmtop Tiger'], canonical: 'Taiga Aisaka', canonicalLocale: null, localized: { en: 'Taiga Aisaka', ja_jp: '逢坂 大河' } } }, 636593: { __typename: Character, id: '31625', slug: victorique-de-blois, image: { original: { url: 'https://media.kitsu.io/characters/images/31625/original.jpg?1483096805' } }, names: { alternatives: ['The Golden Fairy', 'Gray Wolf', 'Monstre Charmant'], canonical: 'Victorique de Blois', canonicalLocale: null, localized: { en: 'Victorique de Blois', ja_jp: ヴィクトリカ・ド・ブロワ } } } }
|
||||||
|
manga: { 636888: { __typename: Manga, id: '21733', slug: tonari-no-seki-kun, posterImage: { original: { url: 'https://media.kitsu.io/manga/poster_images/21733/original.jpg?1496845097', height: null, width: null }, views: [{ url: 'https://media.kitsu.io/manga/poster_images/21733/tiny.jpg?1496845097', height: null, width: null }, { url: 'https://media.kitsu.io/manga/poster_images/21733/small.jpg?1496845097', height: null, width: null }, { url: 'https://media.kitsu.io/manga/poster_images/21733/medium.jpg?1496845097', height: null, width: null }, { url: 'https://media.kitsu.io/manga/poster_images/21733/large.jpg?1496845097', height: null, width: null }] }, titles: { canonical: 'Tonari no Seki-kun', localized: { en: 'My Neighbour Seki', en_jp: 'Tonari no Seki-kun', en_us: 'My Neighbour Seki', ja_jp: となりの関くん } } } }
|
||||||
|
location: 'Michigan, USA'
|
||||||
|
name: timw4mail
|
||||||
|
slug: timw4mail
|
||||||
|
stats:
|
||||||
|
'Time spent watching anime:': '196 days, 5 hours, 25 minutes, and 17 seconds'
|
||||||
|
'Anime series watched:': '1,044'
|
||||||
|
'Anime episodes watched:': '14,943'
|
||||||
|
'Manga series read:': '49'
|
||||||
|
'Manga chapters read:': '2,678'
|
||||||
|
waifu:
|
||||||
|
label: Waifu
|
||||||
|
character: { id: '6553', slug: saber, image: { original: { name: original, url: 'https://media.kitsu.io/characters/images/6553/original.jpg?1483096805', width: null, height: null } }, names: { canonical: Saber, alternatives: ['King of Knights'], localized: { en: Saber, ja_jp: セイバー } } }
|
||||||
|
website: 'https://timshomepage.net'
|
@ -16,9 +16,11 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Tests;
|
namespace Aviat\AnimeClient\Tests;
|
||||||
|
|
||||||
use Amp\Http\Client\Response;
|
|
||||||
|
|
||||||
use function Aviat\AnimeClient\arrayToToml;
|
use function Aviat\AnimeClient\arrayToToml;
|
||||||
|
use function Aviat\AnimeClient\checkFolderPermissions;
|
||||||
|
use function Aviat\AnimeClient\clearCache;
|
||||||
|
use function Aviat\AnimeClient\colNotEmpty;
|
||||||
|
use function Aviat\AnimeClient\getLocalImg;
|
||||||
use function Aviat\AnimeClient\getResponse;
|
use function Aviat\AnimeClient\getResponse;
|
||||||
use function Aviat\AnimeClient\isSequentialArray;
|
use function Aviat\AnimeClient\isSequentialArray;
|
||||||
use function Aviat\AnimeClient\tomlToArray;
|
use function Aviat\AnimeClient\tomlToArray;
|
||||||
@ -89,4 +91,46 @@ class AnimeClientTest extends AnimeClientTestCase
|
|||||||
{
|
{
|
||||||
$this->assertNotEmpty(getResponse('https://example.com'));
|
$this->assertNotEmpty(getResponse('https://example.com'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCheckFolderPermissions(): void
|
||||||
|
{
|
||||||
|
$config = $this->container->get('config');
|
||||||
|
$actual = checkFolderPermissions($config);
|
||||||
|
$this->assertTrue(is_array($actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLocalImageEmptyUrl(): void
|
||||||
|
{
|
||||||
|
$actual = getLocalImg('');
|
||||||
|
$this->assertEquals('images/placeholder.webp', $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLocalImageBadUrl(): void
|
||||||
|
{
|
||||||
|
$actual = getLocalImg('//foo.bar');
|
||||||
|
$this->assertEquals('images/placeholder.webp', $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testColNotEmpty(): void
|
||||||
|
{
|
||||||
|
$hasEmptyCols = [[
|
||||||
|
'foo' => '',
|
||||||
|
], [
|
||||||
|
'foo' => '',
|
||||||
|
]];
|
||||||
|
|
||||||
|
$hasNonEmptyCols = [[
|
||||||
|
'foo' => 'bar',
|
||||||
|
], [
|
||||||
|
'foo' => 'baz',
|
||||||
|
]];
|
||||||
|
|
||||||
|
$this->assertEquals(false, colNotEmpty($hasEmptyCols, 'foo'));
|
||||||
|
$this->assertEquals(true, colNotEmpty($hasNonEmptyCols, 'foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearCache(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(clearCache($this->container->get('cache')));
|
||||||
|
}
|
||||||
}
|
}
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Tests;
|
namespace Aviat\AnimeClient\Tests;
|
||||||
|
|
||||||
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use function Aviat\Ion\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
@ -26,10 +28,16 @@ use Laminas\Diactoros\{
|
|||||||
ServerRequestFactory
|
ServerRequestFactory
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use const Aviat\AnimeClient\{
|
||||||
|
SLUG_PATTERN,
|
||||||
|
DEFAULT_CONTROLLER,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for TestCases
|
* Base class for TestCases
|
||||||
*/
|
*/
|
||||||
class AnimeClientTestCase extends TestCase {
|
class AnimeClientTestCase extends TestCase {
|
||||||
|
use ContainerAware;
|
||||||
use MatchesSnapshots;
|
use MatchesSnapshots;
|
||||||
|
|
||||||
// Test directory constants
|
// Test directory constants
|
||||||
@ -38,17 +46,10 @@ class AnimeClientTestCase extends TestCase {
|
|||||||
public const TEST_DATA_DIR = __DIR__ . '/test_data';
|
public const TEST_DATA_DIR = __DIR__ . '/test_data';
|
||||||
public const TEST_VIEW_DIR = __DIR__ . '/test_views';
|
public const TEST_VIEW_DIR = __DIR__ . '/test_views';
|
||||||
|
|
||||||
protected $container;
|
protected ContainerInterface $container;
|
||||||
protected static $staticContainer;
|
|
||||||
protected static $session_handler;
|
|
||||||
|
|
||||||
public static function setUpBeforeClass(): void
|
public static function setUpBeforeClass(): void
|
||||||
{
|
{
|
||||||
// Use mock session handler
|
|
||||||
//$session_handler = new TestSessionHandler();
|
|
||||||
//session_set_save_handler($session_handler, TRUE);
|
|
||||||
//self::$session_handler = $session_handler;
|
|
||||||
|
|
||||||
// Remove test cache files
|
// Remove test cache files
|
||||||
$files = glob(_dir(self::TEST_DATA_DIR, 'cache', '*.json'));
|
$files = glob(_dir(self::TEST_DATA_DIR, 'cache', '*.json'));
|
||||||
array_map('unlink', $files);
|
array_map('unlink', $files);
|
||||||
@ -59,6 +60,7 @@ class AnimeClientTestCase extends TestCase {
|
|||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$config_array = [
|
$config_array = [
|
||||||
|
'root' => self::ROOT_DIR,
|
||||||
'asset_path' => '/assets',
|
'asset_path' => '/assets',
|
||||||
'img_cache_path' => _dir(self::ROOT_DIR, 'public/images'),
|
'img_cache_path' => _dir(self::ROOT_DIR, 'public/images'),
|
||||||
'data_cache_path' => _dir(self::TEST_DATA_DIR, 'cache'),
|
'data_cache_path' => _dir(self::TEST_DATA_DIR, 'cache'),
|
||||||
@ -88,13 +90,11 @@ class AnimeClientTestCase extends TestCase {
|
|||||||
'file' => ':memory:',
|
'file' => ':memory:',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'routes' => [
|
'routes' => [ ],
|
||||||
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Set up DI container
|
// Set up DI container
|
||||||
$di = require _dir(self::ROOT_DIR, 'app', 'bootstrap.php');
|
$di = require self::ROOT_DIR . '/app/bootstrap.php';
|
||||||
$container = $di($config_array);
|
$container = $di($config_array);
|
||||||
|
|
||||||
// Use mock session handler
|
// Use mock session handler
|
||||||
@ -157,7 +157,7 @@ class AnimeClientTestCase extends TestCase {
|
|||||||
* @param array $args
|
* @param array $args
|
||||||
* @return mixed - the decoded data
|
* @return mixed - the decoded data
|
||||||
*/
|
*/
|
||||||
public function getMockFileData(...$args)
|
public function getMockFileData(mixed ...$args): mixed
|
||||||
{
|
{
|
||||||
$rawData = $this->getMockFile(...$args);
|
$rawData = $this->getMockFile(...$args);
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BaseCommandTest extends AnimeClientTestCase {
|
class BaseCommandTest extends AnimeClientTestCase {
|
||||||
protected $base;
|
protected Command $base;
|
||||||
protected $friend;
|
protected Friend $friend;
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
$this->base = new Command(new Console());
|
$this->base = new Command(new Console());
|
||||||
|
@ -21,13 +21,14 @@ use Aviat\AnimeClient\Controller;
|
|||||||
use Aviat\AnimeClient\Dispatcher;
|
use Aviat\AnimeClient\Dispatcher;
|
||||||
use Aviat\AnimeClient\UrlGenerator;
|
use Aviat\AnimeClient\UrlGenerator;
|
||||||
use Aviat\Ion\Config;
|
use Aviat\Ion\Config;
|
||||||
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Monolog\Handler\TestHandler;
|
use Monolog\Handler\TestHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
|
||||||
class DispatcherTest extends AnimeClientTestCase {
|
class DispatcherTest extends AnimeClientTestCase {
|
||||||
|
|
||||||
protected $container;
|
protected ContainerInterface $container;
|
||||||
protected $router;
|
protected $router;
|
||||||
protected $config;
|
protected $config;
|
||||||
protected $urlGenerator;
|
protected $urlGenerator;
|
||||||
|
37
tests/AnimeClient/Helper/FormHelperTest.php
Normal file
37
tests/AnimeClient/Helper/FormHelperTest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\Helper;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Helper\Form as FormHelper;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
|
||||||
|
class FormHelperTest extends AnimeClientTestCase {
|
||||||
|
public function testFormHelper(): void
|
||||||
|
{
|
||||||
|
$helper = new FormHelper();
|
||||||
|
$helper->setContainer($this->container);
|
||||||
|
|
||||||
|
$actual = $helper('input', [
|
||||||
|
'type' => 'text',
|
||||||
|
'value' => 'foo',
|
||||||
|
'placeholder' => 'field',
|
||||||
|
'name' => 'test'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
}
|
@ -22,53 +22,55 @@ use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
|||||||
class PictureHelperTest extends AnimeClientTestCase {
|
class PictureHelperTest extends AnimeClientTestCase {
|
||||||
/**
|
/**
|
||||||
* @dataProvider dataPictureCase
|
* @dataProvider dataPictureCase
|
||||||
|
* @param array $params
|
||||||
*/
|
*/
|
||||||
public function testPictureHelper($params, $expected = NULL)
|
public function testPictureHelper(array $params): void
|
||||||
{
|
{
|
||||||
$helper = new PictureHelper();
|
$helper = new PictureHelper();
|
||||||
$helper->setContainer($this->container);
|
$helper->setContainer($this->container);
|
||||||
|
|
||||||
$actual = $helper(...$params);
|
$actual = $helper(...$params);
|
||||||
|
|
||||||
if ($expected === NULL)
|
$this->assertMatchesSnapshot($actual);
|
||||||
{
|
|
||||||
$this->assertMatchesSnapshot($actual);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, $actual);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider dataSimpleImageCase
|
* @dataProvider dataSimpleImageCase
|
||||||
|
* @param string $ext
|
||||||
|
* @param bool $isSimple
|
||||||
|
* @param string $fallbackExt
|
||||||
*/
|
*/
|
||||||
public function testSimpleImage(string $ext, bool $isSimple)
|
public function testSimpleImage(string $ext, bool $isSimple, string $fallbackExt = 'jpg'): void
|
||||||
{
|
{
|
||||||
$helper = new PictureHelper();
|
$helper = new PictureHelper();
|
||||||
$helper->setContainer($this->container);
|
$helper->setContainer($this->container);
|
||||||
|
|
||||||
$url = "https://example.com/image.{$ext}";
|
$url = "https://example.com/image.{$ext}";
|
||||||
$actual = $helper($url);
|
$actual = $helper($url, $fallbackExt);
|
||||||
|
|
||||||
$actuallySimple = strpos($actual, '<picture') === FALSE;
|
$actuallySimple = ! str_contains($actual, '<picture');
|
||||||
|
|
||||||
$this->assertEquals($isSimple, $actuallySimple);
|
$this->assertEquals($isSimple, $actuallySimple);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSimpleImageByFallback()
|
public function testSimpleImageByFallback(): void
|
||||||
{
|
{
|
||||||
$helper = new PictureHelper();
|
$helper = new PictureHelper();
|
||||||
$helper->setContainer($this->container);
|
$helper->setContainer($this->container);
|
||||||
|
|
||||||
$actual = $helper("foo.svg", 'svg');
|
$actual = $helper("foo.svg", 'svg');
|
||||||
|
|
||||||
$this->assertTrue(strpos($actual, '<picture') === FALSE);
|
$this->assertTrue(! str_contains($actual, '<picture'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataPictureCase()
|
public function dataPictureCase(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'Full AVIF URL' => [
|
||||||
|
'params' => [
|
||||||
|
'https://www.example.com/image.avif',
|
||||||
|
],
|
||||||
|
],
|
||||||
'Full webp URL' => [
|
'Full webp URL' => [
|
||||||
'params' => [
|
'params' => [
|
||||||
'https://www.example.com/image.webp',
|
'https://www.example.com/image.webp',
|
||||||
@ -112,16 +114,21 @@ class PictureHelperTest extends AnimeClientTestCase {
|
|||||||
'params' => [
|
'params' => [
|
||||||
'images/foo.jpg',
|
'images/foo.jpg',
|
||||||
'jpg',
|
'jpg',
|
||||||
[ 'x' => 1, 'y' => 1 ],
|
[],
|
||||||
['width' => 200, 'height' => 200, 'alt' => 'should exist'],
|
['width' => 200, 'height' => 200, 'alt' => 'should exist'],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataSimpleImageCase()
|
public function dataSimpleImageCase(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'avif' => [
|
||||||
|
'ext' => 'avif',
|
||||||
|
'isSimple' => FALSE,
|
||||||
|
'fallback' => 'jpf'
|
||||||
|
],
|
||||||
'apng' => [
|
'apng' => [
|
||||||
'ext' => 'apng',
|
'ext' => 'apng',
|
||||||
'isSimple' => FALSE,
|
'isSimple' => FALSE,
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<input id="input" type="text" name="input" value="foo" />
|
@ -0,0 +1 @@
|
|||||||
|
<picture loading="lazy"><source srcset="https://www.example.com/image.avif" type="image/avif" /><source srcset="https://www.example.com/image.jpg" type="image/jpeg" /><img src="https://www.example.com/image.jpg" alt="" loading="lazy" /></picture>
|
@ -55,6 +55,17 @@ class KitsuTest extends TestCase {
|
|||||||
$this->assertEquals($expected, Kitsu::parseStreamingLinks($nodes));
|
$this->assertEquals($expected, Kitsu::parseStreamingLinks($nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testParseStreamingLinksNoHost(): void
|
||||||
|
{
|
||||||
|
$nodes = [[
|
||||||
|
'url' => '/link-fragment',
|
||||||
|
'dubs' => [],
|
||||||
|
'subs' => [],
|
||||||
|
]];
|
||||||
|
|
||||||
|
$this->assertEquals([], Kitsu::parseStreamingLinks($nodes));
|
||||||
|
}
|
||||||
|
|
||||||
public function testGetAiringStatusEmptyArguments(): void
|
public function testGetAiringStatusEmptyArguments(): void
|
||||||
{
|
{
|
||||||
$this->assertEquals(AnimeAiringStatus::NOT_YET_AIRED, Kitsu::getAiringStatus());
|
$this->assertEquals(AnimeAiringStatus::NOT_YET_AIRED, Kitsu::getAiringStatus());
|
||||||
@ -123,7 +134,7 @@ class KitsuTest extends TestCase {
|
|||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFilterLocalizedTitles()
|
public function testFilterLocalizedTitles(): void
|
||||||
{
|
{
|
||||||
$input = [
|
$input = [
|
||||||
'canonical' => 'foo',
|
'canonical' => 'foo',
|
||||||
@ -140,7 +151,7 @@ class KitsuTest extends TestCase {
|
|||||||
$this->assertEquals(['Foo the Movie'], $actual);
|
$this->assertEquals(['Foo the Movie'], $actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFilteredTitles()
|
public function testGetFilteredTitles(): void
|
||||||
{
|
{
|
||||||
$input = [
|
$input = [
|
||||||
'canonical' => 'foo',
|
'canonical' => 'foo',
|
||||||
|
@ -22,7 +22,7 @@ class RequirementsTest extends AnimeClientTestCase {
|
|||||||
|
|
||||||
public function testPHPVersion(): void
|
public function testPHPVersion(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(version_compare(PHP_VERSION, "7.4", "ge"));
|
$this->assertTrue(version_compare(PHP_VERSION, "8", "ge"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHasPDO(): void
|
public function testHasPDO(): void
|
||||||
|
52
tests/AnimeClient/Types/ConfigTest.php
Normal file
52
tests/AnimeClient/Types/ConfigTest.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\Types;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Types\Config;
|
||||||
|
|
||||||
|
class ConfigTest extends ConfigTestCase {
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->testClass = Config::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetMethods(): void
|
||||||
|
{
|
||||||
|
$type = $this->testClass::from([
|
||||||
|
'anilist' => [],
|
||||||
|
'cache' => [],
|
||||||
|
'database' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(3, $type->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOffsetUnset(): void
|
||||||
|
{
|
||||||
|
$type = $this->testClass::from([
|
||||||
|
'anilist' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertTrue($type->offsetExists('anilist'));
|
||||||
|
|
||||||
|
$type->offsetUnset('anilist');
|
||||||
|
|
||||||
|
$this->assertNotTrue($type->offsetExists('anilist'));
|
||||||
|
}
|
||||||
|
}
|
72
tests/AnimeClient/Types/ConfigTestCase.php
Normal file
72
tests/AnimeClient/Types/ConfigTestCase.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 8
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2021 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.2
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\Types;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\AnimeClient\Types\UndefinedPropertyException;
|
||||||
|
|
||||||
|
abstract class ConfigTestCase extends AnimeClientTestCase {
|
||||||
|
public string $testClass;
|
||||||
|
|
||||||
|
public function testCheck(): void
|
||||||
|
{
|
||||||
|
$result = $this->testClass::check([]);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetUndefinedProperty(): void
|
||||||
|
{
|
||||||
|
$this->expectException(UndefinedPropertyException::class);
|
||||||
|
$this->testClass::from([
|
||||||
|
'foobar' => 'baz',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToString(): void
|
||||||
|
{
|
||||||
|
$actual = $this->testClass::from([])->__toString();
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOffsetExists(): void
|
||||||
|
{
|
||||||
|
$actual = $this->testClass::from([
|
||||||
|
'anilist' => [],
|
||||||
|
])->offsetExists('anilist');
|
||||||
|
$this->assertTrue($actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetState(): void
|
||||||
|
{
|
||||||
|
$normal = $this->testClass::from([]);
|
||||||
|
$setState = $this->testClass::__set_state([]);
|
||||||
|
|
||||||
|
$this->assertEquals($normal, $setState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsEmpty(): void
|
||||||
|
{
|
||||||
|
$type = $this->testClass::from([]);
|
||||||
|
$this->assertTrue($type->isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCount(): void
|
||||||
|
{
|
||||||
|
$type = $this->testClass::from([]);
|
||||||
|
$this->assertEquals(0, $type->count());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
Aviat\AnimeClient\Types\Config Object
|
||||||
|
(
|
||||||
|
)
|
@ -17,7 +17,7 @@ use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class MockErrorHandler {
|
class MockErrorHandler {
|
||||||
public function addDataTable($name, array $values=[]) {}
|
public function addDataTable(string $name, array $values=[]): void {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -128,12 +128,12 @@ class TestJsonView extends JsonView {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
trait MockInjectionTrait {
|
trait MockInjectionTrait {
|
||||||
public function __get($key)
|
public function __get(string $key): mixed
|
||||||
{
|
{
|
||||||
return $this->$key;
|
return $this->$key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __set($key, $value)
|
public function __set(string $key, mixed $value)
|
||||||
{
|
{
|
||||||
$this->$key = $value;
|
$this->$key = $value;
|
||||||
return $this;
|
return $this;
|
||||||
|
3178
tests/AnimeClient/test_data/Kitsu/characterBeforeTransform.json
Normal file
3178
tests/AnimeClient/test_data/Kitsu/characterBeforeTransform.json
Normal file
File diff suppressed because one or more lines are too long
5846
tests/AnimeClient/test_data/Kitsu/historyBeforeTransform.json
Normal file
5846
tests/AnimeClient/test_data/Kitsu/historyBeforeTransform.json
Normal file
File diff suppressed because it is too large
Load Diff
7833
tests/AnimeClient/test_data/Kitsu/personBeforeTransform.json
Normal file
7833
tests/AnimeClient/test_data/Kitsu/personBeforeTransform.json
Normal file
File diff suppressed because it is too large
Load Diff
536
tests/AnimeClient/test_data/Kitsu/userBeforeTransform.json
Normal file
536
tests/AnimeClient/test_data/Kitsu/userBeforeTransform.json
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"findProfileBySlug": {
|
||||||
|
"about": "Web Developer, Anime Fan, Reader of VNs, and web comics.",
|
||||||
|
"avatarImage": {
|
||||||
|
"original": {
|
||||||
|
"name": "original",
|
||||||
|
"url": "https://media.kitsu.io/users/avatars/2644/original.gif?1491510751",
|
||||||
|
"width": null,
|
||||||
|
"height": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bannerImage": {
|
||||||
|
"original": {
|
||||||
|
"name": "original",
|
||||||
|
"url": "https://media.kitsu.io/users/cover_images/2644/original.jpeg?1487201681",
|
||||||
|
"width": null,
|
||||||
|
"height": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"birthday": "1990-03-09",
|
||||||
|
"id": "2644",
|
||||||
|
"location": "Michigan, USA",
|
||||||
|
"name": "timw4mail",
|
||||||
|
"proMessage": null,
|
||||||
|
"proTier": null,
|
||||||
|
"slug": "timw4mail",
|
||||||
|
"siteLinks": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "5804",
|
||||||
|
"url": "https://timshomepage.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4149",
|
||||||
|
"url": "https://github.com/timw4mail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4151",
|
||||||
|
"url": "https://twitter.com/timw4mail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4150",
|
||||||
|
"url": "timw4mail#9933"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4152",
|
||||||
|
"url": "http://steamcommunity.com/id/timw4mail"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"favorites": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "933073",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Anime",
|
||||||
|
"id": "14212",
|
||||||
|
"slug": "hataraku-saibou-tv",
|
||||||
|
"posterImage": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/14212/original.jpg?1597697195",
|
||||||
|
"height": 1050,
|
||||||
|
"width": 750
|
||||||
|
},
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/14212/tiny.jpg?1597697195",
|
||||||
|
"height": 156,
|
||||||
|
"width": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/14212/small.jpg?1597697195",
|
||||||
|
"height": 402,
|
||||||
|
"width": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/14212/medium.jpg?1597697195",
|
||||||
|
"height": 554,
|
||||||
|
"width": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/14212/large.jpg?1597697195",
|
||||||
|
"height": 780,
|
||||||
|
"width": 550
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"canonical": "Hataraku Saibou",
|
||||||
|
"localized": {
|
||||||
|
"en": "Cells at Work!",
|
||||||
|
"en_jp": "Hataraku Saibou",
|
||||||
|
"ja_jp": "はたらく細胞"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "586217",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Anime",
|
||||||
|
"id": "323",
|
||||||
|
"slug": "fate-stay-night",
|
||||||
|
"posterImage": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/323/original.jpg?1597698066",
|
||||||
|
"height": 1074,
|
||||||
|
"width": 760
|
||||||
|
},
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/323/tiny.jpg?1597698066",
|
||||||
|
"height": 156,
|
||||||
|
"width": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/323/small.jpg?1597698066",
|
||||||
|
"height": 402,
|
||||||
|
"width": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/323/medium.jpg?1597698066",
|
||||||
|
"height": 554,
|
||||||
|
"width": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/323/large.jpg?1597698066",
|
||||||
|
"height": 780,
|
||||||
|
"width": 550
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"canonical": "Fate/stay night",
|
||||||
|
"localized": {
|
||||||
|
"en": "Fate/stay night",
|
||||||
|
"en_jp": "Fate/stay night",
|
||||||
|
"en_us": "Fate/stay night",
|
||||||
|
"ja_jp": "Fate/stay night"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "586219",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "6553",
|
||||||
|
"slug": "saber",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/6553/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [
|
||||||
|
"King of Knights"
|
||||||
|
],
|
||||||
|
"canonical": "Saber",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Saber",
|
||||||
|
"ja_jp": "セイバー"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "586218",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "6556",
|
||||||
|
"slug": "rin-tohsaka",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/6556/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [],
|
||||||
|
"canonical": "Rin Toosaka",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Rin Toosaka",
|
||||||
|
"ja_jp": "遠坂 凛"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "611365",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "32035",
|
||||||
|
"slug": "nano-shinonome",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/32035/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [],
|
||||||
|
"canonical": "Nano Shinonome",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Nano Shinonome",
|
||||||
|
"ja_jp": "東雲 なの"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "611364",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "32034",
|
||||||
|
"slug": "mio-naganohara",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/32034/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [],
|
||||||
|
"canonical": "Mio Naganohara",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Mio Naganohara",
|
||||||
|
"ja_jp": "長野原みお"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "607473",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Anime",
|
||||||
|
"id": "310",
|
||||||
|
"slug": "tsukuyomi-moon-phase",
|
||||||
|
"posterImage": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/310/original.jpg?1597690591",
|
||||||
|
"height": 320,
|
||||||
|
"width": 225
|
||||||
|
},
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/310/tiny.jpg?1597690591",
|
||||||
|
"height": 156,
|
||||||
|
"width": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/310/small.jpg?1597690591",
|
||||||
|
"height": 402,
|
||||||
|
"width": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/310/medium.jpg?1597690591",
|
||||||
|
"height": 554,
|
||||||
|
"width": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/310/large.jpg?1597690591",
|
||||||
|
"height": 780,
|
||||||
|
"width": 550
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"canonical": "Tsukuyomi: Moon Phase",
|
||||||
|
"localized": {
|
||||||
|
"en": "Tsukuyomi: Moon Phase",
|
||||||
|
"en_jp": "Tsukuyomi: Moon Phase",
|
||||||
|
"en_us": "Tsukuyomi: Moon Phase",
|
||||||
|
"ja_jp": "月詠 −MOON PHASE−"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "607472",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Anime",
|
||||||
|
"id": "5992",
|
||||||
|
"slug": "carnival-phantasm",
|
||||||
|
"posterImage": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/5992/original.jpg?1597697878",
|
||||||
|
"height": 693,
|
||||||
|
"width": 533
|
||||||
|
},
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/5992/tiny.jpg?1597697878",
|
||||||
|
"height": 156,
|
||||||
|
"width": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/5992/small.jpg?1597697878",
|
||||||
|
"height": 402,
|
||||||
|
"width": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/5992/medium.jpg?1597697878",
|
||||||
|
"height": 554,
|
||||||
|
"width": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/5992/large.jpg?1597697878",
|
||||||
|
"height": 780,
|
||||||
|
"width": 550
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"canonical": "Carnival Phantasm",
|
||||||
|
"localized": {
|
||||||
|
"en_jp": "Carnival Phantasm",
|
||||||
|
"ja_jp": "カーニバル・ファンタズム"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "636590",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "31851",
|
||||||
|
"slug": "aria-holmes-kanzaki",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/31851/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [
|
||||||
|
"Quadra Aria"
|
||||||
|
],
|
||||||
|
"canonical": "Aria Holmes Kanzaki",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Aria Holmes Kanzaki",
|
||||||
|
"ja_jp": "神崎・H・アリア"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "636591",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "25930",
|
||||||
|
"slug": "taiga-aisaka",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/25930/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [
|
||||||
|
"Palmtop Tiger"
|
||||||
|
],
|
||||||
|
"canonical": "Taiga Aisaka",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Taiga Aisaka",
|
||||||
|
"ja_jp": "逢坂 大河"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "636593",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Character",
|
||||||
|
"id": "31625",
|
||||||
|
"slug": "victorique-de-blois",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/characters/images/31625/original.jpg?1483096805"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"alternatives": [
|
||||||
|
"The Golden Fairy",
|
||||||
|
"Gray Wolf",
|
||||||
|
"Monstre Charmant"
|
||||||
|
],
|
||||||
|
"canonical": "Victorique de Blois",
|
||||||
|
"canonicalLocale": null,
|
||||||
|
"localized": {
|
||||||
|
"en": "Victorique de Blois",
|
||||||
|
"ja_jp": "ヴィクトリカ・ド・ブロワ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "636888",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Manga",
|
||||||
|
"id": "21733",
|
||||||
|
"slug": "tonari-no-seki-kun",
|
||||||
|
"posterImage": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/manga/poster_images/21733/original.jpg?1496845097",
|
||||||
|
"height": null,
|
||||||
|
"width": null
|
||||||
|
},
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/manga/poster_images/21733/tiny.jpg?1496845097",
|
||||||
|
"height": null,
|
||||||
|
"width": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/manga/poster_images/21733/small.jpg?1496845097",
|
||||||
|
"height": null,
|
||||||
|
"width": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/manga/poster_images/21733/medium.jpg?1496845097",
|
||||||
|
"height": null,
|
||||||
|
"width": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/manga/poster_images/21733/large.jpg?1496845097",
|
||||||
|
"height": null,
|
||||||
|
"width": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"canonical": "Tonari no Seki-kun",
|
||||||
|
"localized": {
|
||||||
|
"en": "My Neighbour Seki",
|
||||||
|
"en_jp": "Tonari no Seki-kun",
|
||||||
|
"en_us": "My Neighbour Seki",
|
||||||
|
"ja_jp": "となりの関くん"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "636892",
|
||||||
|
"item": {
|
||||||
|
"__typename": "Anime",
|
||||||
|
"id": "6062",
|
||||||
|
"slug": "nichijou",
|
||||||
|
"posterImage": {
|
||||||
|
"original": {
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/6062/original.jpg?1597696783",
|
||||||
|
"height": 2292,
|
||||||
|
"width": 1610
|
||||||
|
},
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/6062/tiny.jpg?1597696783",
|
||||||
|
"height": 156,
|
||||||
|
"width": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/6062/small.jpg?1597696783",
|
||||||
|
"height": 402,
|
||||||
|
"width": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/6062/medium.jpg?1597696783",
|
||||||
|
"height": 554,
|
||||||
|
"width": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://media.kitsu.io/anime/poster_images/6062/large.jpg?1597696783",
|
||||||
|
"height": 780,
|
||||||
|
"width": 550
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"canonical": "Nichijou",
|
||||||
|
"localized": {
|
||||||
|
"en": "Nichijou - My Ordinary Life",
|
||||||
|
"en_jp": "Nichijou",
|
||||||
|
"en_us": "Nichijou - My Ordinary Life",
|
||||||
|
"ja_jp": "日常"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"animeAmountConsumed": {
|
||||||
|
"completed": 893,
|
||||||
|
"id": "2161520",
|
||||||
|
"media": 1044,
|
||||||
|
"recalculatedAt": "2018-12-25",
|
||||||
|
"time": 16953917,
|
||||||
|
"units": 14943
|
||||||
|
},
|
||||||
|
"mangaAmountConsumed": {
|
||||||
|
"completed": 26,
|
||||||
|
"id": "841057",
|
||||||
|
"media": 49,
|
||||||
|
"recalculatedAt": "2018-12-20",
|
||||||
|
"units": 2678
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "https://kitsu/users/timw4mail",
|
||||||
|
"waifu": {
|
||||||
|
"id": "6553",
|
||||||
|
"slug": "saber",
|
||||||
|
"image": {
|
||||||
|
"original": {
|
||||||
|
"name": "original",
|
||||||
|
"url": "https://media.kitsu.io/characters/images/6553/original.jpg?1483096805",
|
||||||
|
"width": null,
|
||||||
|
"height": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"canonical": "Saber",
|
||||||
|
"alternatives": [
|
||||||
|
"King of Knights"
|
||||||
|
],
|
||||||
|
"localized": {
|
||||||
|
"en": "Saber",
|
||||||
|
"ja_jp": "セイバー"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"waifuOrHusbando": "Waifu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,8 @@ use Monolog\Logger;
|
|||||||
use Monolog\Handler\{TestHandler, NullHandler};
|
use Monolog\Handler\{TestHandler, NullHandler};
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\NotFoundException;
|
use Aviat\Ion\Di\Exception\NotFoundException;
|
||||||
|
use Throwable;
|
||||||
|
use TypeError;
|
||||||
|
|
||||||
class FooTest {
|
class FooTest {
|
||||||
|
|
||||||
@ -49,13 +51,11 @@ class ContainerTest extends IonTestCase {
|
|||||||
return [
|
return [
|
||||||
'Bad index type: number' => [
|
'Bad index type: number' => [
|
||||||
'id' => 42,
|
'id' => 42,
|
||||||
'exception' => ContainerException::class,
|
'exception' => TypeError::class,
|
||||||
'message' => 'Id must be a string'
|
|
||||||
],
|
],
|
||||||
'Bad index type: array' => [
|
'Bad index type: array' => [
|
||||||
'id' => [],
|
'id' => [],
|
||||||
'exception' => ContainerException::class,
|
'exception' => TypeError::class,
|
||||||
'message' => 'Id must be a string'
|
|
||||||
],
|
],
|
||||||
'Non-existent id' => [
|
'Non-existent id' => [
|
||||||
'id' => 'foo',
|
'id' => 'foo',
|
||||||
@ -68,7 +68,7 @@ class ContainerTest extends IonTestCase {
|
|||||||
/**
|
/**
|
||||||
* @dataProvider dataGetWithException
|
* @dataProvider dataGetWithException
|
||||||
*/
|
*/
|
||||||
public function testGetWithException($id, $exception, $message): void
|
public function testGetWithException(mixed $id, $exception, ?string $message = NULL): void
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -79,15 +79,23 @@ class ContainerTest extends IonTestCase {
|
|||||||
$this->assertInstanceOf($exception, $e);
|
$this->assertInstanceOf($exception, $e);
|
||||||
$this->assertEquals($message, $e->getMessage());
|
$this->assertEquals($message, $e->getMessage());
|
||||||
}
|
}
|
||||||
|
catch(Throwable $e)
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf($exception, $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider dataGetWithException
|
* @dataProvider dataGetWithException
|
||||||
*/
|
*/
|
||||||
public function testGetNewWithException($id, $exception, $message): void
|
public function testGetNewWithException(mixed $id, $exception, ?string $message = NULL): void
|
||||||
{
|
{
|
||||||
$this->expectException($exception);
|
$this->expectException($exception);
|
||||||
$this->expectExceptionMessage($message);
|
if ($message !== NULL)
|
||||||
|
{
|
||||||
|
$this->expectExceptionMessage($message);
|
||||||
|
}
|
||||||
|
|
||||||
$this->container->getNew($id);
|
$this->container->getNew($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Work around the silly timezone error
|
// Work around the silly timezone error
|
||||||
$timezone = ini_get('date.timezone');
|
date_default_timezone_set('UTC');
|
||||||
if ($timezone === '' || $timezone === FALSE)
|
|
||||||
{
|
|
||||||
ini_set('date.timezone', 'GMT');
|
|
||||||
}
|
|
||||||
|
|
||||||
define('AC_TEST_ROOT_DIR', dirname(__DIR__) . '/');
|
define('AC_TEST_ROOT_DIR', dirname(__DIR__) . '/');
|
||||||
define('SRC_DIR', AC_TEST_ROOT_DIR . 'src/');
|
define('SRC_DIR', AC_TEST_ROOT_DIR . 'src/');
|
||||||
|
Loading…
Reference in New Issue
Block a user