Merge remote-tracking branch 'origin/develop'

This commit is contained in:
Timothy Warren 2021-04-23 19:01:21 -04:00
commit 60c4e05a66
96 changed files with 20464 additions and 3602 deletions

8
Jenkinsfile vendored
View File

@ -18,7 +18,8 @@ pipeline {
}
}
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'
}
}
@ -30,14 +31,15 @@ pipeline {
}
}
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'
}
}
stage('Code Cleanliness') {
agent any
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(
failOnError: false,
tools: [phpStan(reportEncoding: 'UTF-8', pattern: 'build/logs/phpstan.log')]

View File

@ -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();
}
}

View File

@ -2,16 +2,16 @@
/**
* 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
* @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
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
* @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
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
// ----------------------------------------------------------------------------
$APP_DIR = realpath(__DIR__ . '/../');
$ROOT_DIR = realpath("{$APP_DIR}/../");
$APP_DIR = dirname(__DIR__);
$ROOT_DIR = dirname($APP_DIR);
$tomlConfig = loadConfig(__DIR__);
return array_merge($tomlConfig, [
'root' => $ROOT_DIR,
'asset_dir' => "{$ROOT_DIR}/public",
'base_config_dir' => __DIR__,
'config_dir' => "{$APP_DIR}/config",

View File

@ -2,15 +2,15 @@
/**
* 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
* @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
* @version 4.0
* @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/

View File

@ -34,11 +34,11 @@ use Psr\SimpleCache\CacheInterface;
use function Aviat\Ion\_dir;
if ( ! defined('APP_DIR'))
if ( ! defined('HB_APP_DIR'))
{
define('APP_DIR', __DIR__);
define('ROOT_DIR', dirname(APP_DIR));
define('TEMPLATE_DIR', _dir(APP_DIR, 'templates'));
define('HB_APP_DIR', __DIR__);
define('ROOT_DIR', dirname(HB_APP_DIR));
define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates'));
}
// -----------------------------------------------------------------------------
@ -50,7 +50,7 @@ return static function (array $configArray = []): Container {
// -------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------
$LOG_DIR = _dir(APP_DIR, 'logs');
$LOG_DIR = _dir(HB_APP_DIR, 'logs');
$appLogger = new Logger('animeclient');
$appLogger->pushHandler(new RotatingFileHandler(_dir($LOG_DIR, 'app.log'), 2, Logger::WARNING));

View File

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

View File

@ -5,13 +5,13 @@ namespace Aviat\AnimeClient;
$whose = $config->get('whose_list') . "'s ";
$lastSegment = $urlGenerator->lastSegment();
$extraSegment = $lastSegment === 'list' ? '/list' : '';
$hasAnime = stripos($GLOBALS['_SERVER']['REQUEST_URI'], 'anime') !== FALSE;
$hasManga = stripos($GLOBALS['_SERVER']['REQUEST_URI'], 'manga') !== FALSE;
$hasAnime = str_contains($GLOBALS['_SERVER']['REQUEST_URI'], 'anime');
$hasManga = str_contains($GLOBALS['_SERVER']['REQUEST_URI'], 'manga');
?>
<div id="main-nav" class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1">
<?php if(strpos($route_path, 'collection') === FALSE): ?>
<?php if( ! str_contains($route_path, 'collection')): ?>
<?= $helper->a(
$urlGenerator->defaultUrl($url_type),
$whose . ucfirst($url_type) . ' List',

View File

@ -2,6 +2,7 @@
declare(strict_types=1);
$file_patterns = [
'app/appConf/*.php',
'app/bootstrap.php',
'migrations/*.php',
'src/**/*.php',
@ -16,7 +17,7 @@ if ( ! function_exists('glob_recursive'))
{
// 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);
@ -57,17 +58,21 @@ function get_text_to_replace(array $tokens): string
return $output;
}
function get_tokens($source): array
function get_tokens(string $source): array
{
return token_get_all($source);
}
function replace_files(array $files, $template)
function replace_files(array $files, string $template): void
{
print_r($files);
foreach ($files as $file)
{
$source = file_get_contents($file);
if ($source === FALSE)
{
continue;
}
if (stripos($source, 'namespace') === FALSE)
{

View File

@ -43,26 +43,25 @@
"aviat/query": "^3.0.0",
"danielstjules/stringy": "^3.1.0",
"ext-dom": "*",
"ext-iconv": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-gd": "*",
"ext-pdo": "*",
"filp/whoops": "^2.1",
"laminas/laminas-diactoros": "^2.5.0",
"laminas/laminas-httphandlerrunner": "^1.1.0",
"maximebf/consolekit": "^1.0.3",
"monolog/monolog": "^2.0.2",
"php": "^8.0.0",
"php": ">= 8.0.0",
"psr/container": "^1.0.0",
"psr/http-message": "^1.0.1",
"psr/log": "^1.1.3",
"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"
},
"require-dev": {
"consolidation/robo": "^2.0.0",
"pdepend/pdepend": "^2.",
"phploc/phploc": "^7.0.0",
"phpmd/phpmd": "^2.8.2",

View File

@ -26,7 +26,7 @@ try
'sync:lists' => Command\SyncLists::class
]))->run();
}
catch (\Exception $e)
catch (\Throwable)
{
}

