commit
60c4e05a66
JenkinsfileRoboFile.php
app
build
composer.jsonconsolefrontEndSrc
build-js.js
index.phpphpstan.neonjs
package.jsonspack.config.jsyarn.lockpublic
css
es
js
src
AnimeClient
API
AnimeClient.phpCommand
Controller.phpController
Dispatcher.phpHelper
Types
Ion/Di
tests
AnimeClient
API/Kitsu/Transformer
AnimeListTransformerTest.phpCharacterTransformerTest.phpHistoryTransformerTest.phpPersonTransformerTest.phpUserTransformerTest.php
AnimeClientTest.phpAnimeClientTestCase.php__snapshots__
Command
DispatcherTest.phpHelper
KitsuTest.phpRequirementsTest.phpTypes
mocks.phptest_data/Kitsu
Ion/Di
bootstrap.php
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);
|
||||||
|
}
|
||||||
|
}
|
630
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/AnimeHistoryTransformerTest__testTransform__1.yml
Normal file
630
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/AnimeHistoryTransformerTest__testTransform__1.yml
Normal file
File diff suppressed because one or more lines are too long
14
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/CharacterTransformerTest__testTransform__1.yml
Normal file
14
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/CharacterTransformerTest__testTransform__1.yml
Normal file
File diff suppressed because one or more lines are too long
630
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/HistoryTransformerTest__testAnimeTransform__1.yml
Normal file
630
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/HistoryTransformerTest__testAnimeTransform__1.yml
Normal file
File diff suppressed because one or more lines are too long
1
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/HistoryTransformerTest__testMangaTransform__1.yml
Normal file
1
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/HistoryTransformerTest__testMangaTransform__1.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
{ }
|
12
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/PersonTransformerTest__testTransform__1.yml
Normal file
12
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/PersonTransformerTest__testTransform__1.yml
Normal file
File diff suppressed because one or more lines are too long
20
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/UserTransformerTest__testTransform__1.yml
Normal file
20
tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/UserTransformerTest__testTransform__1.yml
Normal file
@ -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" />
|
1
tests/AnimeClient/Helper/__snapshots__/PictureHelperTest__testPictureHelper with data set Full AVIF URL__1.txt
Normal file
1
tests/AnimeClient/Helper/__snapshots__/PictureHelperTest__testPictureHelper with data set Full AVIF URL__1.txt
Normal file
@ -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…
x
Reference in New Issue
Block a user