View File

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

View File

@ -9,7 +9,7 @@ const matches = (elm, selector) => {
return i > -1;
}
export const AnimeClient = {
const AnimeClient = {
/**
* Placeholder function
*/
@ -18,8 +18,8 @@ export const AnimeClient = {
* DOM selector
*
* @param {string} selector - The dom selector string
* @param {object} [context]
* @return {[HTMLElement]} - array of dom elements
* @param {Element} [context]
* @return array of dom elements
*/
$(selector, context = null) {
if (typeof selector !== 'string') {
@ -60,7 +60,7 @@ export const AnimeClient = {
/**
* 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}
*/
hide (sel) {
@ -77,7 +77,7 @@ export const AnimeClient = {
/**
* 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}
*/
show (sel) {
@ -116,9 +116,9 @@ export const AnimeClient = {
/**
* 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
* @return {HTMLElement|null} - the parent element
* @return {Element|null} - the parent element
*/
closestParent (current, parentSelector) {
if (Element.prototype.closest !== undefined) {
@ -204,9 +204,9 @@ function delegateEvent(sel, target, 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|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
* @return {void}
*/

View File

@ -71,7 +71,7 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
success: (res) => {
const resData = JSON.parse(res);
if (resData.errors) {
if (resData.error) {
_.hide('#loading-shadow');
_.showMessage('error', `Failed to update ${title}. `);
_.scrollToTop();

View File

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

View File

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

View File

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

View File

@ -72,14 +72,22 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
dataType: 'json',
type: 'POST',
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') {
_.hide(parentSel);
}
_.hide('#loading-shadow');
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = completed;
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
_.showMessage('success', `Successfully updated ${mangaName}`);
_.scrollToTop();
},

View File

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

View File

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

View File

@ -8,12 +8,10 @@ _.on('main', 'change', '.big-check', (e) => {
});
export function renderAnimeSearchResults (data) {
const results = [];
data.forEach(item => {
return data.map(item => {
const titles = item.titles.join('<br />');
results.push(`
return `
<article class="media search">
<div class="name">
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
@ -38,19 +36,14 @@ export function renderAnimeSearchResults (data) {
</div>
</div>
</article>
`);
});
return results.join('');
`;
}).join('');
}
export function renderMangaSearchResults (data) {
const results = [];
data.forEach(item => {
return data.map(item => {
const titles = item.titles.join('<br />');
results.push(`
return `
<article class="media search">
<div class="name">
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" />
@ -75,8 +68,6 @@ export function renderMangaSearchResults (data) {
</div>
</div>
</article>
`);
});
return results.join('');
`;
}).join('');
}

View File

@ -3,19 +3,19 @@
"scripts": {
"build": "npm run build:css && npm run build: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:js": "watch 'npm run build:js' ./js",
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
},
"devDependencies": {
"@ampproject/rollup-plugin-closure-compiler": "^0.25.2",
"concurrently": "^5.1.0",
"cssnano": "^4.1.10",
"postcss": "^7.0.27",
"postcss-import": "^12.0.1",
"@swc/cli": "^0.1.39",
"@swc/core": "^1.2.54",
"concurrently": "^6.0.2",
"cssnano": "^5.0.1",
"postcss": "^8.2.6",
"postcss-import": "^14.0.0",
"postcss-preset-env": "^6.7.0",
"rollup": "^2.4.0",
"watch": "^1.0.2"
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -17,9 +17,7 @@
namespace Aviat\AnimeClient;
use Aviat\AnimeClient\Types\Config as ConfigType;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;
use Tracy\Debugger;
use function Aviat\Ion\_dir;
setlocale(LC_CTYPE, 'en_US');
@ -27,12 +25,9 @@ setlocale(LC_CTYPE, 'en_US');
// Load composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
if (file_exists('.is-dev'))
{
$whoops = new Run;
$whoops->pushHandler(new PrettyPageHandler);
$whoops->register();
}
Debugger::$strictMode = true;
Debugger::$showBar = false;
Debugger::enable(Debugger::DEVELOPMENT, __DIR__ . '/app/logs');
// Define base directories
$APP_DIR = _dir(__DIR__, 'app');

View File

@ -4,6 +4,7 @@ parameters:
inferPrivatePropertyTypeFromConstructor: true
level: 8
paths:
- app/appConf
- src
- ./console
- index.php

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,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);
}
};
},