Compare commits

...

1178 Commits
v2 ... master

Author SHA1 Message Date
Timothy Warren 7afdab80ac Update frontend minification config and dependencies
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good Details
2024-02-15 15:21:26 -05:00
Timothy Warren 144718866d Merge branch 'develop'
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good Details
2024-01-23 13:35:40 -05:00
Timothy Warren 1dd9fdd0c2 Attmept to fix issue with Stringy 2024-01-17 08:32:48 -05:00
Timothy Warren 3794ed20a8 Update CI to use several PHP versions 2023-12-21 13:49:54 -05:00
Timothy Warren ff8c837fd9 Update views and templates to use render helper object 2023-12-21 13:46:15 -05:00
Timothy Warren fe1caffc0f Simplify setup of rendering methods by putting them in a wrapper class 2023-12-21 13:19:59 -05:00
Timothy Warren 8e7b2a04fd Fix broken test 2023-10-27 09:28:06 -04:00
Timothy Warren 0e684736bd Vastly simplify logic for getting a user's anime library. Most basic API functionality seems to be working 2023-10-26 16:00:13 -04:00
Timothy Warren 1ad4427584 Start of migration to amphp/http-client 5.0 2023-10-26 11:10:21 -04:00
Timothy Warren b0a16e7730 Pin latest working version of Aura Router 2023-10-25 07:42:13 -04:00
Timothy Warren 7559f79ef6 Various tweaks 2023-10-02 11:48:34 -04:00
Timothy Warren c8b642be1c Add anilist example config file, and make the interface a bit more helpful 2023-07-13 14:17:51 -04:00
Timothy Warren 91c435cdac Update header comments 2023-07-13 11:08:05 -04:00
Timothy Warren 57249882ab Some minor code style fixes 2023-07-13 11:06:52 -04:00
Timothy Warren c28dfc09b3 Update js/css dependencies 2023-06-28 15:48:33 -04:00
Timothy Warren cd5841c4c8 Merge pull request 'default config and documentation update' (#43) from colwellkr/HummingBirdAnimeClient:develop into develop
Reviewed-on: #43
2023-06-28 15:24:25 -04:00
Timothy Warren c83b30f43a Merge branch 'develop' into develop 2023-06-28 15:23:56 -04:00
Kevin Colwell 011160a3c2 add php extensions required to README.md 2023-06-28 18:37:44 +00:00
Timothy Warren 067d888f4c Fix route issue for manga pages 2023-06-28 14:30:30 -04:00
Kevin Colwell 1b5a5080f2 add default theme to config.toml.example 2023-06-28 18:22:00 +00:00
Timothy Warren 1a97277ec4 Tweak a Stringy method to have a more sensible return type 2023-06-28 14:03:15 -04:00
Timothy Warren e426fdd4a3 Fix a load of code cleanliness and style issues 2023-05-19 16:34:38 -04:00
Timothy Warren 5758de7667 Update tooling and config 2023-05-19 16:34:07 -04:00
Timothy Warren d21d9a86d4 Update frontend dependencies, remove period on update banner 2023-05-19 16:33:14 -04:00
Timothy Warren a435162a2b Update rector config 2023-05-19 11:00:12 -04:00
Timothy Warren 4fef2b2942 Fix some remaining formatting issues 2023-05-19 10:57:59 -04:00
Timothy Warren 77c6419c80 Rector updates for tests 2023-05-19 10:56:23 -04:00
Timothy Warren 97fe3b4b40 PHP 8.1 syntax updates 2023-05-19 10:54:08 -04:00
Timothy Warren 5396091b83 Move new helper functions to a more logical place, fix UserTransformer test 2023-05-19 10:42:25 -04:00
Timothy Warren 655ad119ce Fix broken test 2023-05-18 17:00:00 -04:00
Timothy Warren 351446a2ee Fix formatting 2023-05-18 16:27:31 -04:00
Timothy Warren f2b7f61030 More user page tweaks 2023-05-18 16:25:50 -04:00
Timothy Warren b678a3401e More local fixes, add some more data to user profile page 2023-05-18 15:17:19 -04:00
Timothy Warren 465cd99165 Add specific locale arguments to keep up with the API 2023-05-18 12:55:38 -04:00
Timothy Warren 2e67b49447 More code style updates 2023-05-09 12:52:11 -04:00
Timothy Warren 45449b6907 Code style updates 2023-05-09 12:49:36 -04:00
Timothy Warren 05d4fb1ad7 Style and tool updates 2023-05-09 12:46:52 -04:00
Timothy Warren 81d81f4393 Update aviat/query to fix PHP 8.2 deprecation notice 2023-03-17 16:38:41 -04:00
Timothy Warren 60ddcaa08e Make error handling for ajax list incrementing more robust 2023-03-17 11:09:43 -04:00
Timothy Warren 0e780f26b9 Update Kitsu GraphQL schema 2023-03-16 22:20:17 -04:00
Timothy Warren bedd504b64 Update cache dependency 2023-03-16 22:19:56 -04:00
Timothy Warren 2f657dc20b Add Routing attributes to controllers for a potential future refactoring 2023-03-16 13:04:55 -04:00
Timothy Warren 4f8eefe71e Extract Stringy to fix deprecations 2023-03-16 13:03:48 -04:00
Timothy Warren dd63767ddf Update DB dependency 2023-03-16 13:02:17 -04:00
Timothy Warren e1ff1c6e21 Use MAL and Anlist IDs to do simultaneous updates and syncing. Resolves #39 2022-11-16 10:21:00 -05:00
Timothy Warren f673a84cf6 More work on #39 2022-09-22 12:08:21 -04:00
Timothy Warren 4a2273c93c Fix tests 2022-09-22 11:13:53 -04:00
Timothy Warren 3cb90acb13 Fix incrementing with anilist 2022-09-22 10:21:09 -04:00
Timothy Warren ab38e03af9 Update frontend tooling 2022-09-22 10:20:15 -04:00
Timothy Warren 2139f031f4 Pass anilist ids more directly, see #39 2022-09-21 15:36:38 -04:00
Timothy Warren ec280cd76d Skip another test failing on CI 2022-06-29 11:28:12 -04:00
Timothy Warren 5e7f57eb0a Skip failing tests 2022-06-29 11:25:14 -04:00
Timothy Warren 26cbe4b592 Update broken test 2022-06-29 11:15:47 -04:00
Timothy Warren e8aa2bd42b Bugfix for manga pages without characters 2022-06-28 18:57:50 -04:00
Timothy Warren 501062ac37 Update GraphQL schemas 2022-06-08 20:21:50 -04:00
Timothy Warren 4dc0bb29d2 Show completed message for the last episode/chapter of an anime/manga 2022-03-22 09:53:41 -04:00
Timothy Warren b1c4c8cb5e Move header update script to tools folder 2022-03-04 15:52:18 -05:00
Timothy Warren e4fe5bbfec Simplify file headers 2022-03-04 15:50:35 -05:00
Timothy Warren 4b35d25849 Update header comments 2022-03-04 12:32:17 -05:00
Timothy Warren 3b57d08c0a Tweak formatting settings 2022-03-04 12:31:13 -05:00
Timothy Warren 29e70a8e3f More formatting fixes 2022-03-04 12:25:29 -05:00
Timothy Warren a0d30c002a Reformat test suite files 2022-03-04 12:19:47 -05:00
Timothy Warren 327065498b Reformat misc files 2022-03-03 18:19:02 -05:00
Timothy Warren 047ee4cb37 Set up a proper formatter for more consistent code style 2022-03-03 17:26:09 -05:00
Timothy Warren 9b945ca0a5 Remove more redundant phpdoc properties 2022-03-03 14:20:10 -05:00
Timothy Warren e70b0fdb40 Fix tests 2022-03-03 13:52:06 -05:00
Timothy Warren c8a38d5785 Remove a lot of redundant PHPDoc properties 2022-03-03 13:25:10 -05:00
Timothy Warren 10eb7794e9 Remove setup checks for removed directories 2022-01-17 10:22:46 -05:00
Timothy Warren 0dee5f52fc Remove now unused image caching directories 2022-01-17 10:19:20 -05:00
Timothy Warren 7195258d7e Update broken tests 2022-01-17 10:05:14 -05:00
Timothy Warren 9750d63e55 Use images directly from Kitsu for the rest of the views 2022-01-17 09:59:27 -05:00
Timothy Warren e8a14fedf2 Update user details page to pull images directly from Kitsu 2022-01-17 08:58:59 -05:00
Timothy Warren eb897adbd1 Update more views to use images directly from Kitsu 2022-01-17 08:37:00 -05:00
Timothy Warren 5fb1b87e67 Make all media images come directly from Kitsu API, including on the search pages 2022-01-12 18:23:40 -05:00
Timothy Warren a9b24f0cf7 Update more views to use direct kitsu urls for poster images 2022-01-12 18:00:00 -05:00
Timothy Warren f40ee254c9 Fix more cover image stuff 2022-01-10 17:19:28 -05:00
Timothy Warren 2b047634a0 Use kitsu urls for anime cover images 2022-01-10 17:06:05 -05:00
Timothy Warren 7baa5bfe91 Show images directly for anime detail pages, so cover images are pulled correctly 2022-01-10 16:56:10 -05:00
Timothy Warren 2d6058a6b3 Ensure that list items always have some sort of cover image 2022-01-08 13:04:24 -05:00
Timothy Warren d34c79b4cd Remove unused placeholder images in public directory 2022-01-07 20:27:33 -05:00
Timothy Warren ed558a6484 Use image builder class to simplify createPlaceholderImage 2022-01-07 19:57:07 -05:00
Timothy Warren 854987bd44 Create image builder class to simplify creating placeholder images 2022-01-07 19:53:31 -05:00
Timothy Warren 2977f7385e Merge pull request 'Fix table sorting algorithm' (#41) from colwellkr/HummingBirdAnimeClient:fix-sort into develop
Reviewed-on: #41
2022-01-07 17:04:19 -05:00
Timothy Warren 760cc71768 Merge branch 'develop' into fix-sort 2022-01-07 17:02:19 -05:00
Kevin Colwell 573eac78f1 Removed whitespace from string.replace() call 2022-01-07 18:29:37 +00:00
Timothy Warren 02fa04d19d Code style fixes 2022-01-07 12:33:01 -05:00
Timothy Warren 6e1d190230 Update Anilist GraphQL schema 2022-01-06 12:56:46 -05:00
Timothy Warren 80b2495c11 Make Kitsu auth token error easier to recover from 2022-01-06 12:50:26 -05:00
Kevin Colwell 3d9d8202d9 Updated sorting algorithm 2022-01-02 22:48:20 +00:00
Kevin Colwell 188baa5cc8 Update "TV Rating" to "Age Rating" and add additional classes for sorting. 2022-01-02 21:29:08 +00:00
Kevin Colwell 3dbaf7ef32 Update column headers for better consistency between pages 2022-01-02 21:16:34 +00:00
Timothy Warren 545984bb18 Allow adding items to anime collection that are in the anime list 2021-12-29 17:04:55 -05:00
Timothy Warren f9a5716002 Fix testsuite by disabling tests with outdated data 2021-12-02 17:13:31 -05:00
Timothy Warren 818fcf114d Partially fix broken tests 2021-12-02 17:08:11 -05:00
Timothy Warren 3ca606d6f5 Update and streamline dependencies 2021-12-02 16:28:57 -05:00
Timothy Warren 288e64f357 Remove old CodeIgniter style sniffs, as it is not being used 2021-12-02 16:27:43 -05:00
Timothy Warren 6ed1b81451 Lots of little code fixes, hides notices shown on PHP 8.1 2021-12-02 16:06:34 -05:00
Timothy Warren 823ca8a805 sync Manga before Anime, as it's usually faster 2021-10-18 12:20:03 -04:00
Timothy Warren 7301c4852d Update library creation mutation to remove now redundant userID argument 2021-10-18 12:15:47 -04:00
Timothy Warren 602f0fc9c5 Update frontend dependencies, and update Kitsu graphql schema 2021-10-14 22:09:50 -04:00
Timothy Warren 694e7cc01c Remove redundant updates on finishing a media item 2021-10-08 22:55:54 -04:00
Timothy Warren 7efa34efee Update test snapshot for anime detail page with new info 2021-10-08 19:33:25 -04:00
Timothy Warren 49675ffbee Show full Language names on streaming info for anime detail pages 2021-10-08 19:32:10 -04:00
Timothy Warren b82b7d74fc Add airing date range to anime detail pages 2021-10-08 19:31:40 -04:00
Timothy Warren 5102c7c459 Filter out titles not in English or Japanese from media cards 2021-10-08 18:28:30 -04:00
Timothy Warren 1d022bc8ae Show when a media item is already in the list when searching on the /add pages, resolves #38 2021-10-08 12:16:59 -04:00
Timothy Warren 489ec27602 Refactor media search rendering to be less redundant 2021-10-08 12:15:34 -04:00
Timothy Warren 6e4e065b75 Check user library when searching for new media 2021-10-08 12:06:08 -04:00
Timothy Warren 69db87e305 Attempt to fix Jenkins build, again 2021-10-07 21:51:29 -04:00
Timothy Warren 2dacca5d06 Make some anime detail pages more robust 2021-10-07 21:45:17 -04:00
Timothy Warren 1abac0ac0e Sort libraryEvents so watch history is in correct order 2021-07-30 09:37:06 -04:00
Timothy Warren ba6ed8967c Update Kitsu GraphQL schema 2021-07-30 09:36:25 -04:00
Timothy Warren 3d80f755a1 Merge branch 'develop' 2021-04-23 19:23:08 -04:00
Timothy Warren 7a541b609f Fix build issue with phpstan 2021-04-23 19:22:45 -04:00
Timothy Warren 0f4383563f Merge remote-tracking branch 'origin/develop' 2021-04-23 19:01:21 -04:00
Timothy Warren 7b33d40de4 Update GraphQL reference schemas 2021-04-23 19:00:44 -04:00
Timothy Warren 4c85c22c30 Drastically simplify setup for bundling js files 2021-04-23 18:58:51 -04:00
Timothy Warren 7839cf1515 Even less floating, please 2021-04-21 20:20:39 -04:00
Timothy Warren d2a9aaee54 We don't want our ratings to float... 2021-04-21 20:09:03 -04:00
Timothy Warren ff85cb6153 A few minor tweaks 2021-04-21 19:35:22 -04:00
Timothy Warren 4c396ba9c6 Update filtering of MAL IDs for items to check to update 2021-04-21 19:33:51 -04:00
Timothy Warren d2c397f6b9 Fix null error on updating Anime or Manga 2021-03-01 10:08:36 -05:00
Timothy Warren e679322122 Coverage fix for Ion DI 2021-03-01 10:08:07 -05:00
Timothy Warren e6ae6c9e9c Update Kitsu GraphQL schema 2021-03-01 10:06:12 -05:00
Timothy Warren d387b793ea Misc fixes 2021-02-26 14:42:07 -05:00
Timothy Warren 50bb525f60 Replace Whoops with Tracy 2021-02-23 17:08:36 -05:00
Timothy Warren 8de60b332d Remove redundant docblocks from Type classes 2021-02-23 17:08:16 -05:00
Timothy Warren 51eb460ce9 Test Type classes 2021-02-23 15:38:29 -05:00
Timothy Warren 78a37c736f Attempt to fix the build with the right extension 2021-02-23 13:33:54 -05:00
Timothy Warren 380c455332 Use correct symfony polyfill 2021-02-23 13:15:37 -05:00
Timothy Warren 6c35ade209 Fix broken tests 2021-02-23 13:10:26 -05:00
Timothy Warren 633f30d365 Increase test coverage of Kitsu Transformer classes 2021-02-23 13:00:30 -05:00
Timothy Warren 8c1d882404 Improve test coverage 2021-02-23 12:00:22 -05:00
Timothy Warren 6af73cea55 Better handle update API errors 2021-02-22 15:39:03 -05:00
Timothy Warren d3732d1a54 Update GraphQL schemas 2021-02-22 15:38:29 -05:00
Timothy Warren 3aecaf9161 Better catch api errors on incrementing progress 2021-02-22 15:37:35 -05:00
Timothy Warren b12e94cee4 A few minor fixes 2021-02-18 12:48:59 -05:00
Timothy Warren 3d5d2c05ce Remove RoboFile 2021-02-18 07:37:33 -05:00
Timothy Warren f01cc77f92 Remove some invalid exception docblock tags 2021-02-18 07:22:10 -05:00
Timothy Warren c81271864d Fix Content Security Policy 2021-02-18 07:15:43 -05:00
Timothy Warren 10ea494594 Simplify search template functions 2021-02-18 07:14:07 -05:00
Timothy Warren 836b1d17e6 Use str_contains over strpos 2021-02-17 20:02:51 -05:00
Timothy Warren 5004a9f332 Update js build dependencies 2021-02-17 19:50:15 -05:00
Timothy Warren 40f134d7bc Merge remote-tracking branch 'origin/develop' 2021-02-17 13:07:02 -05:00
Timothy Warren 391708a49c Make PHPStan errors CI failures 2021-02-16 15:07:25 -05:00
Timothy Warren 566c9fbd1e Make PHPStan errors CI failures 2021-02-16 15:05:17 -05:00
Timothy Warren 56bda5ed71 Make PHPStan errors CI failures 2021-02-16 15:01:48 -05:00
Timothy Warren d2fbd3b56a Make PHPStan errors CI failures 2021-02-16 14:58:33 -05:00
Timothy Warren 9f680a75e3 Make PHPStan errors CI failures 2021-02-16 14:57:43 -05:00
Timothy Warren 8fadf9d589 Make PHPStan errors CI failures 2021-02-16 14:56:05 -05:00
Timothy Warren b64d8c6c5b Make PHPStan errors CI failures 2021-02-16 14:53:21 -05:00
Timothy Warren b393c695a5 Resolve remaining PHPStan issues 2021-02-16 14:43:51 -05:00
Timothy Warren 1ab4dedddb Merge remote-tracking branch 'origin/develop' 2021-02-16 12:21:52 -05:00
Timothy Warren 73ee1a41e1 Fix more PHPStan issues 2021-02-12 19:17:39 -05:00
Timothy Warren 44a7d36174 Increase warning level of PHPStan 2021-02-12 17:52:58 -05:00
Timothy Warren 77f314ee55 Fix failing test 2021-02-12 13:14:58 -05:00
Timothy Warren 8d742e62ed Fix a bunch more phpstan errors 2021-02-12 13:09:57 -05:00
Timothy Warren e1fd2bed59 Just go back to CheckStyle 2021-02-12 12:00:44 -05:00
Timothy Warren 61d44146cd Try to report phpStan errors more directly 2021-02-12 11:57:36 -05:00
Timothy Warren b2761541ec Try to report phpStan errors more directly 2021-02-12 11:54:22 -05:00
Timothy Warren 6412f1108b Try to report phpStan errors more directly 2021-02-12 11:48:24 -05:00
Timothy Warren c37f50d06e Try to report phpStan errors more directly 2021-02-12 11:43:25 -05:00
Timothy Warren c900e379c5 Try to report phpStan errors more directly 2021-02-12 11:40:25 -05:00
Timothy Warren 326436c0b5 Try to report phpStan errors more directly 2021-02-12 11:27:20 -05:00
Timothy Warren 66df53bf43 Try to report phpStan errors more directly 2021-02-12 11:24:57 -05:00
Timothy Warren 2cd9f99011 Solve more PHPStan issues 2021-02-12 11:14:45 -05:00
Timothy Warren 35ec3c8bfa Fix code warnings for 'src/AnimeClient/AnimeClient.php' 2021-02-12 10:53:07 -05:00
Timothy Warren 52a0ff275d Attempt to add PHPStan messages to CI 2021-02-12 10:42:51 -05:00
Timothy Warren a26c90ff86 Attempt to add PHPStan messages to CI 2021-02-12 10:40:03 -05:00
Timothy Warren c173a8c196 Attempt to add PHPStan messages to CI 2021-02-12 10:38:16 -05:00
Timothy Warren 73cf8ccd5a Attempt to add PHPStan messages to CI 2021-02-12 10:34:08 -05:00
Timothy Warren 244372ff8c Attempt to add PHPStan messages to CI 2021-02-12 10:19:58 -05:00
Timothy Warren 74ef13713d Attempt to add PHPStan messages to CI 2021-02-12 10:07:55 -05:00
Timothy Warren 057d4bfae7 Attempt to add PHPStan messages to CI 2021-02-12 10:04:13 -05:00
Timothy Warren 68bc4693cb Attempt to add PHPStan messages to CI 2021-02-12 09:59:29 -05:00
Timothy Warren ce3260a2f3 Attempt to add PHPStan messages to CI 2021-02-12 09:51:52 -05:00
Timothy Warren 4c5aa1e3de Attempt to add PHPStan messages to CI 2021-02-12 09:42:54 -05:00
Timothy Warren 3c8b564ab9 Attempt to add PHPStan messages to CI 2021-02-12 09:39:28 -05:00
Timothy Warren 7505907976 Yet more PHPStan fixes 2021-02-11 19:54:22 -05:00
Timothy Warren 05455a518b A few more PHPStan fixes 2021-02-10 17:31:20 -05:00
Timothy Warren c39bc23061 Add a bumch of soundness checks suggested by PHPStan 2021-02-10 17:17:51 -05:00
Timothy Warren 9ba1bd4c90 Code style fixes 2021-02-10 13:59:37 -05:00
Timothy Warren 2f789cc4cf Add tests for title uniqueness check 2021-02-10 10:59:15 -05:00
Timothy Warren a18c0bd7b5 Handle null values better in title uniqueness check 2021-02-08 17:03:04 -05:00
Timothy Warren 2b31cae57b Fix the CI build? 2021-02-07 08:54:20 -05:00
Timothy Warren 48dcaa4bcb Fix the CI build? 2021-02-05 20:21:05 -05:00
Timothy Warren 4342b2e795 Fix the CI build? 2021-02-05 20:16:55 -05:00
Timothy Warren b2361c57c2 Fix the CI build? 2021-02-05 20:05:33 -05:00
Timothy Warren e6fdea28d4 Fix the CI build? 2021-02-05 17:49:19 -05:00
Timothy Warren 15dcdfe39c Update docs and CI 2021-02-05 17:19:11 -05:00
Timothy Warren 0250a50731 Merge remote-tracking branch 'origin/develop' 2021-02-04 12:41:24 -05:00
Timothy Warren f2aca2b76f Fix snapshot test 2021-02-04 12:35:01 -05:00
Timothy Warren 97a7d501d0 Fix tests? 2021-02-04 12:27:52 -05:00
Timothy Warren 8c3b583f92 Update PHP version in header comments 2021-02-04 11:57:01 -05:00
Timothy Warren d6e174a014 Fix tests 2021-02-03 10:27:04 -05:00
Timothy Warren df7a9e311b Merge pull request 'develop' (#37) from develop into master
Reviewed-on: #37
2021-02-03 10:07:11 -05:00
Timothy Warren ebf22643ef Update CI to PHP 8 2021-02-03 09:52:18 -05:00
Timothy Warren 3039f412aa Move to PHP 8 2021-02-03 09:46:36 -05:00
Timothy Warren 37ab6034ba Cleanup some path related things 2021-02-03 09:45:18 -05:00
Timothy Warren d5931ff53b Merge pull request 'Update header year code' (#36) from colwellkr/HummingBirdAnimeClient:develop into develop
Reviewed-on: #36
2021-01-14 12:50:22 -05:00
Kevin Colwell fe1250732c Update header year code 2021-01-13 01:58:28 -05:00
Timothy Warren 144e3f5229 Increase test coverage 2020-12-11 15:37:55 -05:00
Timothy Warren 5f494aa9bd Fix tests for PHP8...? 2020-12-11 14:26:54 -05:00
Timothy Warren 7e0cbe8b83 Make sure to run tests for PHP8 2020-12-11 10:26:24 -05:00
Timothy Warren 31ed9d11ab Fix anime collection error 2020-12-11 10:15:24 -05:00
Timothy Warren 6e3a70f9f6 Update dependencies 2020-12-11 10:14:59 -05:00
Timothy Warren 3c47570cce Bump version in header comments 2020-12-10 17:06:50 -05:00
Timothy Warren b4d9e9f21f A little more test coverage 2020-12-10 17:04:45 -05:00
Timothy Warren 1ea5750a76 Refactor, increase test coverage 2020-12-10 15:59:37 -05:00
Timothy Warren 36b396be71 Update Kitsu GraphQL schema file 2020-12-02 12:43:04 -05:00
Timothy Warren bf4f86a010 Add random anime and random manga pages 2020-12-02 12:42:47 -05:00
Timothy Warren 45b0209d8a Merge pull request 'All in GraphQL' (#34) from develop into master
Reviewed-on: #34
2020-12-01 10:07:48 -05:00
Timothy Warren f37ec8022e Revert status mapping change 2020-10-21 21:23:35 -04:00
Timothy Warren 415778295f Fix broken test 2020-10-21 18:53:32 -04:00
Timothy Warren ad0dcb5750 Remove some more dead code 2020-10-21 18:52:12 -04:00
Timothy Warren e0ad68b9d2 Merge remote-tracking branch 'origin/master' into develop 2020-10-21 18:35:43 -04:00
Timothy Warren 608251452f More cleanup, update changelog 2020-10-21 18:12:22 -04:00
Timothy Warren 8c5547d69d More Kitsu GraphQL API cleanup, resolves #33 2020-10-21 17:59:43 -04:00
Timothy Warren 6c29af4533 Remove test for JsonAPI nonsense 2020-10-21 17:07:50 -04:00
Timothy Warren 898dfebbde Use GraphQL to update thumbnails, refactor GraphQL pagination, merge Anime and Manga traits back into the Kitsu model 2020-10-21 17:06:50 -04:00
Timothy Warren 2d5ae3b1c6 Use GraphQL search endpoints, see #33 2020-10-21 15:45:30 -04:00
Timothy Warren 8256815032 Remove old transformer classes 2020-10-21 15:02:25 -04:00
Timothy Warren fe6f737815 Add missing GraphQL query 2020-10-21 14:56:33 -04:00
Timothy Warren 87d15024bb More GraphQL conversion, test updates, see #33 2020-10-21 14:51:17 -04:00
Timothy Warren 470d25f269 Sync Kitsu and Anilist both via GraphQL, see #33 2020-10-16 16:18:56 -04:00
Timothy Warren 70a33e36c0 Fetch Manga List via GraphQL, see #33 2020-10-16 13:28:35 -04:00
Timothy Warren 94d227b08e Fix Manga List Incrementing, start of GraphQL conversion 2020-10-12 14:06:49 -04:00
Timothy Warren ecb913322f Pull anime lists from GraphQL, see #33 2020-10-09 16:18:45 -04:00
Timothy Warren b001af868f Update dependency versions, add Amp base package as dependency 2020-10-09 16:16:23 -04:00
Timothy Warren 1fbf0283ba Fix updating anime status when certain fields are empty 2020-10-07 15:30:42 -04:00
Timothy Warren 5bcc046a12 Add back search query canceling for anime search 2020-10-07 09:10:11 -04:00
Timothy Warren 9009da4b86 Fix hiding anime on completion 2020-10-05 12:32:12 -04:00
Timothy Warren 47a4be2cf9 Update GraphQL queries to match API changes 2020-09-15 08:08:39 -04:00
Timothy Warren 52aabc2b12 Map more external sites 2020-09-10 15:36:34 -04:00
Timothy Warren 7b1217bafe Fix possible issue with hiding completed anime/manga 2020-09-10 15:35:43 -04:00
Timothy Warren a79ab842ee Remove genres from manga list view 2020-09-09 13:26:31 -04:00
Timothy Warren 810731dfbd Update streaming logs, remove genres from anime list view 2020-09-09 13:25:27 -04:00
Timothy Warren c224c8d977 Only show total length of a series if the number is positive 2020-09-09 10:24:55 -04:00
Timothy Warren ce3e3427dc Update GraphQL schema for Kitsu 2020-09-09 10:24:12 -04:00
Timothy Warren 7211aa0de7 Add limit to all relationships 2020-09-09 10:23:17 -04:00
Timothy Warren 02bd0288f2 Do not check session unless already logged in 2020-08-28 14:27:14 -04:00
Timothy Warren a15496e4a5 Sort voice acting roles by character name 2020-08-27 15:39:23 -04:00
Timothy Warren a14ac3a122 Get Person detail pages via GraphQL, resolves #27 2020-08-27 15:01:00 -04:00
Timothy Warren 1a3f1e9654 More components, resolve #31 2020-08-26 17:26:42 -04:00
Timothy Warren 0c936b3fa7 Misc tweaks 2020-08-26 15:25:31 -04:00
Timothy Warren ccb9c9d331 Extract common methods for Anime and Manga models into a trait 2020-08-26 15:24:49 -04:00
Timothy Warren 738e39ba92 Fix Dispatcher test 2020-08-26 15:23:47 -04:00
Timothy Warren 18e8d47167 Move Kitsu class out of API namespace 2020-08-26 15:22:14 -04:00
Timothy Warren c429ce64d3 Missing pieces of previous commit 2020-08-25 16:06:00 -04:00
Timothy Warren 9003c15929 Abort previous requests when search for anime or manga 2020-08-25 16:02:15 -04:00
Timothy Warren eb56ab4c4f Misc fixes and tweaks 2020-08-25 15:11:08 -04:00
Timothy Warren 29a79577d9 Start of pulling library from GraphQL 2020-08-25 13:22:38 -04:00
Timothy Warren e890f978db Update History to use GraphQL, resolves #29,#30 2020-08-24 19:17:41 -04:00
Timothy Warren e944ddc75c Update profile page to use GraphQL, see #27 2020-08-24 15:20:07 -04:00
Timothy Warren 778cda6efc Some syncing cleanup 2020-08-24 13:10:43 -04:00
Timothy Warren e912c83079 Update some GraphQL queries 2020-08-24 13:09:43 -04:00
Timothy Warren 78b9146249 Get library entry via GraphQL, see #28 2020-08-24 13:07:47 -04:00
Timothy Warren e40a1d028f Fix setup of console commands 2020-08-21 19:26:54 -04:00
Timothy Warren edb022be13 Use components instead of duplicating html everywhere 2020-08-21 19:25:27 -04:00
Timothy Warren b75a99a145 Fix tests 2020-08-21 13:07:00 -04:00
Timothy Warren 7aeb74874b Create component system to help cut down on view duplication, see #31 2020-08-21 12:30:01 -04:00
Timothy Warren 9749c59549 Drastically reduce the amount of junk logging, and remove old logic from Character transformer 2020-08-18 16:59:08 -04:00
Timothy Warren 5da0ba87a7 Use constants for the API names instead of literals 2020-08-17 21:08:53 -04:00
Timothy Warren c749c7c923 Fix sync command 2020-08-17 18:08:58 -04:00
Timothy Warren 9b4c9ad76f Full character page pulled from GraphQL API, see #27 2020-08-17 16:36:55 -04:00
Timothy Warren 681a70fd92 Get character details page from GraphQL, still need to do castings section, see #27 2020-08-17 14:01:55 -04:00
Timothy Warren 67d3b7c1dc Fix manga description page 2020-08-17 11:36:01 -04:00
Timothy Warren 79aee53524 Add streaming links back to anime description pages, see #27 2020-08-17 10:45:17 -04:00
Timothy Warren 56f7d5142d Update Kitsu GraphQL schema reference 2020-08-17 10:25:36 -04:00
Timothy Warren 5f7f4b6bdd Update Kitsu GraphQL Mutations 2020-08-17 10:24:17 -04:00
Timothy Warren 0c3ff2ef11 Improve error logging 2020-08-17 10:23:32 -04:00
Timothy Warren 5997ce8a0f Remove some naming redundancies 2020-08-06 09:39:12 -04:00
Timothy Warren 687831efd5 Add missing change from previous commit 2020-08-05 21:52:36 -04:00
Timothy Warren 5a65c7b645 Add background check for session validity
This checks when the app is made visible -- like the tab is switched to,
if the current session is still valid. If the session is not still
valid, the page is reloaded so that the session expiration is apparent.

Resolves #25
2020-08-05 21:46:14 -04:00
Timothy Warren 9dc6643b78 slugs in person urls, refactor AnilistTrait to match KitsuTrait 2020-08-05 20:57:01 -04:00
Timothy Warren c7beb76404 Create and delete media items via GraphQL, see #28 2020-08-05 13:30:24 -04:00
Timothy Warren c132766486 Move GraphQL queries up a level 2020-08-04 14:25:18 -04:00
Timothy Warren 9a112dc413 Bump version 2020-08-04 09:30:21 -04:00
Timothy Warren 1c3216e26a Get manga updates working correctly with GraphQL, see #28 2020-08-04 09:20:28 -04:00
Timothy Warren 78b195f966 Add some previously missing GraphQL fields 2020-08-03 14:36:14 -04:00
Timothy Warren a35bce8a4b Actually fix anime episode incrementing 2020-07-31 20:00:11 -04:00
Timothy Warren 93faf7d88c Fix json loading overlay for anime updates 2020-07-31 19:32:13 -04:00
Timothy Warren a0e7ebd2a0 Increment chapter/episode counts via GraphQL 2020-07-31 19:03:27 -04:00
Timothy Warren 2b54ab5497 Add GraphQL schema reference for Kitsu 2020-07-31 18:59:16 -04:00
Timothy Warren 7bfdd74f22 Add GraphQL schema reference for Anilist 2020-07-31 18:58:49 -04:00
Timothy Warren 4582e2e917 Refactor/streamline View layer 2020-07-31 16:22:32 -04:00
Timothy Warren b0c75d989f Groundwork for some upcoming API updates 2020-07-30 15:16:24 -04:00
Timothy Warren a3bae9255b Actually fix the character API call, previous commit added authentication to GraphQL calls 2020-07-30 10:02:44 -04:00
Timothy Warren 3ab34a64d0 Fix issue retrieving some characters 2020-07-30 09:58:36 -04:00
Timothy Warren 8110f10c3d Skip broken test 2020-07-29 22:08:54 -04:00
Timothy Warren 7dae2dd6eb Small fixes for anime detail pages 2020-07-29 20:06:59 -04:00
Timothy Warren 7c0ea492e1 Get Manga details from GraphQL, See #27 2020-07-29 17:51:58 -04:00
Timothy Warren 9135598649 Fetch anime details by id for the collection 2020-07-29 16:25:57 -04:00
Timothy Warren 0b0e06af00 Anime detail page cleanup 2020-07-29 15:49:16 -04:00
Timothy Warren 1ae99d2189 get anime staff from GraphQL, see #27 2020-07-29 14:04:03 -04:00
Timothy Warren 7275d81468 Re-add characters to anime details page, see #27 2020-07-29 11:00:54 -04:00
Timothy Warren dbfdd1c239 Run local phpunit from robo 2020-07-29 11:00:06 -04:00
Timothy Warren 9eec7123a3 Use GraphQL request for anime detail pages, see #27 2020-07-28 17:46:18 -04:00
Timothy Warren 710d18a43b Prepare for Kitsu GraphQL 2020-07-28 16:11:13 -04:00
Timothy Warren 6d66ad1ea4 Merge remote-tracking branch 'origin/develop' 2020-05-19 13:19:20 -04:00
Timothy Warren 8d87d2fb2b Remove extra titles from cover/list views 2020-05-18 13:52:27 -04:00
Timothy Warren 61fcffdcbe Make sure reAuthenticate method has optional parameter 2020-05-18 13:47:41 -04:00
Timothy Warren 057216a21c Make sure re-authenticate gets arguments 2020-05-18 13:32:02 -04:00
Timothy Warren abb17844fd Add aria attributes to selected menu items 2020-05-18 12:53:00 -04:00
Timothy Warren 891d8af469 Remove extra titles from list/cover display 2020-05-18 12:52:32 -04:00
Timothy Warren c701999af1 Bug fixes 2020-05-11 09:17:11 -04:00
Timothy Warren af0b392e78 Only the command line should be able to get credentials from the cache 2020-05-08 21:34:36 -04:00
Timothy Warren 2cc85049f3 Refactor KitsuTrait 2020-05-08 19:18:10 -04:00
Timothy Warren 21a98dc48e Remove APCu as a cache option...it doesn't work with CLI authentication 2020-05-08 19:17:11 -04:00
Timothy Warren 3ecccb6ad8 Fix settings page subforms (so all the fields show for the cache) 2020-05-08 19:16:04 -04:00
Timothy Warren e724f885c8 Simplify caching 2020-05-08 19:15:21 -04:00
Timothy Warren 43f07dac6c Set up Event-based handling for a few things 2020-05-06 13:16:40 -04:00
Timothy Warren 7bcff79d6e Fix failing test 2020-05-06 10:12:49 -04:00
Timothy Warren f9f868be9d Show more alternate titles on anime detail pages 2020-05-06 09:08:27 -04:00
Timothy Warren 4a70422b23 Add better re-read messages to manga 2020-05-05 19:12:17 -04:00
Timothy Warren d8167ed075 Comment cleanup of sync command 2020-05-04 17:15:50 -04:00
Timothy Warren b6c0db7636 Refactor list sync to be easier to follow 2020-05-04 17:13:03 -04:00
Timothy Warren ffd7fb8745 Improve rewatched messages 2020-05-04 16:46:27 -04:00
Timothy Warren 75bd011a2c Various code tweaks 2020-05-01 19:38:45 -04:00
Timothy Warren 03638991a3 Fix collection episode length/count, display newlines in notes 2020-05-01 19:33:51 -04:00
Timothy Warren a7e6b3f198 Make authentication more reliable for list syncing 2020-05-01 17:08:20 -04:00
Timothy Warren d5e3dcaff8 Merge remote-tracking branch 'origin/develop' 2020-05-01 11:26:25 -04:00
Timothy Warren 9108fe066a Tweak anime list view a bit 2020-04-30 15:35:32 -04:00
Timothy Warren f810e2573e Collection updates for 'all' tab 2020-04-30 15:33:16 -04:00
Timothy Warren a371a334d0 Collection view tweaks 2020-04-30 15:30:52 -04:00
Timothy Warren ed72eaef84 Merge remote-tracking branch 'origin/develop' 2020-04-28 12:25:35 -04:00
Timothy Warren bcbd76d4a9 Update dependency versions 2020-04-28 12:24:34 -04:00
Timothy Warren 754cf80c0b Type *Type classes a bit more strictly 2020-04-28 12:24:12 -04:00
Timothy Warren ce0935333b Tweak error handling of anime collection db calls 2020-04-28 12:13:35 -04:00
Timothy Warren 050ff98d2c Add AnimeCollection link to MainMenu, similar to lists 2020-04-28 12:04:42 -04:00
Timothy Warren 44d2c0e29d Move unusued graphQL queries 2020-04-28 12:03:14 -04:00
Timothy Warren c14bf3a8af Merge remote-tracking branch 'origin/develop' 2020-04-24 16:55:34 -04:00
Timothy Warren 42ffef32fe Don't show episode/chapter 0 in history 2020-04-24 14:18:35 -04:00
Timothy Warren 1cc5703cd7 Fix some bugs with history view 2020-04-24 14:14:52 -04:00
Timothy Warren 62be0beae6 Fix history and collection bug 2020-04-23 20:03:55 -04:00
Timothy Warren e2e23a290f tweak cleanup migration 2020-04-23 19:40:59 -04:00
Timothy Warren 541b59bb28 Improve anime collection with multiple media selections 2020-04-23 18:57:22 -04:00
Timothy Warren d81fba030c Add migrations for collection improvements 2020-04-23 18:54:54 -04:00
Timothy Warren 209655adc3 Update dependency to published version 2020-04-23 18:53:51 -04:00
Timothy Warren 8be0fceb69 Minor tweaks to css and js 2020-04-23 18:51:12 -04:00
Timothy Warren 8094ff5927 More refactoring of History transformers 2020-04-22 17:53:25 -04:00
Timothy Warren b614505499 Add migration to create a link table between anime_sets and media 2020-04-22 17:52:07 -04:00
Timothy Warren e17846f4a4 Refactor history transformers 2020-04-22 12:38:59 -04:00
Timothy Warren 59f2d21a7f Add menu items for history, add manga reading history 2020-04-22 11:39:44 -04:00
Timothy Warren 0a83184db6 Convert Type constructors to static methods 2020-04-22 07:53:52 -04:00
Timothy Warren e3e32b4408 Fix off-by-one errors 2020-04-21 20:37:42 -04:00
Timothy Warren c424e3a65a Merge remote-tracking branch 'origin/php74' into develop 2020-04-21 20:13:59 -04:00
Timothy Warren 2325c8f4ec Fix aggregation of anime watch history items 2020-04-21 20:10:01 -04:00
Timothy Warren 5a3d9547ae Fix tests 2020-04-21 20:09:37 -04:00
Timothy Warren bc529e57e8 Big Work in progress commit 2020-04-21 19:22:56 -04:00
Timothy Warren f71a1ee1ae Update outdated interface reference 2020-04-17 13:34:36 -04:00
Timothy Warren 797e66e520 Temporarily require develop of Query 2020-04-17 13:19:43 -04:00
Timothy Warren 308a564a2d Merge branch 'php74' of github.com:timw4mail/HummingBirdAnimeClient into php74 2020-04-13 09:20:45 -04:00
Timothy Warren 174877ec81 More types 2020-04-13 09:20:05 -04:00
Timothy Warren 83bb85615a Fix merge conflict 2020-04-13 09:17:50 -04:00
Timothy Warren 570c18a069 Type all the class attributes 2020-04-10 20:01:46 -04:00
Timothy Warren 462e93292b Add more types 2020-04-10 16:35:01 -04:00
Timothy Warren 82cd204469 Remove some old frontend files 2020-04-10 15:43:12 -04:00
Timothy Warren 6d55d4136e Bump version and PHP requirement in headers 2020-04-10 15:39:39 -04:00
Timothy Warren 546789ce40 Update dependencies to latest versions 2020-04-10 15:38:32 -04:00
Timothy Warren 97be2e40f8 Merge branch 'php74' of timw4mail/HummingBirdAnimeClient into develop 2020-04-10 15:27:28 -04:00
Timothy Warren dd708bb1fa Just combine JS files for modern browsers, no minifying 2020-04-10 15:20:47 -04:00
Timothy Warren 93a6dbe7d6 Clean up public folder, move JS tools to frontEndSrc folder 2020-04-10 15:07:08 -04:00
Timothy Warren fae3314b56 Merge remote-tracking branch 'origin/develop' 2020-04-10 10:17:23 -04:00
Timothy Warren dbb61372c6 Fix streaming logo display 2020-04-08 10:08:56 -04:00
Timothy Warren 0b4c2c81c3 Merge remote-tracking branch 'origin/develop' 2020-04-07 22:07:01 -04:00
Timothy Warren 0aee62c174 Fix image snapshot test 2020-04-07 22:04:15 -04:00
Timothy Warren 7fd881c8e9 Lazy load images 2020-04-07 21:53:53 -04:00
Timothy Warren 9158d01fdf Update snapshot tests for form generation 2020-03-17 15:39:33 -04:00
Timothy Warren ae8df3e6d6 Simplify Jenkins build again 2020-03-17 15:15:20 -04:00
Timothy Warren f6e00d4336 Downgrade snapshot library to a version that works with PHP 7.3 2020-03-17 15:05:28 -04:00
Timothy Warren 34b454c175 Attempt to clear dependencies on each step 2020-03-17 11:25:17 -04:00
Timothy Warren 5ce34200c9 Add PHP 7.3 test section back 2020-03-17 11:21:27 -04:00
Timothy Warren 1a6a30ef5d Update test snapshots 2020-03-16 15:47:33 -04:00
Timothy Warren 8faf33c438 Try only PHP 7.4 for tests 2020-03-16 15:25:31 -04:00
Timothy Warren 6e16632988 Revert "Will tests still work with fewer file priviledges?"
This reverts commit 14db3f1ec9.
2020-03-16 15:23:19 -04:00
Timothy Warren 14db3f1ec9 Will tests still work with fewer file priviledges? 2020-03-16 15:19:43 -04:00
Timothy Warren a4fe28f7b5 Fix some style issues 2020-03-16 15:06:55 -04:00
Timothy Warren 7c796b3d7b Cleanup some build/quality check stuff 2020-03-13 09:53:31 -04:00
Timothy Warren 06529d7c92 More test coverage of FormGenerator 2020-03-12 12:47:02 -04:00
Timothy Warren 42948017a4 Test FormGenerator 2020-03-12 12:32:32 -04:00
Timothy Warren 055ec80236 Increase code coverage 2020-03-12 12:04:20 -04:00
Timothy Warren 986ff6de0b Update header comments again 2020-03-12 11:45:11 -04:00
Timothy Warren e6a216704c Remove XML codec 2020-03-12 11:44:19 -04:00
Timothy Warren beaeb13353 Yet another CI code coverage commit 2020-03-12 11:29:00 -04:00
Timothy Warren 0ea35cb421 Yet another CI code coverage commit 2020-03-12 11:20:07 -04:00
Timothy Warren 0feb44a836 Yet another CI code coverage commit 2020-03-12 11:17:27 -04:00
Timothy Warren 9b33a45189 Yet another CI code coverage commit 2020-03-12 11:09:56 -04:00
Timothy Warren e0fa618b4e Yet another CI code coverage commit 2020-03-12 11:08:18 -04:00
Timothy Warren 7672976f47 Yet another CI code coverage commit 2020-03-12 11:04:22 -04:00
Timothy Warren 8036104731 Yet another CI code coverage commit 2020-03-12 10:57:19 -04:00
Timothy Warren 76cb8ca00b Yet another CI code coverage commit 2020-03-12 10:49:32 -04:00
Timothy Warren cc3b999bc5 Yet another CI code coverage commit 2020-03-12 10:44:29 -04:00
Timothy Warren fb327f0c58 Yet another CI code coverage commit 2020-03-12 10:40:39 -04:00
Timothy Warren ee914d048b Yet another CI code coverage commit 2020-03-12 10:36:20 -04:00
Timothy Warren f57466d42c A docker image that exists does help 2020-03-12 10:28:21 -04:00
Timothy Warren 3ce928a67d The correct syntax helps for working CI 2020-03-12 10:26:00 -04:00
Timothy Warren 622b435337 Another attempt to fix CI 2020-03-12 10:24:21 -04:00
Timothy Warren 6acee9ca7a Attempt to do code coverage with less memory to begin with 2020-03-12 10:12:28 -04:00
Timothy Warren 16ecfe3eb5 Does code coverage work with a lower explicit memory limit? 2020-03-12 10:04:05 -04:00
Timothy Warren c0fa4cfed2 Try coverage again without setting memory limit 2020-03-12 10:00:20 -04:00
Timothy Warren 00ef5c3706 Fix test suite 2020-03-12 09:52:45 -04:00
Timothy Warren 618328a4c1 Move AnimeClient tests 2020-03-11 23:18:31 -04:00
Timothy Warren e5ef054f5b Put Ion Namespace back in the codebase directly 2020-03-11 23:04:01 -04:00
Timothy Warren d9e81a7cf1 Increase CI PHP memory limit 2020-03-11 22:27:47 -04:00
Timothy Warren b334a60486 Skip robo lint, as it fails on test snapshots 2020-03-11 22:21:23 -04:00
Timothy Warren 17b01f6d48 Make sure git is installed for CI 2020-03-11 22:18:04 -04:00
Timothy Warren ef7c1da5f2 Move source code to sub folder so we can re-integrate ion 2020-03-11 22:11:00 -04:00
Timothy Warren 6718fc78e9 Remove dependency causing issues on PHP < 7.4 2020-03-11 16:34:33 -04:00
Timothy Warren 5216b60789 Update all the header files again 2020-03-11 16:31:52 -04:00
Timothy Warren 37c3d6ecf0 Move to Amp/HttpClient from Amp/Artax 2020-03-11 16:26:17 -04:00
Timothy Warren eb12f57e7d Attempt to fix Jenkins build, take 2 2020-03-11 15:29:34 -04:00
Timothy Warren a03b9be329 Attempt to fix Jenkins build 2020-03-11 15:24:15 -04:00
Timothy Warren 07de5ff79b Show composer install error for CI, please 2020-03-11 15:16:43 -04:00
Timothy Warren 8aa94f7c14 Update all the header comments 2020-03-11 15:15:05 -04:00
Timothy Warren 8842df76be Bump PHP requirement 2020-03-11 15:14:34 -04:00
Timothy Warren 6047444077 Fix a method of Anilist Model 2020-03-11 15:12:10 -04:00
Timothy Warren 95e8b7920a Remove php7.2 step in CI 2020-03-11 15:05:27 -04:00
Timothy Warren 66b13ef7ba Add code coverage to CI 2020-03-11 15:03:04 -04:00
Timothy Warren b32968588a Minor view updates 2020-01-15 15:23:55 -05:00
Timothy Warren fafd75b791 More error checking 2020-01-15 15:22:38 -05:00
Timothy Warren 70eb4f11b3 Better id mapping error handling for Anilist 2020-01-15 12:35:37 -05:00
Timothy Warren ae70eab9ea Bump copyright year 2020-01-08 15:39:49 -05:00
Timothy Warren 926179a72d More refactoring/cleanup 2019-12-09 16:17:25 -05:00
Timothy Warren 143229bea4 Automatically fix some docblocks 2019-12-09 14:41:04 -05:00
Timothy Warren 3978c4d5cb Update all the docblocks 2019-12-09 14:34:23 -05:00
Timothy Warren 705d48abad Annotate property types for Types classes 2019-12-09 13:40:54 -05:00
Timothy Warren 6044a676a6 Various code style tweaks 2019-12-09 13:13:31 -05:00
Timothy Warren 245e1b4344 Various code cleanup 2019-12-06 15:46:56 -05:00
Timothy Warren ec9edff2f3 Add linting check to CI to help catch version-incompatible code 2019-12-06 10:05:09 -05:00
Timothy Warren 3fa5b7ab88 Downgrade snapshot library so that more PHP versions work for tests 2019-12-06 09:25:04 -05:00
Timothy Warren ceb8159dae Remove composer.lock for each test run to make sure dependencies are correctly installed 2019-12-06 09:19:47 -05:00
Timothy Warren 8b677ab7a7 Update header comments 2019-12-06 09:16:35 -05:00
Timothy Warren b4b5c63d65 Tweak tests for new version of PHPUnit 2019-12-06 09:15:49 -05:00
Timothy Warren 347674f9e5 Update dependencies 2019-12-05 16:59:24 -05:00
Timothy Warren d435a17ec8 Add PHP 7.4 tests 2019-12-05 11:20:06 -05:00
Timothy Warren fde9b05bdf Bump PHP version requirement 2019-12-03 15:17:25 -05:00
Timothy Warren 7d9d2e8990 Merge remote-tracking branch 'origin/develop' 2019-12-02 15:30:04 -05:00
Timothy Warren 995690a341 Update clear thumbnails script to work with lots of files 2019-12-02 15:29:24 -05:00
Timothy Warren 808b704383 Merge remote-tracking branch 'origin/develop' 2019-10-08 20:25:24 -04:00
Timothy Warren cd835055ec Catch errors when mapping MAL ids on sync 2019-10-08 19:59:47 -04:00
Timothy Warren 117427ced0 Misc bugfixes, especially for Anime without a MAL id. 2019-10-07 20:10:27 -04:00
Timothy Warren aebb349543 Update CI stuff 2019-08-16 14:40:15 -04:00
Timothy Warren 37f7616ef4 Merge remote-tracking branch 'origin/develop' 2019-08-16 10:39:50 -04:00
Timothy Warren 28d4ce9e86 Remove php 7.4 version in travis config that doesn't exist yet 2019-08-16 10:35:59 -04:00
Timothy Warren 0e893f06ba Minor code cleanup, add newer php version for travis tests 2019-08-16 10:31:31 -04:00
Timothy Warren 58bb1ab0ba Update base request builder to use the correct user agent 2019-08-10 10:42:02 -04:00
Timothy Warren 46041ccfc6 Add first GraphQL files for Kitsu for future implementation 2019-08-10 10:10:09 -04:00
Timothy Warren 625edf5d0c Improve 404 checks for detail pages 2019-08-10 10:09:07 -04:00
Timothy Warren 4edfd9f62c Update detail pages to use one column for text 2019-08-10 10:07:28 -04:00
Timothy Warren e5baccbf56 Simplify _.show and _.hide useage 2019-07-15 16:05:29 -04:00
Timothy Warren bfd5ff32c1 Merge remote-tracking branch 'origin/develop' 2019-07-15 14:31:17 -04:00
Timothy Warren e1fd639ba9 Fix scroll to top on list item update 2019-07-12 23:12:05 -04:00
Timothy Warren 4e88c27cfb Remove now unused css file 2019-07-12 16:27:39 -04:00
Timothy Warren 0153271a62 Add 'automatic' dark theme, based on browser 'prefers-color-scheme: dark' media query 2019-07-12 15:56:24 -04:00
Timothy Warren 7a529619ed Update js dependencies 2019-07-12 13:33:40 -04:00
Timothy Warren a6020df023 Merge remote-tracking branch 'origin/develop' 2019-07-12 13:28:37 -04:00
Timothy Warren cda711607a Clean up commands a little bit 2019-07-11 19:03:35 -04:00
Timothy Warren c5bb555695 Show fewer sync errors by filtering common data disparity issues 2019-07-11 16:38:21 -04:00
Timothy Warren ea5eb21941 Fix syncing manga to anilist when you have to create a new list item 2019-07-11 15:24:34 -04:00
Timothy Warren 038e61bf37 Make Anilist missing username error more reliable, allow editing anilist username in settings panel 2019-07-11 10:28:09 -04:00
Timothy Warren bce1afa546 Collection "All Tab", and filtering. Resolves #6, #7 2019-07-10 13:32:05 -04:00
Timothy Warren 4502c2f183 No more genre-related database errors, and other collection improvements 2019-07-10 10:20:37 -04:00
Timothy Warren b070282899 Merge remote-tracking branch 'origin/develop' 2019-05-08 16:09:29 -04:00
Timothy Warren aa6965e98f Tweak display of descriptions on detail pages 2019-05-08 16:08:51 -04:00
Timothy Warren b2c8bff967 Merge remote-tracking branch 'origin/develop' 2019-05-08 14:18:49 -04:00
Timothy Warren ea2a368100 Use larger cover images for edit forms 2019-05-08 14:18:18 -04:00
Timothy Warren 523fcbd0d9 Edit form style tweaks 2019-05-08 14:17:57 -04:00
Timothy Warren 003492a964 Merge remote-tracking branch 'origin/develop' 2019-05-08 13:19:32 -04:00
Timothy Warren e98699acbc Fix thumbnail generation command 2019-05-08 13:19:03 -04:00
Timothy Warren eb31096bff Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master 2019-05-08 12:40:32 -04:00
Timothy Warren d1a0147ae2 Button and Select style tweaks 2019-05-08 11:14:11 -04:00
Timothy Warren 6c81ddbfd4 Add polyfill for older browsers, so Opera 12 works 2019-05-08 08:57:15 -04:00
Timothy Warren 2fdca56501 Update js sourcemaps 2019-05-08 08:56:26 -04:00
Timothy Warren 47d6314178 Use static closures in bootstrap 2019-05-08 08:55:58 -04:00
Timothy Warren e73326fd6c Update css/js dependencies 2019-05-08 08:53:34 -04:00
Timothy Warren 251bbadd96 Style tweaks. Fixes #16. 2019-05-08 08:50:57 -04:00
Timothy Warren 6ee198c742 Fix some edge cases 2019-04-01 16:17:40 -04:00
Timothy Warren dfdc6b7356 Remove XML tests 2019-03-12 09:47:59 -04:00
Timothy Warren 94b15f455c Remove XML codec class 2019-03-12 09:43:17 -04:00
Timothy Warren f6278a1304 Consistent spacing around auth checks 2019-01-29 16:01:31 -05:00
Timothy Warren 04c5b135a7 Add a per-controller-method check for authorization for private routes 2019-01-29 15:12:31 -05:00
Timothy Warren 8a8ea0b470 Cleanup redundant methods in Collection model 2019-01-28 14:31:48 -05:00
Timothy Warren 4e2437f2bc Fix error on attempt to insert a duplicate series 2019-01-22 10:21:58 -05:00
Timothy Warren 27c7f08d7f Hide missing table error on noninitialized collection, see #20 2019-01-08 15:52:53 -05:00
Timothy Warren b6f12ff2f6 Update phinx.yml file for new version of Phinx, see #20 2019-01-07 14:31:17 -05:00
Timothy Warren 317d8fd29b Cleanup database logic a bit 2019-01-07 14:29:15 -05:00
Timothy Warren b66a35843d Small code cleanup 2019-01-07 09:08:00 -05:00
Timothy Warren ac382a96a8 Simplify/clean up some base classes 2018-12-21 15:52:34 -05:00
Timothy Warren 87d0960424 Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master 2018-12-13 14:51:01 -05:00
Timothy Warren be16ceecb2 Make syncing slightly more robust 2018-12-12 15:31:59 -05:00
Timothy Warren 105b0f52ca Use the same API client instance across the codebase 2018-12-07 10:24:42 -05:00
Timothy Warren 63a50f7ed8 Don't show media tabs with no media on character page, make stats on user page more resiliant 2018-12-07 10:22:16 -05:00
Timothy Warren 4b9f97f49e Remove default API client timeouts, fix time on anime calculation 2018-12-06 16:21:02 -05:00
Timothy Warren 8272cc7240 Remove CSS sourcemaps...because they're pointless 2018-12-06 13:44:31 -05:00
Timothy Warren 538201ef6f Add dark theme with setting toggle 2018-12-06 13:04:54 -05:00
Timothy Warren 5e9780aad7 Update misspelled method, somehow resolves #19 2018-11-29 11:46:06 -05:00
Timothy Warren f3265484da Some API client cleanup 2018-11-29 11:00:50 -05:00
Timothy Warren 4c3f987b85 Fix error in list sync 2018-11-27 15:37:16 -05:00
Timothy Warren 5e3fb46159 Update cache dependency 2018-11-27 14:57:27 -05:00
Timothy Warren 2c73e721d0 Misc code cleanup 2018-11-09 10:38:35 -05:00
Timothy Warren 431f6e7d21 Purge the few inline styles 2018-11-08 14:18:24 -05:00
Timothy Warren c0e16c6d07 Remove data transformation from media detail pages, and into the proper transformers 2018-11-08 12:15:30 -05:00
Timothy Warren 9c0b1e73ef Move data transformation out of controllers, and into transformers 2018-11-08 11:36:42 -05:00
Timothy Warren 05842baccb Fix generic user page route, minor code cleanup 2018-11-07 14:29:21 -05:00
Timothy Warren 7f6b0178a8 Merge remote-tracking branch 'origin/master' into develop 2018-11-05 13:25:42 -05:00
Timothy Warren 8b938add27 Fix collection query 2018-11-05 13:25:18 -05:00
Timothy Warren 8672112bdc Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master 2018-11-05 13:15:58 -05:00
Timothy Warren 033ea46754 More styling tweaks 2018-11-05 11:22:35 -05:00
Timothy Warren 24f80bc18c Make tables responsive 2018-11-05 11:04:19 -05:00
Timothy Warren 4b75987f21 Fix broken snapshot test 2018-11-05 10:42:51 -05:00
Timothy Warren ca487901c2 Sort streaming links by service 2018-11-05 10:40:29 -05:00
Timothy Warren e195987436 Some visual tweaks 2018-11-05 09:56:38 -05:00
Timothy Warren 350dae0109 Responsive updates for smaller screen sizes 2018-11-05 09:47:05 -05:00
Timothy Warren d514c319c0 Update picture helper, move anilist oauth calls to the settings controller 2018-11-02 12:58:19 -04:00
Timothy Warren 4ace9b6806 Make all the css classes and ids kebob case 2018-11-02 10:48:20 -04:00
Timothy Warren 64478d4507 Update controller test 2018-11-01 22:16:45 -04:00
Timothy Warren f314538972 Various refactoring, better webp image handling 2018-11-01 22:15:20 -04:00
Timothy Warren 155650961b Make Controllers more specialized 2018-11-01 22:12:41 -04:00
Timothy Warren b3366131b8 Lots of visual updates 2018-11-01 22:01:09 -04:00
Timothy Warren 040b7f3fdc More page style tweaks 2018-10-30 13:05:49 -04:00
Timothy Warren ef1e435c6b Add tabs to character page sections 2018-10-30 11:42:32 -04:00
Timothy Warren a9c3a44f37 Fix css for character images on user page 2018-10-30 09:43:54 -04:00
Timothy Warren 3842df13db Small code consistency update 2018-10-29 15:48:54 -04:00
Timothy Warren be2f7708ad Add staff section on Manga detail pages 2018-10-29 15:17:48 -04:00
Timothy Warren 29a4114e8c Fix staff section on Anime detail pages, center unusually sized images instead of stretching them 2018-10-29 14:43:06 -04:00
Timothy Warren 1690d8c1e0 Attempt to fix tests again 2018-10-29 10:07:20 -04:00
Timothy Warren e84cbe1bb2 Update test snapshots 2018-10-29 09:41:50 -04:00
Timothy Warren d0af6fd9e8 Update JsonAPI helper to better handle input data without mangling 2018-10-29 09:39:56 -04:00
Timothy Warren bcc7815ae6 Ugly Progress Commit
* Update Person pages to have series organized by character for Voice
Acting
* Miscellaneous style updates
* Add placeholder images for items missing images
2018-10-26 13:08:45 -04:00
Timothy Warren 5d87bd044c Refactor some silly switches 2018-10-19 10:40:11 -04:00
Timothy Warren e2b4fae83b Fix broken tests 2018-10-19 10:39:38 -04:00
Timothy Warren 019fff5d62 Miscellaneous page improvements, including additional data and sorting 2018-10-19 09:30:27 -04:00
Timothy Warren cf1c495f90 Remove need for www subdomain for streaming service mapping 2018-10-17 14:33:16 -04:00
Timothy Warren cee5a28816 Replace switch statement with array mapping 2018-10-17 14:20:07 -04:00
Timothy Warren 83a6629f03 Fix tests, and category list for Manga detail page 2018-10-16 14:32:52 -04:00
Timothy Warren 5810405f12 Remove a reference to genres from an older version of the Kitsu API 2018-10-16 14:22:47 -04:00
Timothy Warren 7b765c6d0b Account for missing genres in anime collection 2018-10-11 16:40:51 -04:00
Timothy Warren a9acf23a15 Update docs a bit 2018-10-11 13:25:53 -04:00
Timothy Warren b14a827715 Update shell script for Jenkins 2018-10-11 13:06:58 -04:00
Timothy Warren 1cd8386559 Closer to working Jenkins 2018-10-11 12:50:29 -04:00
Timothy Warren 1fa11cd50d Try again, for Jenkins 2018-10-11 12:22:44 -04:00
Timothy Warren 8a7223593d Maybe third time is a charm for Jenkins? 2018-10-11 12:08:57 -04:00
Timothy Warren ccc9ad0c14 Attempt2 with Jenkins 2018-10-11 11:54:27 -04:00
Timothy Warren 509db151e7 Let's do CI with Jenkins 2018-10-11 11:11:24 -04:00
Timothy Warren 0bbc4fe4fb Default to secure (https) urls 2018-10-11 09:53:14 -04:00
Timothy Warren 86c311dddf Add console command to re-generate list thumbnails 2018-10-10 16:04:58 -04:00
Timothy Warren eaf3554611 Attempt to fix ssl detection 2018-10-10 15:58:28 -04:00
Timothy Warren 99aaf0303b Fix broken url generator test 2018-10-10 14:26:44 -04:00
Timothy Warren 6d1df75889 Always set the url protocol for the url generator 2018-10-10 14:21:46 -04:00
Timothy Warren 9c8e396c9a Fix javascript minification 2018-10-10 12:57:11 -04:00
Timothy Warren c9ec11c2df Fix tests 2018-10-09 18:26:42 -04:00
Timothy Warren 587d5fa14e Update config.toml.example file 2018-10-09 18:21:06 -04:00
Timothy Warren 229703a6a6 Update README 2018-10-09 18:15:03 -04:00
Timothy Warren 5b8f0c4a9e Full Anilist settings page OAuth flow, ability to run app without manually editing config files. See #7. Resolves #5 2018-10-09 18:10:20 -04:00
Timothy Warren 41d71dac0c Cleanup styles of settings page, cleanup syncing command a bit 2018-10-09 10:11:42 -04:00
Timothy Warren 6dfa66dbde Fix issue with cache settings 2018-10-08 16:47:40 -04:00
Timothy Warren 324abc0f61 More settings, now with tabs 2018-10-08 16:38:08 -04:00
Timothy Warren 3c0fd79195 Settings control panel saves to admin-override.toml in the app/config directory, resolves #7 2018-10-08 15:45:46 -04:00
Timothy Warren 0d30f57e83 Update gitignore 2018-10-08 15:44:03 -04:00
Timothy Warren 247a9d0e5b More webp images, fix login 2018-10-05 22:36:54 -04:00
Timothy Warren d6800dbc46 Ugly Progress Commit
* Cache and resize images - not just cache them
* Convert to webp on cache
* Show webp images if available
* Settings Form Generation (doesn't yet save)
2018-10-05 21:32:15 -04:00
Timothy Warren ae283cd898 Add command to check Kitsu's MAL id mappings 2018-10-05 14:40:30 -04:00
Timothy Warren a8f898822a Update code to use simpler config 2018-10-05 14:32:05 -04:00
Timothy Warren da936b325e Merge config.toml and route_config.toml 2018-10-05 14:27:07 -04:00
Timothy Warren 8b3ce0f079 Fix some api mapping issues for #5 2018-10-01 13:03:48 -04:00
Timothy Warren c9ed90acb4 Update header comments to version 4.1 2018-10-01 11:35:51 -04:00
Timothy Warren 17a9539e94 More work on #5 2018-10-01 10:50:22 -04:00
Timothy Warren 6f717e6ab7 Fix tests 2018-09-27 16:48:12 -04:00
Timothy Warren 0f31a5e10a Ugly progress commit 2018-09-27 16:45:12 -04:00
Timothy Warren e0376c78d1 Fix tests 2018-09-26 22:43:04 -04:00
Timothy Warren a6c253b969 Lots of Anilist integration, see #5 2018-09-26 22:31:04 -04:00
Timothy Warren 5a607db93e Rebuild scripts and css 2018-09-20 16:12:28 -04:00
Timothy Warren e7dc1e8e53 Anilist CRUD operations for Anime! See #5 2018-09-20 16:08:46 -04:00
Timothy Warren 38a5b78295 Update test snapshots 2018-09-20 11:53:12 -04:00
Timothy Warren 034213fccc Progress with simultaneous updates to Anilist for Anime 2018-09-20 10:41:28 -04:00
Timothy Warren 020f561773 JS style updates 2018-09-19 14:11:35 -04:00
Timothy Warren 2fc26bf4c6 Let's do ES modules for browsers that support them 2018-09-14 11:56:48 -04:00
Timothy Warren 09530efefd Optimize streaming service logos 2018-09-12 14:20:51 -04:00
Timothy Warren 0624c3be67 Check config object shape on page load 2018-08-24 14:37:53 -04:00
Timothy Warren a71fb185bd Add Config 'Type', to keep config settings somewhat in check 2018-08-24 14:36:58 -04:00
Timothy Warren 98ae142757 Cleanup config a bit 2018-08-24 14:23:01 -04:00
Timothy Warren cd150d7fef Fix stupid type error 2018-08-22 13:51:58 -04:00
Timothy Warren 3bca049cd8 Update file header comments 2018-08-22 13:48:27 -04:00
Timothy Warren 95b06a7e7e Eradicate MAL integration 2018-08-22 13:43:04 -04:00
Timothy Warren 81f02ad622 Cleanup javascript into fewer files, add show/description link to search pages 2018-08-22 12:54:06 -04:00
Timothy Warren 64d3c241f6 Update service worker so it only caches images 2018-08-21 17:11:03 -04:00
Timothy Warren 4a91a5cb5d Anime Collection improvements
* Allow editing title and alternate title
* Show list of genres on list view of collection
2018-08-21 17:09:42 -04:00
Timothy Warren 226f0ced83 Getting started with some service workers 2018-08-20 16:24:33 -04:00
Timothy Warren bc2122dd98 Fix test failure 2018-08-20 13:41:25 -04:00
Timothy Warren 2a2ff87b3b Various cleanup, some work on #7 2018-08-20 13:01:16 -04:00
Timothy Warren 7c0d02758b Remove php js minifier script, in favor of commited js files 2018-08-20 12:58:56 -04:00
Timothy Warren e6761807b8 Add basic check for folder permissions for quicker troubleshooting 2018-08-16 12:10:24 -04:00
Timothy Warren bfb5d6323d More progress on #5 2018-08-15 16:19:07 -04:00
Timothy Warren b3a3e19146 Making API requests to Anilist, see #5 2018-08-15 14:05:28 -04:00
Timothy Warren b5f8413ceb More prep for Anilist integration 2018-08-15 08:51:37 -04:00
Timothy Warren b2554378e7 Merge remote-tracking branch 'origin/develop' 2018-08-14 11:37:43 -04:00
Timothy Warren 1fa5cce5ae Remove lines and shading from add forms 2018-08-14 11:36:26 -04:00
Timothy Warren ea31131e0f Adjust layout of edit pages 2018-08-13 15:13:20 -04:00
Timothy Warren 675f8ed9b2 Update test snapshots 2018-08-10 20:11:22 -04:00
Timothy Warren 0dcf25e16c More refactoring work, some groundwork for Anilist integration 2018-08-10 20:10:19 -04:00
Timothy Warren dd46e292c4 Update some styles 2018-08-10 20:09:28 -04:00
Timothy Warren e9888c762e Fix test snapshots 2018-08-10 11:01:30 -04:00
Timothy Warren 49295148d1 Update some types
* Remove empty values from types for serialization, so that empty values
are not sent with API requests
* Allow use of explicit setters for more complex types
2018-08-09 11:34:02 -04:00
Timothy Warren d1421d2eb2 Better error handling for incrementing watched count on Anime list 2018-08-09 11:31:15 -04:00
Timothy Warren 69c2482fc1 Remove references to MAL syncing, resolves #4 2018-08-09 11:16:44 -04:00
Timothy Warren 571bbf7595 More tabs for collections, see issue #2 2018-08-09 11:14:57 -04:00
Timothy Warren e01a96b8fb First go at tabs for collection 2018-08-08 17:04:35 -04:00
Timothy Warren 2c7d866677 Fix PHP 7.1 test 2018-08-08 13:08:10 -04:00
Timothy Warren be2b387391 More refactoring, fix snapshot tests 2018-08-08 13:05:38 -04:00
Timothy Warren 06c55a2094 Fix tests 2018-08-08 11:18:57 -04:00
Timothy Warren 9a7084078f Some minor code cleanliness refactoring 2018-08-08 10:12:45 -04:00
Timothy Warren f71e9dbe4d Merge remote-tracking branch 'origin/master' into develop 2018-06-15 08:47:16 -04:00
Timothy Warren cecca5f9f0 Give a better error message on failing to parse an XML API response 2018-06-15 08:46:28 -04:00
Timothy Warren 79be0ebb34 Use more efficient method of combining large sets of data from Kitsu 2018-04-11 09:26:14 -04:00
Timothy Warren c7a77a2eb5 Merge branch 'master' of timw4mail/HummingBirdAnimeClient into develop 2018-04-05 23:03:43 -04:00
Timothy Warren c0c72e40e4 Add more missing streaming logos 2018-04-05 23:00:58 -04:00
Timothy Warren 01bf7144c2 Merge remote-tracking branch 'origin/develop' 2018-04-05 21:56:02 -04:00
Timothy Warren f5f59b8382 Update wiki and CI links due to move from Gitlab 2018-04-05 21:20:43 -04:00
Timothy Warren 75a5727a2e Add Hidive to streaming service mapping 2018-04-05 08:56:01 -04:00
Timothy Warren 4f0c3f7984 Add Hidive to streaming service mapping 2018-04-05 08:39:49 -04:00
Timothy Warren 83e0310ca7 Merge remote-tracking branch 'origin/develop' 2018-02-02 09:53:22 -05:00
Timothy Warren 9cac51bd82 Miscellaneous style updates 2018-02-02 09:50:58 -05:00
Timothy Warren a434c032a2 Minor refactor of Commands 2018-01-31 15:44:48 -05:00
Timothy Warren ad0154f431 Merge remote-tracking branch 'origin/develop' 2018-01-31 11:36:54 -05:00
Timothy Warren 080b112608 Update test snapshot 2018-01-31 11:00:10 -05:00
Timothy Warren 92ad051f6a Add trailer videos to anime detail pages 2018-01-31 10:55:20 -05:00
Timothy Warren da8f4acb29 Use template literals instead of mustache templates 2018-01-30 16:57:13 -05:00
Timothy Warren 43ab033aec Merge remote-tracking branch 'origin/develop' 2018-01-30 14:28:07 -05:00
Timothy Warren 83f9d14630 Fix issue with anime detail pages 2018-01-30 14:02:28 -05:00
Timothy Warren 634335187e Merge remote-tracking branch 'origin/develop' 2018-01-25 19:31:34 -05:00
Timothy Warren f06ba6e3cd Lots of style fixes and minor logic tweaks 2018-01-18 16:21:45 -05:00
Timothy Warren 675b1ef2f1 Update README for requirements 2018-01-16 15:05:17 -05:00
Timothy Warren a46a85bf71 Remove php 7.0 test 2018-01-16 15:04:25 -05:00
Timothy Warren 3a739e3920 Make character layout more closely match anime/manga pages 2018-01-16 14:58:30 -05:00
Timothy Warren 231babe218 Code style improvements 2018-01-16 14:58:07 -05:00
Timothy Warren 4532bd1865 Remove strict types from Artax client implementation 2018-01-15 14:49:17 -05:00
Timothy Warren 7b9adbf52e Update copyright year 2018-01-15 14:43:15 -05:00
Timothy Warren f607403111 Merge branch 'master' into 'develop'
Correct index of id for kitsu sync

See merge request timw4mail/HummingBirdAnimeClient!22
2018-01-11 09:49:43 -05:00
Timothy Warren 34996f009b Correct index of id for kitsu sync 2018-01-10 16:43:49 -05:00
Timothy Warren 39031ccf3e Simplify syncing script 2018-01-10 16:34:25 -05:00
Timothy Warren 4451544389 Merge remote-tracking branch 'origin/master' into develop 2018-01-10 16:28:37 -05:00
Timothy Warren 5aa750cde7 Fix some documentation generation issues 2018-01-10 16:24:00 -05:00
Timothy Warren 11068029e8 Handle syncing errors more consistently 2018-01-10 16:18:06 -05:00
Timothy Warren e75c52d9dc Fix bug with sync, remove some code duplication 2017-12-13 11:38:21 -05:00
Timothy Warren 09f9aa2069 Merge branch 'master' into 'develop'
Merge branch 'develop' into 'master'

See merge request timw4mail/HummingBirdAnimeClient!21
2017-12-11 13:24:30 -05:00
Timothy Warren bbe2fd0a5d Update Artax, and update other code to work with new version 2017-12-08 22:32:00 -05:00
Timothy Warren 2ad0a24483 Miscellaneous code style changes 2017-12-06 14:40:13 -05:00
Timothy Warren 0c52831ec6 Merge branch 'develop' into 'master'
Update master from develop

See merge request timw4mail/HummingBirdAnimeClient!20
2017-12-06 12:36:23 -05:00
Timothy Warren 55fca3f92f Correct some errors in creating and deleting anime collection items 2017-12-06 12:30:12 -05:00
Timothy Warren 034174418b Update dependencies 2017-12-06 11:48:15 -05:00
Timothy Warren e8f53542e8 Make sure to add git... 2017-12-06 11:20:12 -05:00
Timothy Warren ae52f8737a Don't try to run update, maybe? 2017-12-06 11:16:47 -05:00
Timothy Warren 888dbe7187 Another attempt to fix gitlab ci builds 2017-12-06 11:12:51 -05:00
Timothy Warren 37f1391a07 Try, try, again 2017-12-06 11:10:50 -05:00
Timothy Warren 8b8ece3dad Attempt to fix gitlab ci build 2017-12-06 11:07:23 -05:00
Timothy Warren 015b3d0f34 Attempt to test PHP 7.2 2017-12-06 10:58:26 -05:00
Timothy Warren ee57f72ca6 Merge remote-tracking branch 'origin/master' into develop 2017-12-04 16:07:46 -05:00
Timothy Warren 9497f5c3df Fix MAL sync issue for anime 2017-12-04 16:06:27 -05:00
Timothy Warren 63fe8684c4 Fix js minification url 2017-12-04 15:57:13 -05:00
Timothy Warren a2fbe471e3 Attempt to fix builds 3 2017-10-19 18:38:56 -04:00
Timothy Warren c6f2981d63 Attempt to fix builds 2 2017-10-19 18:34:11 -04:00
Timothy Warren 410ff4fcc1 Attempt to fix builds 2017-10-19 18:31:28 -04:00
Timothy Warren 7e8e9a3141 Merge branch 'master' into develop 2017-10-18 20:00:00 -04:00
Timothy Warren 98c24d4704 Fix about section of user page 2017-10-18 19:59:29 -04:00
Timothy Warren c9533db6a1 Merge branch 'master' into develop 2017-10-18 19:42:38 -04:00
Timothy Warren a4555ca908 Merge branch 'master' of git.timshomepage.net:timw4mail/HummingBirdAnimeClient 2017-10-18 19:42:01 -04:00
Timothy Warren 45e14a7503 Add proper logging to console commands 2017-10-18 19:28:57 -04:00
Timothy Warren 5eaa33ba82 Fix an issue with Kitsu <-> MAL sync 2017-10-18 19:28:22 -04:00
Timothy Warren 38eee85752 Revert former change so that kitsu anime lists are properly pulled for sync 2017-09-15 16:51:47 -04:00
Timothy Warren 2b9adb0395 Adding missing method for manga collection 2017-09-15 15:05:35 -04:00
Timothy Warren 5fb042a773 Better handle empty lists on sync, resolves #29 2017-09-15 15:04:57 -04:00
Timothy Warren 04ec5b2fd6 Tweak handling of empty list sections 2017-09-14 17:33:24 -04:00
Timothy Warren b0ee397994 Fix an issue fetching anime for the add item call 2017-09-14 17:32:40 -04:00
Timothy Warren 200ff1339c Rough start of Manga collection...need to set up proper structure for manga collection items 2017-09-14 16:18:13 -04:00
Timothy Warren a8e2049d08 Refactor a bit to prepare for manga collection 2017-09-14 15:32:53 -04:00
Timothy Warren 07152cc3be Remove PHP 7.2 test that doesn't work 2017-09-12 12:52:56 -04:00
Timothy Warren 89816dc062 Test with php 7.2 2017-09-12 12:38:26 -04:00
Timothy Warren 3c4e34f1ed Fix tests broken by api change fixes 2017-09-12 12:33:57 -04:00
Timothy Warren 372b616101 Update some api calls based on api changes 2017-09-12 12:18:31 -04:00
Timothy Warren d93d22f7df Add overlay during update request on list pages, resolves #31 2017-07-12 16:40:56 -04:00
Timothy Warren 6493f33faf Attempt to re-authenticate when access token expires 2017-06-19 15:31:24 -04:00
Timothy Warren e66a9f885a Some minor refactoring 2017-06-19 13:49:28 -04:00
Timothy Warren fa74e59854 Update htaccess so images can load 2017-04-28 13:20:59 -04:00
Timothy Warren a15a97370b Update readme with another folder that needs to be writable 2017-04-28 13:20:34 -04:00
Timothy Warren 3fd7c84774 Further filter titles, showing only the canonical title if it is really long 2017-04-26 10:09:14 -04:00
Timothy Warren 428a77b93d Merge branch 'develop' into 'master'
Merge develop into master

See merge request !18
2017-04-24 09:28:40 -04:00
Timothy Warren 8e8ee81397 Kitsu <-> MAL manga list item comparison, resolves #18 2017-04-19 16:48:53 -04:00
Timothy Warren cf12dfee76 kitsu <-> mal comparison for anime, see #18 2017-04-19 16:15:39 -04:00
Timothy Warren d2fc955260 Miscellaneous code cleanup 2017-04-17 16:13:36 -04:00
Timothy Warren 4d991629b1 More main menu tweaking 2017-04-17 14:49:33 -04:00
Timothy Warren 6111dfe6f9 Tweak the main menu a bit 2017-04-17 12:45:29 -04:00
Timothy Warren 921d594931 Update dependencies, and set a more locked-down content security policy 2017-04-13 15:08:28 -04:00
Timothy Warren ac13d57634 Use proxy and cached images on user info page 2017-04-13 14:25:39 -04:00
Timothy Warren 42d36ff4bb Update manga transformer tests 2017-04-13 11:54:58 -04:00
Timothy Warren 28da32f2ac Get images from proxy or cache for Manga views, and Add views 2017-04-13 11:44:03 -04:00
Timothy Warren 08aff2ffe8 All anime images now pull from proxy or cache 2017-04-13 11:26:28 -04:00
Timothy Warren 8b43dee20f Css tweaks, and start caching kitsu images 2017-04-13 11:15:16 -04:00
Timothy Warren 0d2cde37a0 Update .gitignore 2017-04-11 13:28:05 -04:00
Timothy Warren 5cca3cf335 Fix changing a list item's status with no score or progress on MAL 2017-04-11 09:28:07 -04:00
Timothy Warren 679560e185 Fix various edge cases 2017-04-10 15:31:35 -04:00
Timothy Warren d157f097d1 Use a more appropriate function for substring filtering 2017-04-07 16:58:08 -04:00
Timothy Warren ec6f9b1189 Tweak handling of alternate titles, to ensure the +1 button is always usable 2017-04-07 16:44:27 -04:00
Timothy Warren 7825e46321 Replace 0 with - 2017-04-07 13:57:14 -04:00
Timothy Warren 6436ca2e9c Update doc generation 2017-04-06 21:27:47 -04:00
Timothy Warren 81a1a927b1 Make sure Cast heading only shows up if there are actual cast entries 2017-04-06 21:27:03 -04:00
Timothy Warren b210954874 Make sure rating parameter sent to Kitsu is greater than 0 2017-04-06 14:53:38 -04:00
Timothy Warren 472be3c4ed Use snapshots library with tests to simplify testcases 2017-04-06 11:59:53 -04:00
Timothy Warren edc6e6227e Simplify css and javascript minification 2017-04-06 11:45:25 -04:00
Timothy Warren 32b3617fed Update postcss 2017-04-06 10:01:09 -04:00
Timothy Warren 45bf1e1136 Add a better API timeout message emoticon 2017-04-05 13:08:16 -04:00
Timothy Warren fb3805b789 Add staff to character pages 2017-04-05 13:02:48 -04:00
Timothy Warren b861db5d1f Catch API timeouts 2017-04-05 13:01:51 -04:00
Timothy Warren e49ed606f5 Merge remote-tracking branch 'origin/develop' 2017-04-03 16:53:25 -04:00
Timothy Warren 8172d1a593 Remove some dead code 2017-04-03 16:53:04 -04:00
Timothy Warren a413d7d9ca Fix collection images, resolves #26 2017-04-03 16:49:40 -04:00
Timothy Warren 8ceec846a5 Fix creating missing Kitsu items 2017-04-03 15:46:16 -04:00
Timothy Warren d2d48905d7 More basic tests, see #16 2017-04-03 14:46:29 -04:00
Timothy Warren 7d7ae73f5e Merge remote-tracking branch 'origin/develop' 2017-03-31 17:02:26 -04:00
Timothy Warren 82c8d36144 small tweak to user page 2017-03-31 17:01:53 -04:00
Timothy Warren c50b1da53b Details and user page updates, resolves #27 2017-03-31 16:36:22 -04:00
Timothy Warren ddd30fc713 Add favorite characters to user page, see #27 2017-03-31 14:15:29 -04:00
Timothy Warren 1e28a1795d Update detail pages 2017-03-31 13:37:53 -04:00
Timothy Warren b2300f4cfb Remove duplicated function 2017-03-30 16:57:58 -04:00
Timothy Warren 92fe6b7146 Update header comments 2017-03-30 16:49:48 -04:00
Timothy Warren fa4ee22100 Allow over-riding the default lists in the user config 2017-03-30 16:47:02 -04:00
Timothy Warren 5ae6864a3f Fix config typo 2017-03-30 16:18:59 -04:00
Timothy Warren fb567e85e9 Simplify routing code a bit 2017-03-30 16:16:40 -04:00
Timothy Warren 2d33663318 Add rereading info to manga list 2017-03-30 14:50:25 -04:00
Timothy Warren 528d3584b8 Make sure rating is only updated if it is numeric 2017-03-29 16:09:22 -04:00
Timothy Warren 69b4d0c88b More tests 2017-03-29 15:14:30 -04:00
Timothy Warren e79021da29 Add and delete manga simulaneously from kitsu and mal 2017-03-29 14:25:03 -04:00
Timothy Warren 79980683d1 Allow manga +1 button to update both kitsu and mal 2017-03-29 14:00:57 -04:00
Timothy Warren ec7e0cc93b Fix tests 2017-03-29 13:42:40 -04:00
Timothy Warren 15b26d8e39 Simultaneously update kitsu and MAL manga list item 2017-03-29 13:29:03 -04:00
Timothy Warren 377e102650 Create missing manga items on kitsu and mal with sync command 2017-03-29 12:32:36 -04:00
Timothy Warren 4a9e0f0293 Update sync lists command to create Kitsu items that are missing compared to MAL 2017-03-28 16:52:27 -04:00
Timothy Warren 0b18c06058 Minor api model refactoring 2017-03-28 14:36:23 -04:00
Timothy Warren 8d289f9eb5 Reorgnize order of Kitsu model methods 2017-03-28 14:34:33 -04:00
Timothy Warren 394c1241fb Update dependencies, use ParallelAPIRequest 2017-03-28 11:01:38 -04:00
Timothy Warren 0c4c0a436c Merge branch 'develop' into 'master'
Develop

See merge request !17
2017-03-28 10:43:51 -04:00
Timothy Warren b8b5beeae1 Code cleanup and fix 'On Hold' title on all section of anime list 2017-03-27 10:09:45 -04:00
Timothy Warren be0baac962 List characters on manga pages 2017-03-24 10:59:07 -04:00
Timothy Warren cf0db8b9fa Update all the page titles 2017-03-24 09:58:27 -04:00
Timothy Warren dd90dd541c Fix title of anime pages 2017-03-24 09:10:30 -04:00
Timothy Warren cdd3878e55 Show custom 404 pages for missing anime and characters 2017-03-24 09:08:39 -04:00
Timothy Warren 51c26e6b30 Remove code coverage ignore annotations 2017-03-24 08:49:39 -04:00
Timothy Warren 0c2cc0e32b Get rid of whoops 2017-03-23 11:21:13 -04:00
Timothy Warren 828b7a2154 Fix 'all' view with missing sections 2017-03-22 16:53:46 -04:00
Timothy Warren be4f54b99d Merge branch 'master' into 'develop'
Master

Closes #24

See merge request !16
2017-03-22 13:48:01 -04:00
Timothy Warren 25e57eb493 Merge branch 'develop' into 'master'
Update favicon with blue version, resolves #24

See merge request !15
2017-03-22 13:17:27 -04:00
Timothy Warren 707a36fe53 Update favicon with blue version, resolves #24 2017-03-22 13:12:29 -04:00
Timothy Warren cd58e083bf Merge branch 'master' of git.timshomepage.net:timw4mail/HummingBirdAnimeClient 2017-03-22 12:49:18 -04:00
Timothy Warren a259ea7b18 Update streaming link handling, and add daisuki and viewster 2017-03-22 12:29:07 -04:00
Timothy Warren 74602f60fa Fix syntax error, prime manga cache too. See #19 2017-03-22 12:28:19 -04:00
Timothy Warren 53b8ce44bf Fix php 7.1 build? 2017-03-22 11:43:20 -04:00
Timothy Warren e99205be54 Add command to prime cache, see #19 2017-03-22 11:41:25 -04:00
Timothy Warren b52d301b2a Fix issue with updating anime item 2017-03-22 11:15:40 -04:00
Timothy Warren 52bd2773c0 Remove unused mappings 2017-03-22 11:14:59 -04:00
Timothy Warren c9768855a5 All anime api calls are now using paginated requests, see #23 2017-03-22 11:13:50 -04:00
Timothy Warren 1a45e57b7c Miscellaneous code cleanup 2017-03-20 19:08:33 -04:00
Timothy Warren cd242596bc Move link to user profile page 2017-03-20 13:16:01 -04:00
Timothy Warren bc6854a8e5 Show characters on anime details page 2017-03-20 13:14:01 -04:00
Timothy Warren 0d4b26e493 Minor model refactoring 2017-03-14 14:28:08 -04:00
Timothy Warren 0e6a1b6591 More work on user profile page 2017-03-10 12:50:48 -05:00
Timothy Warren 84f0a27d86 use readable cache keys 2017-03-10 12:50:29 -05:00
Timothy Warren 8645926006 More work on profile page 2017-03-08 16:21:01 -05:00
Timothy Warren 960537f8e0 Fix tests and start on profile page 2017-03-08 13:46:50 -05:00
Timothy Warren 5d9b7e9e63 Add basic character pages 2017-03-08 12:55:49 -05:00
Timothy Warren 6bf107e1ad Update all the header comments with the correct repository url 2017-03-07 20:53:58 -05:00
Timothy Warren fa7651faf9 Fix issues with sync-lists command, add more docblocks 2017-03-07 20:49:31 -05:00
Timothy Warren 8b2cd7dd1e Fix 'All' section on Manga page 2017-03-07 18:41:51 -05:00
Timothy Warren 5d2dac5b99 Add back 'All' menu item for anime 2017-03-07 17:51:08 -05:00
Timothy Warren df7102d13d Update gitignore, and make sure cache directory for js minifier exists 2017-03-07 17:48:35 -05:00
Timothy Warren 82b596ec3c Make sure to actually add streaming logos to repo 2017-03-07 15:22:45 -05:00
Timothy Warren 497216cffa Make sure mapping is accurately named
\!
2017-03-03 11:51:53 -05:00
Timothy Warren 969ff75078 Update README 2017-03-03 11:33:42 -05:00
Timothy Warren f932a80e58 Minor refactor of bootstrap setup 2017-03-03 11:33:32 -05:00
Timothy Warren ae6b1cb209 Move AnimeWatchingStatus and MangaReadingStatus enums to the same namespace 2017-03-02 11:12:19 -05:00
Timothy Warren d0a236e7ee Remove accidentially created Java file 2017-03-01 22:11:37 -05:00
Timothy Warren ed01b28c0d Rework the rest of the mappings 2017-03-01 22:07:51 -05:00
Timothy Warren 52e1d1822a Update Manga mappings and enums 2017-03-01 21:52:30 -05:00
Timothy Warren 5e9b3db1f2 Add new mapping class for Anime watching statuses 2017-03-01 20:51:40 -05:00
Timothy Warren e0e1b59777 Merge branch 'develop' into 'master'
Develop

See merge request !14
2017-02-28 16:58:53 -05:00
Timothy Warren 443ffaa132 Rename on packagist 2017-02-28 16:47:39 -05:00
Timothy Warren 4b0226838c Attempt to fix travis ci after switch to phpdbg 2017-02-28 14:24:32 -05:00
Timothy Warren 79ce8c7790 Actually install dev dependencies for gitlab ci 2017-02-28 13:52:39 -05:00
Timothy Warren 0fa4a3c963 Try, try, again 2017-02-28 13:44:41 -05:00
Timothy Warren 14967e9ad0 Add phpunit as a dev dependency 2017-02-28 13:39:49 -05:00
Timothy Warren b4573296d8 Properly setup test coverage 2017-02-28 13:21:37 -05:00
Timothy Warren 0127d65dfc Simplify gitlab ci setup 2017-02-28 13:17:06 -05:00
Timothy Warren 14e8fa9f03 More PHPStan fixes 2017-02-22 15:08:29 -05:00
Timothy Warren adc331d60d PHPStan fixes 2017-02-22 14:46:35 -05:00
Timothy Warren 65227e82ac Move some README info to the wiki 2017-02-21 15:56:19 -05:00
Timothy Warren 56ae9ed80e Update method references in Manga controller 2017-02-21 15:37:29 -05:00
Timothy Warren f3d9af311e Update method references in Collection controller 2017-02-21 15:36:34 -05:00
Timothy Warren 136b7dab66 Simplify database config example 2017-02-21 14:56:10 -05:00
Timothy Warren cd7a836db0 Remove old/unused config options from example file 2017-02-21 14:41:59 -05:00
Timothy Warren e87e5cb47c Make sure anime detail pages don't distort images 2017-02-21 12:24:34 -05:00
Timothy Warren 7122085590 Will teh Gitlab build be triggered? 2017-02-20 15:13:16 -05:00
Timothy Warren 9f0484a93b Use new ParallelAPIRequest class 2017-02-20 13:37:08 -05:00
Timothy Warren 93038e61e5 More code style fixes 2017-02-17 11:37:22 -05:00
Timothy Warren 30b43fd27c Lots of style fixes, with more to come 2017-02-17 10:55:17 -05:00
Timothy Warren 6efe1ffbc8 Slightly reorganize model hierarchy 2017-02-17 08:39:27 -05:00
Timothy Warren 8898655a49 Various tweaking 2017-02-17 08:25:19 -05:00
Timothy Warren 07b1422fad Reference svg logos as image files, not raw html 2017-02-16 14:30:39 -05:00
Timothy Warren 0232d18f1f Yet more snake case to camel case 2017-02-16 14:30:06 -05:00
Timothy Warren 0cef44c986 More javascript style fixes 2017-02-16 13:22:26 -05:00
Timothy Warren 6ed755e252 JS style fixes 2017-02-16 11:47:54 -05:00
Timothy Warren 85cd77267b Make sure header comments are actually updated for all code files 2017-02-16 11:09:37 -05:00
Timothy Warren 488a01f8a5 Js snake case to camel case 2017-02-15 16:58:08 -05:00
Timothy Warren ee2760b2b5 Rename test base class 2017-02-15 16:40:18 -05:00
Timothy Warren 84cb1cb520 And more snake case to camel case 2017-02-15 16:30:14 -05:00
Timothy Warren db07976403 Update header comments 2017-02-15 16:13:32 -05:00
Timothy Warren c96a3bbd50 Snake case to camel case 2017-02-15 16:11:52 -05:00
Timothy Warren 8cbfaf3646 More snake case to camel case 2017-02-15 15:56:10 -05:00
Timothy Warren 29c04e62be More code styles fixes 2017-02-15 15:35:41 -05:00
Timothy Warren 1224882092 Javascript style fixes 2017-02-15 14:08:15 -05:00
Timothy Warren df8b64cff9 Snake case to camel case 2017-02-15 14:07:22 -05:00
Timothy Warren ae42fafe84 Update headers 2017-02-15 13:08:17 -05:00
Timothy Warren da9ebe8867 More style fixes 2017-02-15 13:07:36 -05:00
Timothy Warren 0a72e60f68 Fix more code style issues 2017-02-15 11:57:29 -05:00
Timothy Warren 36874cbe55 Properly namespace all the tests 2017-02-15 11:49:38 -05:00
Timothy Warren d94a280437 Snake case to camel case 2017-02-15 11:30:16 -05:00
Timothy Warren 470c39cf79 Remove some unused code 2017-02-15 11:18:55 -05:00
Timothy Warren 941a15c4f4 Add yarn lock file 2017-02-15 11:05:03 -05:00
Timothy Warren 3caad13577 Try, try again 2017-02-15 10:23:07 -05:00
Timothy Warren acbae86a6b Slim build config 2017-02-15 10:12:18 -05:00
Timothy Warren 34d0aaa8e5 Ignore stupid xsl requirement 2017-02-15 10:05:06 -05:00
Timothy Warren 7941303987 Install xsl because of the stupid dev dependency 2017-02-15 09:57:08 -05:00
Timothy Warren da8b34a867 Will xdebug work? 2017-02-15 09:53:46 -05:00
Timothy Warren 9bf362e08e Will xdebug work? 2017-02-15 09:50:13 -05:00
Timothy Warren 6ee319b78c xdebug try again 2017-02-15 09:47:52 -05:00
Timothy Warren 90f1b39db5 Try to use xdebug another way 2017-02-15 09:42:20 -05:00
Timothy Warren 85a6fafd4f Make sure to try to install the correct packages 2017-02-15 09:36:51 -05:00
Timothy Warren 2bdf4be682 Remove unneeded bashism 2017-02-15 09:32:31 -05:00
Timothy Warren 455adf4b11 Maybe this will work better? 2017-02-15 09:30:27 -05:00
Timothy Warren 9e49566641 Maybe stages will help? 2017-02-15 09:26:49 -05:00
Timothy Warren d1f0ab0c73 Attempt to use alpine php image 2017-02-15 09:21:08 -05:00
Timothy Warren 79153ae433 Attempt testing hhvm with a different docker image 2017-02-15 08:55:51 -05:00
Timothy Warren 9c44b5189f Merge branch 'develop' into 'master'
Develop

See merge request !13
2017-02-14 16:39:37 -05:00
Timothy Warren 312125f182 Add hummingbird favicon 2017-02-14 16:23:18 -05:00
Timothy Warren 652eac5be0 Get sync-lists command to create missing entries on MAL 2017-02-14 15:29:13 -05:00
Timothy Warren dba0d47789 Uncomment rewatching stuff 2017-02-13 13:33:01 -05:00
Timothy Warren 4a3be8b4bf Fix mapping from Kitsu to MAL for updating a list item 2017-02-13 12:42:05 -05:00
Timothy Warren 7f5966a147 Attempt to fix hhvm tests 2017-02-10 16:33:42 -05:00
Timothy Warren 8f8f528823 Fix config mapping for BaseCommand 2017-02-10 16:12:02 -05:00
Timothy Warren 1ec7322b18 Split user config from application config 2017-02-10 15:50:07 -05:00
Timothy Warren 8f8c413927 Fix update requests broken by Artax conversion 2017-02-09 20:10:13 -05:00
Timothy Warren 50c543218b Remove 'fix' for issue caused by php.ini setting 2017-02-09 13:45:40 -05:00
Timothy Warren 18af49f1f4 Replace Guzzle with Artax 2017-02-09 13:44:56 -05:00
Timothy Warren 906a1f1efa Another ugly progress commit
- Eradicated Guzzle from main codebase
- All API requests now use Artax
- Refactor code to use function and constant imports
- And more!
2017-02-08 15:48:20 -05:00
Timothy Warren deecb5a912 Start of work to replace Guzzle with Artax 2017-02-08 00:44:57 -05:00
Timothy Warren 02838c5024 Update headers and some whitespace 2017-02-07 13:27:41 -05:00
Timothy Warren bf7f6973a4 Create Request Builder wrapper around Artax 2017-02-07 13:11:42 -05:00
Timothy Warren c0b54e11e1 Update PHPUnit 2017-02-07 09:13:13 -05:00
Timothy Warren 0fe01e14e1 Only translate fields that are passed in 2017-02-07 09:12:44 -05:00
Timothy Warren 2f71a97327 Merge branch 'develop' into 'master'
Develop

See merge request !12
2017-02-06 11:51:58 -05:00
Timothy Warren e73ea09ffd Update test config to allow hhvm failures 2017-02-06 11:35:21 -05:00
Timothy Warren bc0c3774eb Update deprecated test 2017-02-06 11:00:18 -05:00
Timothy Warren b10487ac13 Update changelog 2017-02-06 10:57:38 -05:00
Timothy Warren 64ed6cc0ef Update README 2017-02-06 10:56:27 -05:00
Timothy Warren b6c2aee17a Update dependencies 2017-02-06 10:42:41 -05:00
Timothy Warren 5eea985828 Actually update MAL if enabled 2017-02-04 15:18:34 -05:00
Timothy Warren 1835e34690 Able to create list items on MAL 2017-02-01 09:53:02 -05:00
Timothy Warren 2d0fa51c40 Delete outdated test data 2017-01-31 12:55:28 -05:00
Timothy Warren 108c649a91 Misc updates 2017-01-31 12:52:43 -05:00
Timothy Warren c26a4ca8e8 More test coverage for transformers 2017-01-31 12:51:14 -05:00
Timothy Warren 9d9c1e2cce Add license 2017-01-27 16:34:03 -05:00
Timothy Warren 2cacff6b9b Add coverage button to readme 2017-01-27 15:56:40 -05:00
Timothy Warren 47a43517cb More test coverage, attempt to get Gitlab to see test coverage 2017-01-27 15:41:52 -05:00
Timothy Warren 1b5590bda7 Merge branch 'develop' into 'master'
Develop

See merge request !10
2017-01-27 13:03:53 -05:00
Timothy Warren 91a6d76c4b Attempt to show code coverage 2017-01-27 12:42:57 -05:00
Timothy Warren 03964c446a Ugly progress commit 2017-01-27 12:35:28 -05:00
Timothy Warren 74897faa78 Add back zlib to the docker build 2017-01-27 12:26:32 -05:00
Timothy Warren 2b2ec71edd Add back dependency for xsl in docker build 2017-01-27 12:17:02 -05:00
Timothy Warren a80a860f8d Attempt simpler php setup, with xdebug 2017-01-27 12:09:05 -05:00
Timothy Warren 3d54a0d62c Another attempt at hhvm testing 2017-01-27 11:38:18 -05:00
Timothy Warren aabdf4de30 Attempt to fix hhvm pipeline 2017-01-27 11:21:25 -05:00
Timothy Warren 20c3d69717 Get xml parsing working predictably 2017-01-27 09:43:42 -05:00
Timothy Warren f57c24abe4 Make sure to pass the correct arguments to the cache hash method 2017-01-26 13:06:35 -05:00
Timothy Warren 0ae52a13f3 Refactor KitsuModel, add more docblocks 2017-01-26 13:03:38 -05:00
Timothy Warren ece4f343e2 Fix broken test 2017-01-26 13:02:18 -05:00
Timothy Warren b2fb562de6 Fix display of streaming links in cover and list views 2017-01-25 13:37:39 -05:00
Timothy Warren 19c2d0fddc Merge branch 'develop' into 'master'
Develop

See merge request !9
2017-01-25 12:16:50 -05:00
Timothy Warren b2544fab16 Fix issue with selected list highlighting, fixes #20 2017-01-25 12:13:37 -05:00
Timothy Warren 96cbf78e28 Fix cache clear command 2017-01-19 12:49:18 -05:00
Timothy Warren e30a0b867d Merge branch 'develop' 2017-01-17 12:52:02 -05:00
Timothy Warren 11f9e41254 Update commands 2017-01-17 12:47:02 -05:00
Timothy Warren a45eba3c56 Update cache dependency 2017-01-17 12:46:47 -05:00
Timothy Warren 9bc22baa80 Cache manga list 2017-01-16 14:14:45 -05:00
Timothy Warren 7bad51d867 Fix failing test 2017-01-16 14:05:42 -05:00
Timothy Warren f441b7680a Update views for collection, remove old json import 2017-01-16 14:03:30 -05:00
Timothy Warren 10368fabe4 Fix anime collection 2017-01-16 13:49:51 -05:00
Timothy Warren a42a9bc785 Update README to be more accurate 2017-01-16 12:42:30 -05:00
Timothy Warren 42f152b366 Merge branch 'develop' 2017-01-16 11:27:49 -05:00
Timothy Warren 0ec8d6d6b3 Restore cache clearing functionality 2017-01-16 11:26:19 -05:00
Timothy Warren 1eecff0178 Streaming links, caching, and more MAL integration 2017-01-13 16:53:56 -05:00
Timothy Warren 3e72a66297 Cache API errors at the dispatcher level, so a more appropriate error page can be displayed 2017-01-13 16:52:12 -05:00
Timothy Warren 79c0c6cf90 Update search to bring in My anime list id for future integration 2017-01-13 16:51:31 -05:00
Timothy Warren c1724397d3 Update views to show streaming links 2017-01-13 16:49:46 -05:00
Timothy Warren 9e30783ecb Update transformer tests 2017-01-13 16:48:08 -05:00
Timothy Warren 496eb68078 Really ugly progress commit 2017-01-12 15:41:20 -05:00
Timothy Warren 2fe45a6b57 Update Changelog and Readme 2017-01-12 11:31:49 -05:00
Timothy Warren aca66ae86d Add test data files 2017-01-11 22:27:36 -05:00
Timothy Warren f619c78232 Update config files 2017-01-11 22:26:43 -05:00
Timothy Warren d91432d960 Third time's a charm for updating the header comment? 2017-01-11 19:37:14 -05:00
Timothy Warren 62c06b7731 Start of integration with My Anime List 2017-01-11 19:35:51 -05:00
Timothy Warren e060e1b107 Update header comments, with proper newlines 2017-01-11 10:34:24 -05:00
Timothy Warren 08c40de381 Merge branch 'develop' into 'master'
Replace Hummingbird with Kitsu

See merge request !8
2017-01-11 10:32:10 -05:00
Timothy Warren c76bb4d32a Update header comments 2017-01-11 10:31:17 -05:00
Timothy Warren 93e58874de Merge branch 'master' into 'develop'
Hummingbird to Kitsu

See merge request !7
2017-01-11 10:25:43 -05:00
Timothy Warren 712956d564 Fix unit tests 2017-01-10 21:13:44 -05:00
Timothy Warren 39118d63e5 All basic API functionality:
* Anime List Item:
	* Creation
	* Updating
	* Retreiving
	* Deletion

* Manga List Item:
	* Creation
	* Updating
	* Retreiving
	* Deletion

* Anime detail page
* Manga detail page
2017-01-10 12:35:46 -05:00
Timothy Warren 9f048b739e Fix some javascript issues 2017-01-09 21:38:42 -05:00
Timothy Warren 6e818b7f45 Anime and Manga editing, incrementing, and deletion 2017-01-09 20:36:48 -05:00
Timothy Warren e085754955 Update header comments 2017-01-06 23:34:56 -05:00
Timothy Warren 98bf1e455f Episode incrementing and update work for anime 2017-01-06 21:39:01 -05:00
Timothy Warren 9d84398ee7 Better handling of alternate titles, Airing Status and genres for anime list views 2017-01-05 22:24:45 -05:00
Timothy Warren 09338e9132 Authentication, show edit forms for Anime 2017-01-05 13:41:32 -05:00
Timothy Warren 239b0c055c Update postcss to actually output compatible css 2017-01-04 13:51:04 -05:00
Timothy Warren 444e18c1d9 Update css to fit blocks within poster images 2017-01-04 13:40:46 -05:00
Timothy Warren f1893d9708 Manga lists and detail pages 2017-01-04 13:16:58 -05:00
Timothy Warren 3030b6b908 Start of changes for Manga list 2017-01-03 21:06:49 -05:00
Timothy Warren cdf9ad0105 Remove some old code to better make way for kitsu/MAL api integration 2017-01-03 20:29:43 -05:00
Timothy Warren 05c6f22f67 Another ugly progress commit
Sort of working:
* Get anime list by status
* Get anime description pages
2016-12-22 21:36:23 -05:00
Timothy Warren e74fe9d2f5 Pull stuff from the Kitsu API 2016-12-21 12:46:20 -05:00
Timothy Warren c9f5964d5e Ugly progress commit 2016-12-20 12:58:37 -05:00
Timothy Warren e097b02457 Start of API integration 2016-12-20 12:55:43 -05:00
Timothy Warren f2fcc8ee93 Remove Hummingbird stuff 2016-12-16 21:52:59 -05:00
Timothy Warren 513ba4c70f Fix hhvm tests take 2 2016-11-03 11:39:24 -04:00
Timothy Warren 66e51af8d7 Fix hhvm tests 2016-11-03 11:30:22 -04:00
Timothy Warren ba9c41f495 Update CI tools to exclude old PHP versions 2016-11-01 09:10:11 -04:00
Timothy Warren 07ebb3e988 Update headers and namespaces 2016-10-20 22:32:17 -04:00
Timothy Warren c915ea871d Update EVERYTHING 2016-10-20 22:09:36 -04:00
Timothy Warren 656688a5f3 Make sure to use the version of phpunit I actually install 2016-08-30 12:02:21 -04:00
Timothy Warren 60c9c86580 Attempt to fix travis build 2016-08-30 11:51:35 -04:00
Timothy Warren b0c49ca19c Update Robofile to work properly 2016-08-30 11:45:17 -04:00
Timothy Warren dd2d25d54c Code style fixes to satisfy phpcs 2016-08-30 10:57:41 -04:00
Timothy Warren 230c80459e Update header comments 2016-08-30 10:01:18 -04:00
Timothy Warren f7915ba6f2 Move tests to tests/ directory 2016-08-29 17:09:56 -04:00
Timothy Warren ac971c5248 Move src files to root of src/ 2016-08-29 16:36:13 -04:00
Timothy Warren eaa0e517c1 Build/doc generation updates 2016-08-29 15:50:59 -04:00
Timothy Warren 4e4ac58263 Fix tests broken due to changes in container 2016-08-29 15:36:36 -04:00
Timothy Warren 88e06a0052 Convert Dependency injection bootstrap file to use factory functions, rather than direct instances 2016-08-29 14:51:32 -04:00
Timothy Warren e0e63cf094 Move Ion namespace into composer dependency 2016-08-09 11:08:45 -04:00
Timothy Warren 410d45029f Another attempt at hhvm setup 2016-08-03 18:43:09 -04:00
Timothy Warren 8f3ce089a4 actually use the correct composer command to install phpunit for hhvm 2016-08-03 18:25:28 -04:00
Timothy Warren 7e05a4dcc2 try a different docker container for running hhvm tests 2016-08-03 18:09:55 -04:00
Timothy Warren 7b9b62b200 Attempt hhvm testing on gitlab ci, adjust acceptable failures on travis 2016-08-03 14:30:36 -04:00
Timothy Warren 6bdd33da2a Don't install dev dependencies in test environments 2016-08-03 14:14:38 -04:00
Timothy Warren 3894ba8312 Another attempt to fix gitlab ci build 2016-08-03 14:08:02 -04:00
Timothy Warren 473a046ed8 Attempt to fix gitlab ci build 2016-08-03 13:47:14 -04:00
Timothy Warren 47e67d5d1a Set up mutation testing for unit tests 2016-08-01 14:38:23 -04:00
Timothy Warren e62558d607 Add dev dependencies, augment gitignore 2016-08-01 13:10:00 -04:00
Timothy Warren 6b9770698b Refactor cache to remove dependency on container 2016-08-01 13:02:26 -04:00
Timothy Warren 893584696b Move cache class to IOn namespace, use safer json for serialization in cache drivers 2016-07-28 10:44:13 -04:00
Timothy Warren a108adfa23 Finish moving get_cached_image method to Util class 2016-07-27 14:32:37 -04:00
Timothy Warren f00acbe2d6 Fix travis ci tests 2016-07-27 13:35:30 -04:00
Timothy Warren 563adace2f Refactor out some Interdependency between Ion and AnimeClient namespaces 2016-07-27 13:18:52 -04:00
Timothy Warren cc7046f0ec Update the correct file to change config for gitlab ci 2016-07-25 12:29:42 -04:00
Timothy Warren b415938583 Attempt moving config file in a different way 2016-07-25 12:18:51 -04:00
Timothy Warren 2a41106638 Fix typo in test path 2016-07-25 12:04:56 -04:00
Timothy Warren cb046dbde2 Add redis config file for gitlab ci tests 2016-07-25 11:58:43 -04:00
Timothy Warren 96d3dfdbb4 Update Redis tests to work with gitlab ci 2016-07-22 17:48:13 -04:00
Timothy Warren fa22f7b493 Update Redis cache driver to use PHP-only library, removing the dependence on an extension: 2016-07-22 17:22:00 -04:00
Timothy Warren e2c6c14af8 Remove redundant mbstring extesion from build setup 2016-07-19 10:45:16 -04:00
Timothy Warren ca62a411e1 Attempt 2 to install gd in gitlab ci tests 2016-07-19 10:25:54 -04:00
Timothy Warren 10bb167ff4 Fix failing test by installing gd in gitlab ci test 2016-07-19 10:17:53 -04:00
Timothy Warren ead6f8b487 Set default timezone to prevent stupid test errors 2016-07-18 13:06:37 -04:00
Timothy Warren 672552a1e8 Make tests skip redis integration if the extension is not installed 2016-07-18 12:59:34 -04:00
Timothy Warren f940606f0c Another attempt at getting gitlab ci to run 2016-07-18 12:47:51 -04:00
Timothy Warren b271fd48a6 Attempt tests without redis for now 2016-07-18 10:16:21 -04:00
Timothy Warren 8e00f3a59d Gitlab CI take 3 2016-07-18 10:07:50 -04:00
Timothy Warren ebc8006e04 Make sure docker sh script doesn't have CRLF line endings 2016-07-18 09:58:23 -04:00
Timothy Warren c84e76a869 Gitlab CI take two 2016-07-18 09:55:06 -04:00
Timothy Warren a178ac86c6 First attempt at setting up gitlab ci 2016-07-18 09:47:34 -04:00
Timothy Warren 1e18344990 Merge branch 'develop' 2016-07-15 11:35:31 -04:00
Timothy Warren 6b31e759c9 Minor example file tweaks, add smooth scrolling to browsers that support it 2016-06-07 11:36:02 -04:00
Timothy Warren 1842e1a30b Remove redundant cache loading 2016-04-22 10:52:42 -04:00
Timothy Warren dd20c774ed Shrink api clearing button 2016-04-22 10:47:18 -04:00
Timothy Warren b2990e8457 Add button to clear api cache 2016-04-21 11:14:21 -04:00
Timothy Warren 4e48c8c4fa Update changelog and add additional tests 2016-04-19 14:51:58 -04:00
Timothy Warren 3605ec6d0b Update README for version 3 2016-04-19 13:24:21 -04:00
Timothy Warren 2151d64f25 Small miscellaneous cleanup 2016-04-19 13:23:49 -04:00
Timothy Warren 89080171c9 Add batch image thumbnail creation, see #6, #14 2016-04-19 13:02:50 -04:00
Timothy Warren 733d3f3871 Resolves #10, adds ability to delete from anime collection 2016-04-14 19:10:03 -04:00
Timothy Warren a14d268652 Update sonarqube version 2016-04-14 18:03:34 -04:00
Timothy Warren b5949466e4 Add detail view to anime list 2016-04-14 17:51:00 -04:00
Timothy Warren 1f9afd07f3 Add ability to delete items from manga list. See #10 2016-04-14 17:00:34 -04:00
Timothy Warren 717c296e52 Add ability to delete items from anime list. References #10 2016-04-14 15:16:13 -04:00
Timothy Warren b528b8dc17 Re-add cache to manga controller so cache can be invalidated on update 2016-04-14 11:05:16 -04:00
Timothy Warren 98f709c36e Add tests for Cache Manager class 2016-04-12 14:05:13 -04:00
Timothy Warren 330ac65e18 Add missing update to base API Model 2016-04-12 13:41:50 -04:00
Timothy Warren e26e9f10b6 Update manga model to cache the one api response. 2016-04-12 13:41:03 -04:00
Timothy Warren 7d6c6fe2a0 Add NullDriver for cache layer, for the sake of testing, or config without a cache 2016-04-12 12:05:42 -04:00
Timothy Warren 5d0b879623 Miscellaneous cleanup 2016-04-08 18:05:52 -04:00
Timothy Warren 1a182a62a7 Remove allowed failures for PHP 5.5 & 5.6 2016-04-08 15:21:10 -04:00
Timothy Warren 429c9861e3 One last attempt to get redis to work with travis CI 2016-04-08 15:17:45 -04:00
Timothy Warren 99a5907e1b Another attempt to get redis working 2016-04-08 15:06:16 -04:00
Timothy Warren 1e42d431c8 Update travis config to test redis 2016-04-08 14:56:09 -04:00
Timothy Warren 05a3d0e729 Fix anime collection selection template to match db schema 2016-04-08 14:45:11 -04:00
Timothy Warren 4b4a259f8a Add Redis Cache driver 2016-04-08 14:25:45 -04:00
Timothy Warren 454324aba6 Update first migration to allow empty notes on collection 2016-04-08 13:39:37 -04:00
Timothy Warren 470795f21e Add changelog 2016-04-08 13:22:29 -04:00
Timothy Warren 95abe28322 Remove zepto 2016-04-08 13:22:10 -04:00
Timothy Warren e35373a114 Set up package.json for myth css processing 2016-04-08 11:57:16 -04:00
Timothy Warren ea1d342db5 Remove target=_blank from links 2016-04-08 11:56:17 -04:00
Timothy Warren 83a200871d Add caching to Manga views 2016-04-07 13:11:45 -04:00
Timothy Warren 0f6c998109 Add tests for SQL based api cache 2016-04-07 12:34:57 -04:00
Timothy Warren 440a999c2e Remove json 'cache' files from anime model 2016-04-07 12:32:32 -04:00
Timothy Warren 7711765563 Fix tests 2016-04-06 14:58:19 -04:00
Timothy Warren 7efa180bbf Add some naive cache invalidation to update methods 2016-04-06 12:11:07 -04:00
Timothy Warren 84e4e6ce1b Start of caching implementation 2016-04-05 13:19:35 -04:00
Timothy Warren 972d96c0e0 Fix search methods to work with new Request library 2016-04-05 12:06:07 -04:00
Timothy Warren 3bcb0442ca Add migration for sql cache backend 2016-04-05 12:03:56 -04:00
Timothy Warren 2afbe84afd Start of interface for caching backend 2016-04-01 17:35:53 -04:00
Timothy Warren 06f07978dc Fix broken tests 2016-03-29 11:40:27 -04:00
Timothy Warren 2dde6f8a7d Fix issue with cover not being hidden on last episode 2016-03-29 11:30:51 -04:00
Timothy Warren c8789dc267 Update url generation to use new router 2016-03-07 14:37:49 -05:00
Timothy Warren ed16fd8d45 Fix most of the broken tests 2016-03-03 16:53:17 -05:00
Timothy Warren dd74e85626 Further refactor handling of request variables, routing works again 2016-02-17 11:36:37 -05:00
Timothy Warren ab19e9db08 Get HTML output working again, still refactoring router 2016-02-17 10:29:05 -05:00
Timothy Warren 98f3026a74 Start integration of PSR 7 Request/Response 2016-02-16 16:28:44 -05:00
Timothy Warren 1ba302999d Merge remote-tracking branch 'origin/master' into develop 2016-02-16 12:26:54 -05:00
Timothy Warren 197a1f8326 Update some javascript documentation, and add show/hide methods 2016-02-16 12:07:01 -05:00
Timothy Warren 9cc491a05e Minor style fixes, and fix double message issue 2016-02-10 17:56:46 -05:00
Timothy Warren bcd28acfc5 Minor style fixes, and fix double message issue 2016-02-10 17:50:07 -05:00
Timothy Warren d99f1e7595 Start of migration from php to toml config, see #11 2016-02-10 17:30:45 -05:00
Timothy Warren 1fb95adfbf Merge branch 'develop' into 'master'
Version 2.2

Pull Request tracking changes leading up to version 2.2

See merge request !1
2016-02-10 12:29:39 -05:00
Timothy Warren e8cc479a1e Better front-end tests 2016-02-10 12:25:13 -05:00
Timothy Warren fb29c90691 Update README 2016-02-09 21:42:56 -05:00
Timothy Warren 3027c8a4a8 Update tests relating to issue #9 2016-02-09 21:03:26 -05:00
Timothy Warren 0b6269edd6 Polyfill classList api for browsers lacking support 2016-02-09 20:57:40 -05:00
Timothy Warren 12d05dd71a Fixes issue #9 2016-02-09 20:20:54 -05:00
Timothy Warren ecf3bce14b Partially fix #9 -- API calls fail on 'Plan to Watch' section 2016-02-09 20:12:39 -05:00
Timothy Warren 5563902b69 Combine javascript library files into one base file 2016-02-09 20:07:01 -05:00
Timothy Warren 874ad521a7 Update README with some server setup details, resolves #7 2016-02-09 16:45:22 -05:00
Timothy Warren a9f5e48bb2 Remove last dependencies on zepto 2016-02-08 20:21:41 -05:00
Timothy Warren cbc7555cf2 Remove zepto ajax calls 2016-02-08 13:37:44 -05:00
Timothy Warren e08161aec2 Further improve minifiers, add start of front-end tests 2016-02-08 11:32:39 -05:00
Timothy Warren 7754377ecb Fix #8, make minfiers output correctly 2016-02-08 10:57:44 -05:00
Timothy Warren 0b2a0d4ea9 Add basic htaccess file for apache 2016-02-05 14:14:02 -05:00
Timothy Warren 94f54366a7 Some temporary fixes for tempramental minifiers 2016-02-04 21:57:14 -05:00
Timothy Warren f5dc15659f Make build.xml file more phing friendly 2016-02-03 21:24:10 -05:00
Timothy Warren 1a21a23e73 Rewrite minifiers into cleaner classes. Resolves #5 2016-02-03 14:57:00 -05:00
Timothy Warren 4533ea0b26 Minor spacing fixes 2016-02-02 21:38:38 -05:00
Timothy Warren 540a82fe22 Minor code quality fixes for Scrutinizer 2016-02-02 21:28:32 -05:00
Timothy Warren 467763f8a6 Fix manga editing for sections other than 'Reading' 2016-02-02 14:13:49 -05:00
Timothy Warren 99f2adf491 Add form for manga 2016-02-02 11:34:03 -05:00
Timothy Warren 3fa8e7d8e8 Ugly progress commit 2016-02-01 09:49:18 -05:00
Timothy Warren 9080f45601 Add missing table sorting lib 2016-01-20 20:14:32 -05:00
Timothy Warren 08348fd349 Add proper table sorting and add some security headers 2016-01-20 13:01:41 -05:00
Timothy Warren e109c6a06c Another attempt at code coverage for codeclimate 2016-01-12 12:47:48 -05:00
Timothy Warren 457c2680b6 Code coverage for codeclimate 2016-01-12 12:33:45 -05:00
Timothy Warren a85f0e28c6 Merge pull request #13 from timw4mail/develop
Sync with dev
2016-01-11 15:44:57 -05:00
Timothy Warren b611d02a2c Change logger methods to be inline with interface, fix Manga Model tests 2016-01-11 15:31:53 -05:00
Timothy Warren e51159e7f3 Update whoops to 2.0 2016-01-11 14:57:43 -05:00
Timothy Warren 2391ca14ca Remove errorhandler, and replace with logger 2016-01-11 14:39:53 -05:00
Timothy Warren 212c779552 Update collection to use flash messages and more intelligent redirects 2016-01-11 13:33:56 -05:00
Timothy Warren 0c2243a4f3 Add more test coverage, and update build.xml 2016-01-11 10:42:34 -05:00
Timothy Warren 3981b8471a Further refine Dispatcher 2016-01-08 16:39:18 -05:00
Timothy Warren 276a14a80c Refactor Dispatcher 2016-01-08 15:54:21 -05:00
Timothy Warren b4949eaea2 Fix line endings in view classes 2016-01-08 15:53:50 -05:00
Timothy Warren 993b625042 Actually fix view tests 2016-01-08 11:40:24 -05:00
Timothy Warren 557b27ef77 Fix view tests 2016-01-08 11:19:56 -05:00
Timothy Warren 45b04105f1 Fix http verb for update route, add correct http codes for http errors 2016-01-07 20:48:18 -05:00
Timothy Warren ae4530b5d2 Update codebase to use new Json class 2016-01-07 13:45:43 -05:00
Timothy Warren 00fd23895d Add missing classes 2016-01-06 17:08:44 -05:00
Timothy Warren aa67b941d4 Fix PHP 5.5 build 2016-01-06 17:06:30 -05:00
Timothy Warren b94bf01dee Remove unnamespaced constants, and improve some tests 2016-01-06 15:44:40 -05:00
Timothy Warren ba94f439bb Simplify routing 2016-01-06 11:08:56 -05:00
Timothy Warren e3af767246 Fix testss 2016-01-05 10:05:14 -05:00
Timothy Warren b0f5bdf668 Merge pull request #12 from timw4mail/scrutinizer-patch-2
Scrutinizer Auto-Fixes
2016-01-05 10:02:18 -05:00
Scrutinizer Auto-Fixer 2b5d650ef8 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2016-01-05 14:55:08 +00:00
Timothy Warren e9e16dd2b1 Update header comments, add start of manga editing functionality 2016-01-04 16:58:33 -05:00
Timothy Warren 6b9be7e4d0 Merge pull request #11 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2016-01-04 11:16:15 -05:00
Scrutinizer Auto-Fixer 8b528d8659 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2016-01-04 16:15:25 +00:00
Timothy Warren 9c81836648 Add full edit form to anime list 2016-01-04 10:53:03 -05:00
Timothy Warren c04c85b999 Update composer.json 2015-12-16 10:12:31 -05:00
Timothy Warren 6953cd08e6 Start of delete functionality for anime collection 2015-12-15 15:55:30 -05:00
Timothy Warren c82576e4dd Merge pull request #10 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-12-09 15:13:24 -05:00
Scrutinizer Auto-Fixer b70ff95f03 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-12-09 19:59:54 +00:00
Timothy Warren 30b6afb601 Some more minor code-style fixes 2015-12-09 14:54:11 -05:00
Timothy Warren 6ca086d85b Some code style fixes 2015-12-08 16:39:49 -05:00
Timothy Warren bfb8500386 update travis build file 2015-12-08 14:58:43 -05:00
Timothy Warren 70bfe2bab7 Fix collection functionality 2015-12-08 14:52:59 -05:00
Timothy Warren 7d1c8c383c Update README and composer 2015-11-18 16:03:40 -05:00
Timothy Warren 3b876f2c42 Skip erroring tests on travis 2015-11-18 10:58:12 -05:00
Timothy Warren 56f9fa28aa Fix some minor formating issues 2015-11-18 10:54:06 -05:00
Timothy Warren 2577bad0af Update minor documention issues 2015-11-18 10:48:05 -05:00
Timothy Warren ed1e888c58 Try mocking out get_cached_image method 2015-11-18 10:41:00 -05:00
Timothy Warren 254afc990e More test coverage 2015-11-18 10:31:42 -05:00
Timothy Warren 8d1986d13b Improve some test coverage 2015-11-17 16:45:41 -05:00
Timothy Warren 5d2cad4690 Remove loose functions file 2015-11-16 19:30:04 -05:00
Timothy Warren 49b3f507a6 Fix manga list updating 2015-11-16 15:57:37 -05:00
Timothy Warren 912f1b2ff2 Merge pull request #9 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-11-16 11:40:26 -05:00
Timothy Warren f75016ca69 Update header comments 2015-11-16 11:40:01 -05:00
Scrutinizer Auto-Fixer 4e1bcd962a Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-11-16 15:33:30 +00:00
Timothy Warren 69f35d222c Poor style progress update commit 2015-11-13 16:31:01 -05:00
Timothy Warren 898905c21c Update some config and metadata 2015-11-13 11:34:30 -05:00
Timothy Warren f26adc1c28 Update 404 view 2015-11-13 11:33:47 -05:00
Timothy Warren dfe91d0f1c Make updating of anime list work 2015-11-13 11:33:27 -05:00
Timothy Warren 05912cd540 Update js minifier to be more robust, with better error handling 2015-11-13 11:32:12 -05:00
Timothy Warren ab955f5154 Fix various code style nuances 2015-11-11 15:28:51 -05:00
Timothy Warren 8343fa9182 Fix some sonarqube issues 2015-11-11 14:53:09 -05:00
Timothy Warren 406c7c13cb Merge pull request #8 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-11-09 15:55:54 -05:00
Scrutinizer Auto-Fixer cd8185960b Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-11-09 20:08:08 +00:00
Timothy Warren 87f5324761 Update config and header for new auth class 2015-11-09 11:50:24 -05:00
Timothy Warren a18f1926d9 Fix ArrayType class 2015-11-09 11:49:51 -05:00
Timothy Warren 17a335275c More quality fixes 2015-11-09 11:10:15 -05:00
Timothy Warren ff13f2ce05 No coverage for scrutinizer 2015-11-05 11:30:51 -05:00
Timothy Warren 4937d4c099 Fix some more code style issues 2015-11-05 11:26:03 -05:00
Timothy Warren 1de8b46657 Some more style fixes 2015-11-05 10:41:46 -05:00
Timothy Warren 6aa51e5915 Some code style fixes 2015-11-04 16:53:22 -05:00
Timothy Warren 8936944743 Some minor refactoring 2015-11-04 16:36:54 -05:00
Timothy Warren 76672d5a60 Merge pull request #7 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-11-04 16:33:18 -05:00
Scrutinizer Auto-Fixer 025bc4ef64 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-11-04 21:31:03 +00:00
Timothy Warren a741526da1 Update metadata and build information files 2015-11-04 16:12:46 -05:00
Timothy Warren 799a3652d4 Fix tests broken by missing fix to Anime Collection Model 2015-10-21 15:46:50 -04:00
Timothy Warren 2f8886c28f More test coverage 2015-10-21 15:43:51 -04:00
Timothy Warren c3643565e3 Fix issue where cache file doesn't exist, add tests for Menu Helper 2015-10-21 11:57:58 -04:00
Timothy Warren 28e03f6fb2 Fix default redirect and tests 2015-10-20 16:41:51 -04:00
Timothy Warren 4ef2d6df57 Fix the rest of the menu urls 2015-10-20 15:59:51 -04:00
Timothy Warren 779f4a00eb Remove another vistigal controller method 2015-10-19 15:19:02 -04:00
Timothy Warren d281c26a1c Remove risky tests, update .gitignore 2015-10-19 15:13:18 -04:00
Timothy Warren 77399e3fa4 Remove some vestigal methods from base controller 2015-10-19 13:58:59 -04:00
Timothy Warren 5f5bf66c75 Fix spacing style 2015-10-19 13:26:50 -04:00
Timothy Warren 385037b669 Fix tests for PHP 5.5 2015-10-19 13:02:10 -04:00
Timothy Warren fee09c50ae More test coverage 2015-10-19 12:50:46 -04:00
Timothy Warren 285a132d35 Scrutinizer fixes 2015-10-16 12:53:55 -04:00
Timothy Warren 0b79ac3596 Better testing for ArrayType and Config classes 2015-10-15 22:00:09 -04:00
Timothy Warren de444857dd Add partial test for config delete 2015-10-15 10:23:00 -04:00
Timothy Warren d231b39fdc Fix origin value in API Model tests 2015-10-15 09:49:38 -04:00
Timothy Warren d5c76a0f01 Fix html view test for PHP < 7 2015-10-15 09:28:10 -04:00
Timothy Warren 485ba46838 More test coverage 2015-10-15 09:25:30 -04:00
Timothy Warren 7f2000f180 Update lots of docblocks 2015-10-14 09:20:52 -04:00
Timothy Warren 34acf00b5e Fix documentation issues 2015-10-12 14:27:20 -04:00
Timothy Warren bc6aed51c8 Remove some dead code 2015-10-12 14:11:00 -04:00
Timothy Warren 011bdda777 Merge pull request #6 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-10-12 11:00:09 -04:00
Scrutinizer Auto-Fixer 80f83e5f53 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-10 02:35:39 +00:00
Timothy Warren b687dcf2a8 Rearrange some namespaces and add more docblocks 2015-10-09 22:29:59 -04:00
Timothy Warren bb3e5a643f Merge pull request #5 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-10-09 15:04:55 -04:00
Scrutinizer Auto-Fixer 4dddd3238c Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-09 18:55:15 +00:00
Timothy Warren a8e3c594e3 Basic Menu generation 2015-10-09 14:34:55 -04:00
Timothy Warren 728850da08 More scrutinizer fixes 2015-10-06 13:38:59 -04:00
Timothy Warren 443802332b Merge pull request #4 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-10-06 13:37:48 -04:00
Scrutinizer Auto-Fixer bf6550d0d9 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-06 17:35:42 +00:00
Timothy Warren 37ef0f5621 Fix more scrutinizer issues 2015-10-06 12:15:19 -04:00
Timothy Warren 38cfaa023d Fix failing test 2015-10-06 11:41:21 -04:00
Timothy Warren f3772528c5 Code style improvements 2015-10-06 11:38:20 -04:00
Timothy Warren dbbea163d1 Scrutinizer fixes 2015-10-06 10:44:33 -04:00
Timothy Warren 7269c5c393 Merge pull request #3 from timw4mail/scrutinizer-patch-1
Spacing and docblock fixes
2015-10-06 10:28:33 -04:00
Scrutinizer Auto-Fixer 7e695edd29 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-06 14:24:48 +00:00
Timothy Warren 6b322d18da Miscellaneous updates, prep for menu generator 2015-10-05 16:54:25 -04:00
Timothy Warren be96e2c6af Update Router 2015-10-01 16:30:46 -04:00
Timothy Warren 5de88986ed Update some meta files 2015-10-01 16:21:09 -04:00
Timothy Warren b68cbe5a26 fix test 2015-10-01 16:07:40 -04:00
Timothy Warren 2462e09205 Lots of miscellaneous improvements 2015-10-01 16:02:51 -04:00
Timothy Warren acb0a1e8a2 Fix views to match transformed data 2015-10-01 16:01:23 -04:00
Timothy Warren a73e150ee3 Fix broken test 2015-09-28 15:11:45 -04:00
Timothy Warren b8f753a424 Use Anime transformer class 2015-09-28 14:41:45 -04:00
Timothy Warren 23c16fc9c3 Transformers and Enums 2015-09-25 13:41:12 -04:00
Timothy Warren 082b5296b9 Update manga model to use Zipper transformer 2015-09-21 09:48:15 -04:00
Timothy Warren f87dd2636d More tests for Ion 2015-09-18 22:55:40 -04:00
Timothy Warren 60109b3531 Fix failing tests for PHP < 5.6 2015-09-18 13:06:22 -04:00
Timothy Warren e2731db7ea Decouple and generalise 2015-09-17 23:11:18 -04:00
Timothy Warren 156461a0b9 Start of refactoring routing to be more convention based 2015-09-16 12:25:35 -04:00
Timothy Warren 8a68559f0e More namespace refactoring 2015-09-15 13:19:29 -04:00
Timothy Warren 97a5abe665 Namespace refactoring 2015-09-14 19:54:34 -04:00
Timothy Warren d93f5a82a0 fix a few variable changes, remove old code from app folder 2015-09-14 16:14:02 -04:00
Timothy Warren 9d139a7d1c Pass the tests! 2015-09-14 15:49:20 -04:00
Timothy Warren e53f9abf3f Some progress toward better structure through refactoring 2015-09-14 10:54:50 -04:00
Timothy Warren c5f3093a78 Update default config, add phpci config file 2015-07-20 16:13:00 -04:00
Timothy Warren 4622b6efa9 Update readme with new instructions for collection 2015-07-06 14:35:24 -04:00
Timothy Warren 3e39aa0277 Miscellaneous rework, and adding/editing of collection items when logged in 2015-07-02 14:04:04 -04:00
Timothy Warren 3e53ec1526 More dependency injection, and code coverage 2015-06-30 13:03:20 -04:00
Timothy Warren 2abb5f3c3a Merge pull request #1 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-06-29 10:36:28 -04:00
549 changed files with 67429 additions and 5355 deletions

20
.editorconfig Normal file
View File

@ -0,0 +1,20 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = false
charset = utf-8
indent_style = tab
trim_trailing_whitespace = true
[*.{cpp,c,h,hpp,cxx}]
insert_final_newline = true
# Yaml files
[*.{yml,yaml}]
indent_style = space
indent_size = 4

167
.gitignore vendored
View File

@ -1,10 +1,157 @@
vendor
app/cache/*
public/images/*
public/js/cache/*
composer.lock
*.sqlite
*.db
*.sqlite3
docs/*
coverage/*
# Created by https://www.gitignore.io/api/macos,jetbrains+all
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.gitignore.io/api/macos,jetbrains+all
.codelite
.phing_targets
.sonar/
*.phprj
*.workspace
vendor
**/cache/**
**/logs/**
**/coverage/**
**/docs/**
**/node_modules/**
composer.lock
*.sqlite
*.db
*.sqlite3
apidocs/**
tests/test_data/sessions/*
cache.properties
build/**
!build/*.txt
!build/*.xml
!build/*.php
app/config/*.toml
!app/config/*.toml.example
phinx.yml
Caddyfile
build/humbuglog.txt
public/images/anime/**
public/images/avatars/**
public/images/manga/**
public/images/characters/**
public/images/people/**
public/mal_mappings.json
.phpunit.result.cache
.is-dev
tmp
tools/vendor/
tools/phinx/vendor/
/.php-cs-fixer.php
/.php-cs-fixer.cache

6
.htaccess Normal file
View File

@ -0,0 +1,6 @@
#Rewrite index.php out of the app urls
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

534
.php-cs-fixer.dist.php Normal file
View File

@ -0,0 +1,534 @@
<?php declare(strict_types=1);
use Nexus\CsConfig\Factory;
use PhpCsFixer\{Config, Finder};
$finder = Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
__DIR__ . '/tools',
])
->exclude([
'vendor',
]);
return (new Config())
->setRiskyAllowed(TRUE)
->setFinder($finder)
->setIndent(' ')
->setRules([
'align_multiline_comment' => false,
'array_indentation' => true,
'array_push' => true,
'array_syntax' => ['syntax' => 'short'],
'assign_null_coalescing_to_coalesce_equal' => true,
'backtick_to_shell_exec' => true,
'binary_operator_spaces' => [
'default' => 'single_space',
'operators' => [
'=' => NULL,
'&' => NULL,
]
],
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => false,
'blank_line_before_statement' => [
'statements' => [
// 'case',
'continue',
'declare',
'default',
'do',
'exit',
'for',
'foreach',
'goto',
'return',
'switch',
'throw',
'try',
'while',
'yield',
'yield_from',
],
],
// 'braces' => [
// 'allow_single_line_anonymous_class_with_empty_body' => true,
// 'allow_single_line_closure' => true,
// 'position_after_anonymous_constructs' => 'same',
// 'position_after_control_structures' => 'next',
// 'position_after_functions_and_oop_constructs' => 'next',
// ],
'cast_spaces' => ['space' => 'single'],
'class_attributes_separation' => [
'elements' => [
'const' => 'none',
'property' => 'none',
'method' => 'one',
'trait_import' => 'none',
],
],
'class_definition' => [
'multi_line_extends_each_single_line' => true,
'single_item_single_line' => true,
'single_line' => true,
'space_before_parenthesis' => true,
],
'class_reference_name_casing' => true,
'clean_namespace' => true,
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'combine_nested_dirname' => true,
'comment_to_phpdoc' => [
'ignored_tags' => [
'todo',
'codeCoverageIgnore',
'codeCoverageIgnoreStart',
'codeCoverageIgnoreEnd',
'phpstan-ignore-line',
'phpstan-ignore-next-line',
],
],
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'constant_case' => ['case' => 'upper'],
'control_structure_braces' => true,
'control_structure_continuation_position' => ['position' => 'next_line'],
'curly_braces_position' => [
'allow_single_line_anonymous_functions' => true,
'allow_single_line_empty_anonymous_classes' => true,
'anonymous_functions_opening_brace' => 'same_line',
'classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
'control_structures_opening_brace' => 'next_line_unless_newline_at_signature_end',
'functions_opening_brace' => 'next_line_unless_newline_at_signature_end',
],
'date_time_immutable' => false,
'declare_equal_normalize' => ['space' => 'none'],
'declare_parentheses' => true,
'declare_strict_types' => true,
'dir_constant' => true,
'doctrine_annotation_array_assignment' => false,
'doctrine_annotation_braces' => false,
'doctrine_annotation_indentation' => false,
'doctrine_annotation_spaces' => false,
'echo_tag_syntax' => [
'format' => 'short',
'long_function' => 'echo',
'shorten_simple_statements_only' => false,
],
'elseif' => false,
'empty_loop_body' => ['style' => 'braces'],
'empty_loop_condition' => ['style' => 'while'],
'encoding' => true,
'error_suppression' => [
'mute_deprecation_error' => true,
'noise_remaining_usages' => false,
'noise_remaining_usages_exclude' => [],
],
'escape_implicit_backslashes' => [
'double_quoted' => false,
'heredoc_syntax' => false,
'single_quoted' => false,
],
'explicit_indirect_variable' => false,
'explicit_string_variable' => false,
'final_class' => false,
'final_internal_class' => [
'annotation_exclude' => ['@no-final'],
'annotation_include' => ['@internal'],
'consider_absent_docblock_as_internal_class' => false,
],
'final_public_method_for_abstract_class' => false,
'fopen_flag_order' => true,
'fopen_flags' => ['b_mode' => true],
'full_opening_tag' => true,
'fully_qualified_strict_types' => true,
'function_declaration' => ['closure_function_spacing' => 'one'],
'function_to_constant' => [
'functions' => [
'get_called_class',
'get_class',
'get_class_this',
'php_sapi_name',
'phpversion',
'pi',
],
],
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => false,
'general_phpdoc_tag_rename' => false,
'get_class_to_class_keyword' => false,
'global_namespace_import' => [
'import_constants' => true,
'import_functions' => true,
'import_classes' => true,
],
'group_import' => true,
'header_comment' => false, // false by default
// 'heredoc_indentation' => ['indentation' => 'start_plus_one'],
'heredoc_to_nowdoc' => true,
'implode_call' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'indentation_type' => true,
'integer_literal_case' => true,
'is_null' => true,
'lambda_not_used_import' => true,
'line_ending' => true,
'linebreak_after_opening_tag' => false,
'list_syntax' => ['syntax' => 'short'],
'logical_operators' => true,
'lowercase_cast' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'mb_str_functions' => false,
'method_argument_space' => [
'after_heredoc' => false,
'keep_multiple_spaces_after_comma' => false,
'on_multiline' => 'ensure_fully_multiline',
],
'method_chaining_indentation' => true,
'modernize_strpos' => false, // requires 8.0+
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
'native_constant_invocation' => false,
'native_function_casing' => true,
'native_function_invocation' => false,
'native_function_type_declaration_casing' => true,
'new_with_braces' => true,
'no_alias_functions' => ['sets' => ['@all']],
'no_alias_language_construct_call' => true,
'no_alternative_syntax' => ['fix_non_monolithic_code' => false],
'no_binary_string' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_blank_lines_before_namespace' => false, // conflicts with `single_blank_line_before_namespace`
'no_break_comment' => ['comment_text' => 'no break'],
'no_closing_tag' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => ['tokens' => ['extra']],
'no_homoglyph_names' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ['use' => 'echo'],
'no_multiline_whitespace_around_double_arrow' => true,
'no_null_property_initialization' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_space_around_double_colon' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => ['positions' => ['inside', 'outside']],
'no_spaces_inside_parenthesis' => true,
'no_superfluous_elseif' => true,
'no_superfluous_phpdoc_tags' => [
'allow_mixed' => true,
'allow_unused_params' => true,
'remove_inheritdoc' => false,
],
'no_trailing_comma_in_singleline' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_trailing_whitespace_in_string' => true,
'no_unneeded_control_parentheses' => [
'statements' => [
'break',
'clone',
'continue',
'echo_print',
'return',
'switch_case',
'yield',
],
],
'no_unneeded_curly_braces' => ['namespaces' => true],
'no_unneeded_final_method' => ['private_methods' => true],
'no_unneeded_import_alias' => true,
'no_unreachable_default_argument_value' => true,
'no_unset_cast' => true,
'no_unset_on_property' => false,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'no_useless_sprintf' => true,
'no_whitespace_before_comma_in_array' => ['after_heredoc' => true],
'no_whitespace_in_blank_line' => true,
'non_printable_character' => ['use_escape_sequences_in_strings' => true],
'normalize_index_brace' => true,
'not_operator_with_space' => true,
'not_operator_with_successor_space' => true,
'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => true],
'object_operator_without_whitespace' => true,
'operator_linebreak' => ['only_booleans' => true, 'position' => 'beginning'],
'ordered_class_elements' => [
'order' => [
'use_trait',
'case',
'constant_public',
'constant_protected',
'constant_private',
'property_public',
'property_protected',
'property_private',
'construct',
'destruct',
'magic',
],
'sort_algorithm' => 'none',
],
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'function', 'const'],
],
'ordered_interfaces' => false,
'ordered_traits' => false,
'php_unit_construct' => [
'assertions' => [
'assertSame',
'assertEquals',
'assertNotEquals',
'assertNotSame',
],
],
'php_unit_dedicate_assert' => ['target' => 'newest'],
'php_unit_dedicate_assert_internal_type' => ['target' => 'newest'],
'php_unit_expectation' => ['target' => 'newest'],
'php_unit_fqcn_annotation' => true,
'php_unit_internal_class' => ['types' => ['final']],
'php_unit_method_casing' => ['case' => 'camel_case'],
'php_unit_mock' => ['target' => 'newest'],
'php_unit_mock_short_will_return' => true,
'php_unit_namespaced' => ['target' => 'newest'],
'php_unit_no_expectation_annotation' => [
'target' => 'newest',
'use_class_const' => true,
],
'php_unit_set_up_tear_down_visibility' => true,
'php_unit_size_class' => false,
// 'php_unit_strict' => [
// 'assertions' => [
// 'assertAttributeEquals',
// 'assertAttributeNotEquals',
// 'assertEquals',
// 'assertNotEquals',
// ],
// ],
'php_unit_test_annotation' => ['style' => 'prefix'],
'php_unit_test_case_static_method_calls' => [
'call_type' => 'this',
'methods' => [],
],
'php_unit_test_class_requires_covers' => false,
'phpdoc_add_missing_param_annotation' => ['only_untyped' => true],
'phpdoc_align' => [
'align' => 'left'
],
'phpdoc_annotation_without_dot' => false,
'phpdoc_indent' => true,
'phpdoc_inline_tag_normalizer' => [
'tags' => [
'example',
'id',
'internal',
'inheritdoc',
'inheritdocs',
'link',
'source',
'toc',
'tutorial',
],
],
'phpdoc_line_span' => [
'const' => 'multi',
'method' => 'multi',
'property' => 'multi',
],
'phpdoc_no_access' => true,
'phpdoc_no_empty_return' => false,
'phpdoc_no_package' => false,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => true,
'phpdoc_order_by_value' => [
'annotations' => [
'author',
'covers',
'coversNothing',
'dataProvider',
'depends',
'group',
'internal',
'method',
'property',
'property-read',
'property-write',
'requires',
'throws',
'uses',
],
],
'phpdoc_return_self_reference' => [
'replacements' => [
'this' => '$this',
'@this' => '$this',
'$self' => 'self',
'@self' => 'self',
'$static' => 'static',
'@static' => 'static',
],
],
'phpdoc_scalar' => [
'types' => [
'boolean',
'callback',
'double',
'integer',
'real',
'str',
],
],
'phpdoc_separation' => false,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => false,
'phpdoc_tag_casing' => ['tags' => ['inheritDoc']],
'phpdoc_tag_type' => ['tags' => ['inheritDoc' => 'inline']],
'phpdoc_to_comment' => false,
'phpdoc_to_param_type' => false,
'phpdoc_to_property_type' => false,
'phpdoc_to_return_type' => false,
'phpdoc_trim' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true,
'phpdoc_types' => ['groups' => ['simple', 'alias', 'meta']],
'phpdoc_types_order' => [
'null_adjustment' => 'always_last',
'sort_algorithm' => 'alpha',
],
'phpdoc_var_annotation_correct_order' => true,
'phpdoc_var_without_name' => true,
'pow_to_exponentiation' => true,
'protected_to_private' => true,
'psr_autoloading' => ['dir' => null],
'random_api_migration' => [
'replacements' => [
'getrandmax' => 'mt_getrandmax',
'rand' => 'mt_rand',
'srand' => 'mt_srand',
],
],
'regular_callable_call' => true,
'return_assignment' => true,
'return_type_declaration' => ['space_before' => 'none'],
'self_accessor' => false,
'self_static_accessor' => true,
'semicolon_after_instruction' => false,
'set_type_to_cast' => true,
'short_scalar_cast' => true,
'simple_to_complex_string_variable' => true,
'simplified_if_return' => true,
'simplified_null_return' => false,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => ['elements' => ['const', 'property']],
'single_import_per_statement' => false,
'single_line_after_imports' => true,
'single_line_comment_style' => ['comment_types' => ['asterisk', 'hash']],
'single_line_throw' => false,
'single_quote' => ['strings_containing_single_quote_chars' => false],
'single_space_around_construct' => [
'constructs_followed_by_a_single_space' => [
'abstract',
'as',
'attribute',
'break',
'case',
'catch',
'class',
'clone',
'comment',
'const',
'const_import',
'continue',
'do',
'echo',
'else',
'elseif',
'extends',
'final',
'finally',
'for',
'foreach',
'function',
'function_import',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'match',
'named_argument',
'new',
'open_tag_with_echo',
'php_doc',
'php_open',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'throw',
'trait',
'try',
'use',
'use_lambda',
'use_trait',
'var',
'while',
'yield',
'yield_from',
],
],
'single_trait_insert_per_statement' => true,
'space_after_semicolon' => ['remove_in_empty_for_expressions' => true],
'standardize_increment' => true,
'standardize_not_equals' => true,
'statement_indentation' => true,
'static_lambda' => true,
'strict_comparison' => true,
'strict_param' => true,
'string_length_to_empty' => true,
'string_line_ending' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'switch_continue_to_break' => true,
'ternary_operator_spaces' => true,
'ternary_to_elvis_operator' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline' => [
'after_heredoc' => true,
'elements' => ['arrays'],
],
'trim_array_spaces' => true,
'types_spaces' => ['space' => 'none'],
'unary_operator_spaces' => false,
'use_arrow_functions' => true,
'visibility_required' => ['elements' => ['const', 'method', 'property']],
'void_return' => false, // changes method signature
'whitespace_after_comma_in_array' => true,
'yoda_style' => [
'equal' => false,
'identical' => null,
'less_and_greater' => false,
'always_move_variable' => false,
],
]);

View File

@ -1,20 +1,17 @@
language: php
install:
- composer install
- composer install --ignore-platform-reqs
php:
- 5.4
- 5.5
- 5.6
- 7
- hhvm
- 8.0
- 8.1
- nightly
script:
- mkdir -p build/logs
- phpunit --coverage-clover=coverage.clover
- php vendor/bin/phpunit -c build
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
#matrix:
# allow_failures:
# - php: nightly

51
CHANGELOG.md Normal file
View File

@ -0,0 +1,51 @@
# Changelog
## Version 5.3
* Update PHP requirement to 8.2
## Version 5.2
* Updated PHP requirement to 8.1
* Updated to support PHP 8.2
* Improve Anilist <-> Kitsu mappings to be more reliable
## Version 5.1
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.
* Updated logging config so that much fewer, much smaller files are generated.
* Updated Kitsu integration to use GraphQL API, reducing a lot of internal complexity.
## Version 5
* Updated PHP requirement to 7.4
* Added anime watching history view
* Added manga reading history view
* Updated anime collection to have more media types
## Version 4.2
* Updated dependencies
* Updated PHP requirement to 7.3
* Added option to automatically set dark mode based on the OS setting
## Version 4.1
* Added optional dark theme
* Removed MAL integration, added Anilist Integration
* Now uses WebP cache images when the browser supports it
* Replaces JS minifier with pre-minified scripts (Removes the need for one caching folder, too)
* Updated console command to sync Kitsu and Anilist data (Kitsu can sync MAL, and MAL's API broke, so MAL sync was removed)
* Added page to update settings without having to edit config files
* Defaulted to secure (HTTPS) urls
* Updated Character pages to show voice actors
* Added People pages, showing which works they contributed to, and in what role
## Version 4
* Updated to use Kitsu API after discontinuation of Hummingbird
* Added streaming links to list entries from the Kitsu API
* Added simple integration with MyAnimeList, so an update can cross-post to both Kitsu and MyAnimeList (anime and manga)
* Added console command to sync Kitsu and MyAnimeList data
* Added character pages
## Version 3
* Converted user configuration to toml files
* Added a caching layer for api calls, which resets upon updates from the
app.
* Added a bulk thumbnail generator script
* Removed json file "cache" from the app folder

65
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,65 @@
pipeline {
agent none
stages {
stage('setup') {
agent any
steps {
sh 'curl -sS https://getcomposer.org/installer | php'
sh 'rm -rf ./vendor'
sh 'rm -f composer.lock'
sh 'php composer.phar install --ignore-platform-reqs'
}
}
stage('PHP 8.1') {
agent {
docker {
image 'php:8.1-cli-alpine'
args '-u root --privileged'
}
}
steps {
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('PHP 8.2') {
agent {
docker {
image 'php:8.2-cli-alpine'
args '-u root --privileged'
}
}
steps {
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('Latest PHP') {
agent {
docker {
image 'php:cli-alpine'
args '-u root --privileged'
}
}
steps {
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('Coverage') {
agent any
steps {
sh 'php composer.phar run-script coverage'
step([
$class: 'CloverPublisher',
cloverReportDir: '',
cloverReportFileName: 'build/logs/clover.xml',
])
junit 'build/logs/junit.xml'
}
}
}
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Timothy J Warren
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,10 +1,11 @@
# Hummingbird Anime Client
A self-hosted client that allows custom formatting of data from the hummingbird api
Update your anime/manga list on Kitsu.io and Anilist
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
[![Build Status](https://travis-ci.com/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.com/github/timw4mail/HummingBirdAnimeClient)
[![Build Status](https://jenkins.timshome.page/buildStatus/icon?job=timw4mail/HummingBirdAnimeClient/develop)](https://jenkins.timshome.page/job/timw4mail/job/HummingBirdAnimeClient/job/develop/)
[[Hosted Example](https://anime.timshomepage.net)]
[[Hosted Example](https://list.timshomepage.net)]
## Features
@ -13,43 +14,53 @@ A self-hosted client that allows custom formatting of data from the hummingbird
* Plan to Watch
* On Hold
* Dropped
* Completed
* All of the above
* Completed
* Combined View
* Manga List views (Each with list and cover views):
* Reading
* Plan to Read
* On Hold
* Dropped
* Completed
* All of the above
* Combined View
* Anime collection view (segmented by media type):
* Cover Images
* Table List
### Requirements
* PHP 5.4+
* PDO SQLite (For collection tab)
* GD
* PHP 8.2
* ext-dom (For editing the DOM)
* ext-gd (For caching images)
* ext-intl (For time localization)
* ext-json
* ext-mbstring
* ext-pdo
### Highly Recommended
* Redis or Memcached for caching
* PDO SQLite or PDO PostgreSQL (For collection tab)
### Installation
1. Install dependencies via composer: `composer install`
2. Change the `WHOSE` constant declaration in `index.php` to your name
3. Configure settings in `app/config/config.php` to your liking
1. Install via git, then install dependencies via composer: `composer install`
2. Duplicate `app/config/config.toml.example` file as `app/config/config.toml`
3. Configure settings in `app/config/config.toml` to your liking
4. Create the following directories if they don't exist, and make sure they are world writable
* app/cache
* public/images/manga
* app/config
* app/logs
* public/images/avatars
* public/images/anime
* public/js/cache
* public/images/characters
* public/images/manga
5. Make sure the `console` script is executable
6. Additional settings are on the settings page once you log in.
#### Anime Collection Additional Installation
* Run `php /vendor/bin/phinx migrate -e development` to create the database tables
* For importing anime:
1. Find the anime you are looking for on the hummingbird search api page: `https://hummingbird.me/api/v1/search/anime?query=`
2. Create an `import.json` file in the root of the app, with an array of objects from the search page that you want to import
3. Go to the anime collection tab, and the import will be run
### Server Setup
See the [wiki](https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient/wiki)
for more in-depth information

View File

@ -0,0 +1,60 @@
<?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
*/
use function Aviat\AnimeClient\loadConfig;
// ----------------------------------------------------------------------------
// Lower level configuration
//
// You shouldn't generally need to change anything below this line
// ----------------------------------------------------------------------------
$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",
// No config defaults
'kitsu_username' => 'timw4mail',
'whose_list' => 'Someone',
'cache' => [
'connection' => [],
'driver' => 'null',
],
'secure_urls' => TRUE,
// Routing defaults
'asset_path' => '/public',
'default_list' => 'anime', //anime|manga
'default_anime_list_path' => 'watching', // watching|plan_to_watch|on_hold|dropped|completed|all
'default_manga_list_path' => 'reading', // reading|plan_to_read|on_hold|dropped|completed|all
'default_view_type' => 'cover_view', // cover_view|list_view
// Template file path
'view_path' => "{$APP_DIR}/views",
// Cache paths
'data_cache_path' => "{$APP_DIR}/cache",
'img_cache_path' => "{$ROOT_DIR}/public/images",
// Included config files
'routes' => require 'routes.php',
]);

21
app/appConf/menus.toml Normal file
View File

@ -0,0 +1,21 @@
[anime_list]
route_prefix = ""
[anime_list.items]
watch_history = '/history/anime'
watching = '/anime/watching'
plan_to_watch = '/anime/plan_to_watch'
on_hold = '/anime/on_hold'
dropped = '/anime/dropped'
completed = '/anime/completed'
all = '/anime/all'
[manga_list]
route_prefix = ""
[manga_list.items]
reading_history = '/history/manga'
reading = '/manga/reading'
plan_to_read = '/manga/plan_to_read'
on_hold = '/manga/on_hold'
dropped = '/manga/dropped'
completed = '/manga/completed'
all = '/manga/all'

323
app/appConf/routes.php Normal file
View File

@ -0,0 +1,323 @@
<?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
*/
use const Aviat\AnimeClient\{
ALPHA_SLUG_PATTERN,
DEFAULT_CONTROLLER,
DEFAULT_CONTROLLER_METHOD,
SLUG_PATTERN,
NUM_PATTERN,
};
// -------------------------------------------------------------------------
// Routing Config
//
// Maps paths to controllers and methods
// -------------------------------------------------------------------------
$base_routes = [
// ---------------------------------------------------------------------
// AJAX Routes
// ---------------------------------------------------------------------
'cache_purge' => [
'path' => '/cache_purge',
'action' => 'clearCache',
],
'heartbeat' => [
'path' => '/heartbeat',
'action' => 'heartbeat',
],
// ---------------------------------------------------------------------
// Anime List Routes
// ---------------------------------------------------------------------
'anime.add.get' => [
'path' => '/anime/add',
'action' => 'addForm',
],
'anime.add.post' => [
'path' => '/anime/add',
'action' => 'add',
'verb' => 'post',
],
'anime.random' => [
'path' => '/anime/details/random',
'action' => 'random',
],
'anime.details' => [
'path' => '/anime/details/{id}',
'action' => 'details',
'tokens' => [
'id' => SLUG_PATTERN,
],
],
'anime.delete' => [
'path' => '/anime/delete',
'action' => 'delete',
'verb' => 'post',
],
// ---------------------------------------------------------------------
// Manga Routes
// ---------------------------------------------------------------------
'manga.search' => [
'path' => '/manga/search',
'action' => 'search',
],
'manga.add.get' => [
'path' => '/manga/add',
'action' => 'addForm',
],
'manga.add.post' => [
'path' => '/manga/add',
'action' => 'add',
'verb' => 'post',
],
'manga.delete' => [
'path' => '/manga/delete',
'action' => 'delete',
'verb' => 'post',
],
'manga.random' => [
'path' => '/manga/details/random',
'action' => 'random',
],
'manga.details' => [
'path' => '/manga/details/{id}',
'action' => 'details',
'tokens' => [
'id' => SLUG_PATTERN,
],
],
// ---------------------------------------------------------------------
// Anime Collection Routes
// ---------------------------------------------------------------------
'anime.collection.search' => [
'path' => '/anime-collection/search',
'action' => 'search',
],
'anime.collection.add.get' => [
'path' => '/anime-collection/add',
'action' => 'form',
],
'anime.collection.edit.get' => [
'path' => '/anime-collection/edit/{id}',
'action' => 'form',
'tokens' => [
'id' => NUM_PATTERN,
],
],
'anime.collection.add.post' => [
'path' => '/anime-collection/add',
'action' => 'add',
'verb' => 'post',
],
'anime.collection.edit.post' => [
'path' => '/anime-collection/edit',
'action' => 'edit',
'verb' => 'post',
],
'anime.collection.view' => [
'path' => '/anime-collection/view{/view}',
'action' => 'view',
'tokens' => [
'view' => ALPHA_SLUG_PATTERN,
],
],
'anime.collection.delete' => [
'path' => '/anime-collection/delete',
'action' => 'delete',
'verb' => 'post',
],
'anime.collection.redirect' => [
'path' => '/anime-collection',
],
'anime.collection.redirect2' => [
'path' => '/anime-collection/',
],
// ---------------------------------------------------------------------
// Manga Collection Routes
// ---------------------------------------------------------------------
'manga.collection.search' => [
'path' => '/manga-collection/search',
'action' => 'search',
],
'manga.collection.add.get' => [
'path' => '/manga-collection/add',
'action' => 'form',
],
'manga.collection.edit.get' => [
'path' => '/manga-collection/edit/{id}',
'action' => 'form',
'tokens' => [
'id' => NUM_PATTERN,
],
],
'manga.collection.add.post' => [
'path' => '/manga-collection/add',
'action' => 'add',
'verb' => 'post',
],
'manga.collection.edit.post' => [
'path' => '/manga-collection/edit',
'action' => 'edit',
'verb' => 'post',
],
'manga.collection.view' => [
'path' => '/manga-collection/view{/view}',
'tokens' => [
'view' => ALPHA_SLUG_PATTERN,
],
],
'manga.collection.delete' => [
'path' => '/manga-collection/delete',
'action' => 'delete',
'verb' => 'post',
],
// ---------------------------------------------------------------------
// Other Routes
// ---------------------------------------------------------------------
'character' => [
'path' => '/character/{slug}',
'tokens' => [
'slug' => SLUG_PATTERN,
],
],
'person' => [
'path' => '/people/{slug}',
'tokens' => [
'slug' => SLUG_PATTERN,
],
],
'default_user_info' => [
'path' => '/me',
'action' => 'me',
'controller' => 'user',
],
'user_info' => [
'path' => '/user/{username}',
'controller' => 'user',
'action' => 'about',
'tokens' => [
'username' => '.*?',
],
],
// ---------------------------------------------------------------------
// Default / Shared routes
// ---------------------------------------------------------------------
'anilist-redirect' => [
'path' => '/anilist-redirect',
'action' => 'anilistRedirect',
'controller' => 'settings',
],
'anilist-callback' => [
'path' => '/anilist-oauth',
'action' => 'anilistCallback',
'controller' => 'settings',
],
'image_proxy' => [
'path' => '/public/images/{type}/{file}',
'action' => 'cache',
'controller' => 'images',
'tokens' => [
'type' => SLUG_PATTERN,
'file' => '[a-z0-9\-]+\.[a-z]{3,4}',
],
],
'settings' => [
'path' => '/settings',
],
'settings-post' => [
'path' => '/settings/update',
'action' => 'update',
'verb' => 'post',
],
'login' => [
'path' => '/login',
'action' => 'login',
],
'login.post' => [
'path' => '/login',
'action' => 'loginAction',
'verb' => 'post',
],
'logout' => [
'path' => '/logout',
'action' => 'logout',
],
'history' => [
'controller' => 'history',
'path' => '/history/{type}',
'tokens' => [
'type' => SLUG_PATTERN,
],
],
'increment' => [
'path' => '/{controller}/increment',
'action' => 'increment',
'verb' => 'post',
'tokens' => [
'controller' => ALPHA_SLUG_PATTERN,
],
],
'update' => [
'path' => '/{controller}/update',
'action' => 'update',
'verb' => 'post',
'tokens' => [
'controller' => ALPHA_SLUG_PATTERN,
],
],
'update.post' => [
'path' => '/{controller}/update_form',
'action' => 'formUpdate',
'verb' => 'post',
'tokens' => [
'controller' => ALPHA_SLUG_PATTERN,
],
],
'edit' => [
'path' => '/{controller}/edit/{id}/{status}',
'action' => 'edit',
'tokens' => [
'id' => SLUG_PATTERN,
'status' => SLUG_PATTERN,
],
],
'list' => [
'path' => '/{controller}/{status}{/view}',
'tokens' => [
'status' => ALPHA_SLUG_PATTERN,
'view' => ALPHA_SLUG_PATTERN,
],
],
'index_redirect' => [
'path' => '/',
'action' => 'redirectToDefaultRoute',
],
];
$defaultMap = [
'action' => DEFAULT_CONTROLLER_METHOD,
'controller' => DEFAULT_CONTROLLER,
'params' => [],
'verb' => 'get',
];
$routes = [];
foreach ($base_routes as $name => $route)
{
$routes[$name] = array_merge($defaultMap, $route);
}
return $routes;

View File

@ -1,81 +0,0 @@
<?php
/**
* Base API Model
*/
namespace AnimeClient;
use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar;
/**
* Base model for api interaction
*/
class BaseApiModel extends BaseModel {
/**
* Base url for making api requests
* @var string
*/
protected $base_url = '';
/**
* The Guzzle http client object
* @var object
*/
protected $client;
/**
* Cookie jar object for api requests
* @var object
*/
protected $cookieJar;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->cookieJar = new CookieJar();
$this->client = new Client([
'base_url' => $this->base_url,
'defaults' => [
'cookies' => $this->cookieJar,
'headers' => [
'User-Agent' => $_SERVER['HTTP_USER_AGENT'],
'Accept-Encoding' => 'application/json'
],
'timeout' => 5,
'connect_timeout' => 5
]
]);
}
/**
* Attempt login via the api
*
* @codeCoverageIgnore
* @param string $username
* @param string $password
* @return bool
*/
public function authenticate($username, $password)
{
$result = $this->client->post('https://hummingbird.me/api/v1/users/authenticate', [
'body' => [
'username' => $username,
'password' => $password
]
]);
if ($result->getStatusCode() === 201)
{
$_SESSION['hummingbird_anime_token'] = $result->json();
return TRUE;
}
return FALSE;
}
}
// End of BaseApiModel.php

View File

@ -1,254 +0,0 @@
<?php
/**
* Base Controller
*/
namespace AnimeClient;
use Aura\Web\WebFactory;
/**
* Base class for controllers, defines output methods
*/
class BaseController {
/**
* The global configuration object
* @var object $config
*/
protected $config;
/**
* Request object
* @var object $request
*/
protected $request;
/**
* Response object
* @var object $response
*/
protected $response;
/**
* The api model for the current controller
* @var object
*/
protected $model;
/**
* Common data to be sent to views
* @var array
*/
protected $base_data = [];
/**
* Constructor
*/
public function __construct(Config $config, Array $web)
{
$this->config = $config;
list($request, $response) = $web;
$this->request = $request;
$this->response = $response;
}
public function __destruct()
{
$this->output();
}
/**
* Get the string output of a partial template
*
* @param string $template
* @param array|object $data
* @return string
*/
public function load_partial($template, $data=[])
{
if (isset($this->base_data))
{
$data = array_merge($this->base_data, $data);
}
global $router, $defaultHandler;
$route = $router->get_route();
$data['route_path'] = ($route) ? $router->get_route()->path : "";
$defaultHandler->addDataTable('Template Data', $data);
$template_path = _dir(APP_DIR, 'views', "{$template}.php");
if ( ! is_file($template_path))
{
throw new Exception("Invalid template : {$path}");
}
ob_start();
extract($data);
include _dir(APP_DIR, 'views', 'header.php');
include $template_path;
include _dir(APP_DIR, 'views', 'footer.php');
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
}
/**
* Output a template to HTML, using the provided data
*
* @param string $template
* @param array|object $data
* @return void
*/
public function outputHTML($template, $data=[])
{
$buffer = $this->load_partial($template, $data);
$this->response->content->setType('text/html');
$this->response->content->set($buffer);
}
/**
* Output json with the proper content type
*
* @param mixed $data
* @return void
*/
public function outputJSON($data)
{
if ( ! is_string($data))
{
$data = json_encode($data);
}
$this->response->content->setType('application/json');
$this->response->content->set($data);
}
/**
* Redirect to the selected page
*
* @param string $url
* @param int $code
* @return void
*/
public function redirect($url, $code, $type="anime")
{
$url = full_url($url, $type);
$codes = [
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other'
];
header("HTTP/1.1 {$code} {$codes[$code]}");
header("Location: {$url}");
}
/**
* Add a message box to the page
*
* @param string $type
* @param string $message
* @return string
*/
public function show_message($type, $message)
{
return $this->load_partial('message', [
'stat_class' => $type,
'message' => $message
]);
}
/**
* Clear the api session
*
* @return void
*/
public function logout()
{
session_destroy();
$this->response->redirect->seeOther(full_url(''));
}
/**
* Show the login form
*
* @param string $status
* @return void
*/
public function login($status="")
{
$message = "";
if ($status != "")
{
$message = $this->show_message('error', $status);
}
$this->outputHTML('login', [
'title' => 'Api login',
'message' => $message
]);
}
/**
* Attempt to log in with the api
*
* @return void
*/
public function login_action()
{
if (
$this->model->authenticate(
$this->config->hummingbird_username,
$this->request->post->get('password')
)
)
{
$this->response->redirect->afterPost(full_url('', $this->base_data['url_type']));
return;
}
$this->login("Invalid username or password.");
}
/**
* Send the appropriate response
*
* @return void
*/
private function output()
{
// send status
@header($this->response->status->get(), true, $this->response->status->getCode());
// headers
foreach($this->response->headers->get() as $label => $value)
{
@header("{$label}: {$value}");
}
// cookies
foreach($this->response->cookies->get() as $name => $cookie)
{
@setcookie(
$name,
$cookie['value'],
$cookie['expire'],
$cookie['path'],
$cookie['domain'],
$cookie['secure'],
$cookie['httponly']
);
}
// send the actual response
echo $this->response->content->get();
}
}
// End of BaseController.php

View File

@ -1,32 +0,0 @@
<?php
/**
* Base DB model
*/
namespace AnimeClient;
/**
* Base model for database interaction
*/
class BaseDBModel extends BaseModel {
/**
* The query builder object
* @var object $db
*/
protected $db;
/**
* The database connection information array
* @var array $db_config
*/
protected $db_config;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->db_config = $this->config->database;
}
}
// End of BaseDBModel.php

View File

@ -1,106 +0,0 @@
<?php
/**
* Base for base models
*/
namespace AnimeClient;
use abeautifulsite\SimpleImage;
/**
* Common base for all Models
*/
class BaseModel {
/**
* The global configuration object
* @var object $config
*/
protected $config;
/**
* Constructor
*/
public function __construct()
{
global $config;
$this->config = $config;
}
/**
* Get the path of the cached version of the image. Create the cached image
* if the file does not already exist
*
* @codeCoverageIgnore
* @param string $api_path - The original image url
* @param string $series_slug - The part of the url with the series name, becomes the image name
* @param string $type - Anime or Manga, controls cache path
* @return string - the frontend path for the cached image
*/
public function get_cached_image($api_path, $series_slug, $type="anime")
{
$api_path = str_replace("jjpg", "jpg", $api_path);
$path_parts = explode('?', basename($api_path));
$path = current($path_parts);
$ext_parts = explode('.', $path);
$ext = end($ext_parts);
// Workaround for some broken extensions
if ($ext == "jjpg") $ext = "jpg";
// Failsafe for weird urls
if (strlen($ext) > 3) return $api_path;
$cached_image = "{$series_slug}.{$ext}";
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";
// Cache the file if it doesn't already exist
if ( ! file_exists($cached_path))
{
if (ini_get('allow_url_fopen'))
{
copy($api_path, $cached_path);
}
elseif (function_exists('curl_init'))
{
$ch = curl_init($api_path);
$fp = fopen($cached_path, 'wb');
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_HEADER => 0
]);
curl_exec($ch);
curl_close($ch);
fclose($ch);
}
else
{
throw new Exception("Couldn't cache images because they couldn't be downloaded.");
}
// Resize the image
if ($type == 'anime')
{
$resize_width = 220;
$resize_height = 319;
$this->_resize($cached_path, $resize_width, $resize_height);
}
}
return "/public/images/{$type}/{$cached_image}";
}
/**
* Resize an image
*
* @codeCoverageIgnore
* @param string $path
* @param string $width
* @param string $height
*/
private function _resize($path, $width, $height)
{
$img = new SimpleImage($path);
$img->resize($width,$height)->save();
}
}
// End of BaseModel.php

View File

@ -1,56 +0,0 @@
<?php
namespace AnimeClient;
/**
* Wrapper for configuration values
*/
class Config {
/**
* Config object
*
* @var array
*/
protected $config = [];
/**
* Constructor
*
* @param array $config_files
*/
public function __construct(Array $config_files=[])
{
// @codeCoverageIgnoreStart
if (empty($config_files))
{
require_once _dir(CONF_DIR, 'config.php'); // $config
require_once _dir(CONF_DIR, 'base_config.php'); // $base_config
}
else // @codeCoverageIgnoreEnd
{
$config = $config_files['config'];
$base_config = $config_files['base_config'];
}
$this->config = array_merge($config, $base_config);
}
/**
* Getter for config values
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (isset($this->config[$key]))
{
return $this->config[$key];
}
return NULL;
}
}
// End of config.php

View File

@ -1,175 +0,0 @@
<?php
/**
* Routing logic
*/
namespace AnimeClient;
/**
* Basic routing/ dispatch
*/
class Router {
/**
* The route-matching object
* @var object $router
*/
protected $router;
/**
* The global configuration object
* @var object $config
*/
protected $config;
/**
* Array containing request and response objects
* @var array $web
*/
protected $web;
/**
* Constructor
*
* @param
*/
public function __construct(Config $config, \Aura\Router\Router $router, \Aura\Web\Request $request, \Aura\Web\Response $response)
{
$this->config = $config;
$this->router = $router;
$this->web = [$request, $response];
$this->_setup_routes();
}
/**
* Get the current route object, if one matches
*
* @return object
*/
public function get_route()
{
global $defaultHandler;
$raw_route = $_SERVER['REQUEST_URI'];
$route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route);
$route_path = "/" . trim($route_path, '/');
$defaultHandler->addDataTable('Route Info', [
'route_path' => $route_path
]);
$route = $this->router->match($route_path, $_SERVER);
return $route;
}
/**
* Handle the current route
*
* @param [object] $route
* @return void
*/
public function dispatch($route = NULL)
{
global $defaultHandler;
if (is_null($route))
{
$route = $this->get_route();
}
if ( ! $route)
{
$failure = $this->router->getFailedRoute();
$defaultHandler->addDataTable('failed_route', (array)$failure);
$controller_name = 'BaseController';
$action_method = 'outputHTML';
$params = [
'template' => '404',
'data' => [
'title' => 'Page Not Found'
]
];
}
else
{
list($controller_name, $action_method) = $route->params['action'];
$params = (isset($route->params['params'])) ? $route->params['params'] : [];
if ( ! empty($route->tokens))
{
foreach($route->tokens as $key => $v)
{
if (array_key_exists($key, $route->params))
{
$params[$key] = $route->params[$key];
}
}
}
}
$controller = new $controller_name($this->config, $this->web);
// Run the appropriate controller method
$defaultHandler->addDataTable('controller_args', $params);
call_user_func_array([$controller, $action_method], $params);
}
/**
* Select controller based on the current url, and apply its relevent routes
*
* @return void
*/
private function _setup_routes()
{
$route_map = [
'anime' => '\\AnimeClient\\AnimeController',
'manga' => '\\AnimeClient\\MangaController',
];
$route_type = "anime";
if ($this->config->manga_host !== "" && strpos($_SERVER['HTTP_HOST'], $this->config->manga_host) !== FALSE)
{
$route_type = "manga";
}
else if ($this->config->manga_path !== "" && strpos($_SERVER['REQUEST_URI'], $this->config->manga_path) !== FALSE)
{
$route_type = "manga";
}
$routes = $this->config->routes;
// Add routes
foreach(['common', $route_type] as $key)
{
foreach($routes[$key] as $name => &$route)
{
$path = $route['path'];
unset($route['path']);
// Prepend the controller to the route parameters
array_unshift($route['action'], $route_map[$route_type]);
// Select the appropriate router method based on the http verb
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
if ( ! array_key_exists('tokens', $route))
{
$this->router->$add($name, $path)->addValues($route);
}
else
{
$tokens = $route['tokens'];
unset($route['tokens']);
$this->router->$add($name, $path)
->addValues($route)
->addTokens($tokens);
}
}
}
}
}
// End of Router.php

View File

@ -1,134 +0,0 @@
<?php
/**
* Global functions
*/
/**
* Check if the user is currently logged in
*
* @return bool
*/
function is_logged_in()
{
return array_key_exists('hummingbird_anime_token', $_SESSION);
}
/**
* HTML selection helper function
*
* @param string $a - First item to compare
* @param string $b - Second item to compare
* @return string
*/
function is_selected($a, $b)
{
return ($a === $b) ? 'selected' : '';
}
/**
* Inverse of selected helper function
*
* @param string $a - First item to compare
* @param string $b - Second item to compare
* @return string
*/
function is_not_selected($a, $b)
{
return ($a !== $b) ? 'selected' : '';
}
/**
* Get the base url for css/js/images
*
* @return string
*/
function asset_url(/*...*/)
{
global $config;
$args = func_get_args();
$base_url = rtrim($config->asset_path, '/');
array_unshift($args, $base_url);
return implode("/", $args);
}
/**
* Get the base url from the config
*
* @param string $type - (optional) The controller
# @param object $config - (optional) Config
* @return string
*/
function base_url($type="anime", $config=NULL)
{
if (is_null($config)) global $config;
$config_path = trim($config->{"{$type}_path"}, "/");
$config_host = $config->{"{$type}_host"};
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
$path = ($config_path !== '') ? $config_path : "";
return implode("/", ['/', $host, $path]);
}
/**
* Generate full url path from the route path based on config
*
* @param string $path - (optional) The route path
* @param string $type - (optional) The controller (anime or manga), defaults to anime
# @param object $config - (optional) Config
* @return string
*/
function full_url($path="", $type="anime", $config=NULL)
{
if (is_null($config)) global $config;
$config_path = trim($config->{"{$type}_path"}, "/");
$config_host = $config->{"{$type}_host"};
$config_default_route = $config->{"default_{$type}_path"};
// Remove beginning/trailing slashes
$config_path = trim($config_path, '/');
$path = trim($path, '/');
// Remove any optional parameters from the route
$path = preg_replace('`{/.*?}`i', '', $path);
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
// Set the default view
if ($path === '')
{
$path .= trim($config_default_route, '/');
if ($config->default_to_list_view) $path .= '/list';
}
// Set an leading folder
if ($config_path !== '')
{
$path = "{$config_path}/{$path}";
}
return "//{$host}/{$path}";
}
/**
* Get the last segment of the current url
*
* @return string
*/
function last_segment()
{
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', $path);
return end($segments);
}
// End of functions.php

View File

@ -1,43 +0,0 @@
<?php
/**
* Functions that need to be included before config
*/
/**
* Joins paths together. Variadic to take an
* arbitrary number of arguments
*
* @return string
*/
function _dir()
{
return implode(DIRECTORY_SEPARATOR, func_get_args());
}
/**
* Set up autoloaders
*
* @codeCoverageIgnore
* @return void
*/
function _setup_autoloaders()
{
require _dir(ROOT_DIR, '/vendor/autoload.php');
spl_autoload_register(function ($class) {
$class_parts = explode('\\', $class);
$class = end($class_parts);
$dirs = ["base", "controllers", "models"];
foreach($dirs as $dir)
{
$file = _dir(APP_DIR, $dir, "{$class}.php");
if (file_exists($file))
{
require_once $file;
return;
}
}
});
}

View File

@ -1,57 +1,199 @@
<?php
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 8.1
*
* @copyright 2015 - 2023 Timothy J. Warren <tim@timshome.page>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace AnimeClient;
namespace Aviat\AnimeClient;
use \Whoops\Handler\PrettyPageHandler;
use \Whoops\Handler\JsonResponseHandler;
use \Aura\Web\WebFactory;
use \Aura\Router\RouterFactory;
use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar;
use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\{Anilist, Kitsu};
use Aviat\AnimeClient\{Component, Model};
use Aviat\Banker\Teller;
use Aviat\Ion\Config;
use Aviat\Ion\Di\{Container, ContainerInterface};
use Laminas\Diactoros\ServerRequestFactory;
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Psr\SimpleCache\CacheInterface;
use function Aviat\Ion\_dir;
if ( ! defined('HB_APP_DIR'))
{
define('HB_APP_DIR', __DIR__);
define('ROOT_DIR', dirname(HB_APP_DIR));
define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates'));
}
// -----------------------------------------------------------------------------
// Setup error handling
// Setup DI container
// -----------------------------------------------------------------------------
$whoops = new \Whoops\Run();
return static function (array $configArray = []): Container {
$container = new Container();
// Set up default handler for general errors
$defaultHandler = new PrettyPageHandler();
$whoops->pushHandler($defaultHandler);
// -------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------
$LOG_DIR = _dir(HB_APP_DIR, 'logs');
// Set up json handler for ajax errors
$jsonHandler = new JsonResponseHandler();
$jsonHandler->onlyForAjaxRequests(true);
$whoops->pushHandler($jsonHandler);
$appLogger = new Logger('animeclient');
$appLogger->pushHandler(new RotatingFileHandler(_dir($LOG_DIR, 'app.log'), 2, Logger::WARNING));
$container->setLogger($appLogger);
$whoops->register();
foreach (['anilist-request', 'kitsu-request', 'kitsu-graphql'] as $channel)
{
$logger = new Logger($channel);
$handler = new RotatingFileHandler(_dir($LOG_DIR, "{$channel}.log"), 2, Logger::WARNING);
$handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler);
// -----------------------------------------------------------------------------
// Injected Objects
// -----------------------------------------------------------------------------
$container->setLogger($logger, $channel);
}
// Create Config Object
$config = new Config();
require _dir(BASE_DIR, '/functions.php');
// -------------------------------------------------------------------------
// Injected Objects
// -------------------------------------------------------------------------
// Create Aura Router Object
$router_factory = new RouterFactory();
$aura_router = $router_factory->newInstance();
// Create Config Object
$container->set('config', static fn () => new Config($configArray));
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => $_GET,
'_POST' => $_POST,
'_COOKIE' => $_COOKIE,
'_SERVER' => $_SERVER,
'_FILES' => $_FILES
]);
$request = $web_factory->newRequest();
$response = $web_factory->newResponse();
// Create Cache Object
$container->set('cache', static function (ContainerInterface $container): CacheInterface {
$logger = $container->getLogger();
$config = $container->get('config')->get('cache');
// -----------------------------------------------------------------------------
// Router
// -----------------------------------------------------------------------------
$router = new Router($config, $aura_router, $request, $response);
$router->dispatch();
return new Teller($config, $logger);
});
// End of bootstrap.php
// Create Aura Router Object
$container->set('aura-router', static fn () => new RouterContainer());
// Create Html helpers
$container->set('html-helper', static function (ContainerInterface $container) {
$htmlHelper = (new HelperLocatorFactory())->newInstance();
$helpers = [
'menu' => Helper\Menu::class,
'field' => Helper\Form::class,
'picture' => Helper\Picture::class,
];
foreach ($helpers as $name => $class)
{
$htmlHelper->set($name, static function () use ($class, $container) {
$helper = new $class();
$helper->setContainer($container);
return $helper;
});
}
return $htmlHelper;
});
// Create Component helpers
$container->set('component-helper', static function (ContainerInterface $container) {
$helper = (new HelperLocatorFactory())->newInstance();
$components = [
'animeCover' => Component\AnimeCover::class,
'mangaCover' => Component\MangaCover::class,
'character' => Component\Character::class,
'media' => Component\Media::class,
'tabs' => Component\Tabs::class,
'verticalTabs' => Component\VerticalTabs::class,
];
foreach ($components as $name => $componentClass)
{
$helper->set($name, static function () use ($container, $componentClass) {
$helper = new $componentClass();
$helper->setContainer($container);
return $helper;
});
}
return $helper;
});
// Create Request Object
$container->set('request', static fn () => ServerRequestFactory::fromGlobals(
$GLOBALS['_SERVER'],
$_GET,
$_POST,
$_COOKIE,
$_FILES
));
// Create session Object
$container->set('session', static fn () => (new SessionFactory())->newInstance($_COOKIE));
// Models
$container->set('kitsu-model', static function (ContainerInterface $container): Kitsu\Model {
$requestBuilder = new Kitsu\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
$listItem = new Kitsu\ListItem();
$listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new Kitsu\Model($listItem);
$model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
$cache = $container->get('cache');
$model->setCache($cache);
return $model;
});
$container->set('anilist-model', static function (ContainerInterface $container): Anilist\Model {
$requestBuilder = new Anilist\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('anilist-request'));
$listItem = new Anilist\ListItem();
$listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new Anilist\Model($listItem);
$model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
return $model;
});
$container->set('settings-model', static function ($container) {
$model = new Model\Settings($container->get('config'));
$model->setContainer($container);
return $model;
});
$container->setSimple('anime-model', Model\Anime::class);
$container->setSimple('manga-model', Model\Manga::class);
$container->setSimple('anime-collection-model', Model\AnimeCollection::class);
// Miscellaneous Classes
$container->setSimple('util', Util::class);
$container->setSimple('auth', Kitsu\Auth::class);
$container->setSimple('url-generator', UrlGenerator::class);
$container->setSimple('render-helper', RenderHelper::class);
// -------------------------------------------------------------------------
// Dispatcher
// -------------------------------------------------------------------------
$container->setSimple('dispatcher', Dispatcher::class);
return $container;
};
// End of bootstrap.php

View File

@ -0,0 +1,6 @@
################################################################################
# Anilist API #
################################################################################
client_id = "your_client_id"
client_secret = "your_client_secret"
username = "user123"

View File

@ -1,15 +0,0 @@
<?php
// ----------------------------------------------------------------------------
// Lower level configuration
//
// You shouldn't generally need to change anything below this line
// ----------------------------------------------------------------------------
$base_config = [
// Cache paths
'data_cache_path' => _dir(APP_DIR, 'cache'),
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
// Included config files
'routes' => require _dir(CONF_DIR, 'routes.php'),
'database' => require _dir(CONF_DIR, 'database.php'),
];

View File

@ -0,0 +1,22 @@
################################################################################
# Cache Setup #
################################################################################
# See https://git.timshomepage.net/aviat/banker for more information
# Available drivers are memcached, redis or null
# Null cache driver means no caching
driver = "redis"
[connection]
# Host or socket to connect to
host = "127.0.0.1"
# Connection port
#port = 6379
# Connection password
#password = ""
# Database number
database = 2

View File

@ -1,40 +0,0 @@
<?php
$config = [
// ----------------------------------------------------------------------------
// Username for anime and manga lists
// ----------------------------------------------------------------------------
'hummingbird_username' => 'timw4mail',
// ----------------------------------------------------------------------------
// General config
// ----------------------------------------------------------------------------
// do you wish to show the anime collection tab?
'show_anime_collection' => TRUE,
// path to public directory
'asset_path' => '//' . $_SERVER['HTTP_HOST'] . '/public',
// path to public directory on the server
'asset_dir' => __DIR__ . '/../../public',
// ----------------------------------------------------------------------------
// Routing
//
// Route by path, or route by domain. To route by path, set the _host suffixed
// options to an empty string. To route by host, set the _path suffixed options
// to an empty string
// ----------------------------------------------------------------------------
'anime_host' => 'anime.timshomepage.net',
'manga_host' => 'manga.timshomepage.net',
'anime_path' => '',
'manga_path' => '',
// Default pages for anime/manga
'default_anime_path' => '/watching',
'default_manga_path' => '/all',
// Default to list view?
'default_to_list_view' => FALSE,
];

View File

@ -0,0 +1,39 @@
################################################################################
# Main User Configuration #
################################################################################
# Username for anime and manga lists
kitsu_username = "johnsmith"
# Whose list is it?
whose_list = "Someone"
# do you wish to show the anime collection?
show_anime_collection = true
# do you wish to show the manga collection?
show_manga_collection = false
# what theme would you like to use? light, dark, or auto
theme = "auto"
################################################################################
# Default views and paths
################################################################################
# Which list should be the default?
default_list = "anime" # anime or manga
# Default pages for anime/manga
default_anime_list_path = "watching" # watching|plan_to_watch|on_hold|dropped|completed|all
default_manga_list_path = "reading" # reading|plan_to_read|on_hold|dropped|completed|all
################################################################################
# Not on Settings Page
#
# These settings are not available to change on the settings page
################################################################################
# Use HTTPs for URLs
# It is not recommended to change this setting
secure_urls = true

View File

@ -1,13 +0,0 @@
<?php
return [
'collection' => [
'type' => 'sqlite',
'host' => '',
'user' => '',
'pass' => '',
'port' => '',
'name' => 'default',
'database' => '',
'file' => __DIR__ . '/../../anime_collection.sqlite',
]
];

View File

@ -0,0 +1,11 @@
################################################################################
# Database Configuration #
################################################################################
type = "sqlite"
host = ""
user = ""
pass = ""
port = ""
database = ""
file = "anime_collection.sqlite3"

View File

@ -1,60 +0,0 @@
<?php
/**
* Easy Min
*
* Simple minification for better website performance
*
* @author Timothy J. Warren
* @copyright Copyright (c) 2012
* @link https://github.com/aviat4ion/Easy-Min
* @license http://philsturgeon.co.uk/code/dbad-license
*/
// --------------------------------------------------------------------------
/* $config = */require 'config.php';
$config = (object)$config;
// Should we use myth to preprocess?
$use_myth = FALSE;
/*
|--------------------------------------------------------------------------
| CSS Folder
|--------------------------------------------------------------------------
|
| The folder where css files exist, in relation to the document root
|
*/
$css_root = $config->asset_dir. '/css/';
/*
|--------------------------------------------------------------------------
| Path from
|--------------------------------------------------------------------------
|
| Path fragment to rewrite in css files
|
*/
$path_from = '';
/*
|--------------------------------------------------------------------------
| Path to
|--------------------------------------------------------------------------
|
| The path fragment replacement for the css files
|
*/
$path_to = '';
/*
|--------------------------------------------------------------------------
| JS Folder
|--------------------------------------------------------------------------
|
| The folder where javascript files exist, in relation to the document root
|
*/
$js_root = $config->asset_dir. '/js/';

View File

@ -1,36 +0,0 @@
<?php
/**
* Easy Min
*
* Simple minification for better website performance
*
* @author Timothy J. Warren
* @copyright Copyright (c) 2012
* @link https://github.com/aviat4ion/Easy-Min
* @license http://philsturgeon.co.uk/code/dbad-license
*/
// --------------------------------------------------------------------------
/**
* This is the config array for css files to concatenate and minify
*/
return [
/*-----
Css
-----*/
/*
For each group create an array like so
'my_group' => array(
'path/to/css/file1.css',
'path/to/css/file2.css'
),
*/
'base' => [
'marx.css',
'base.css'
]
];
// End of css_groups.php

View File

@ -1,40 +0,0 @@
<?php
/**
* Easy Min
*
* Simple minification for better website performance
*
* @author Timothy J. Warren
* @copyright Copyright (c) 2012
* @link https://github.com/aviat4ion/Easy-Min
* @license http://philsturgeon.co.uk/code/dbad-license
*/
// --------------------------------------------------------------------------
/**
* This is the config array for javascript files to concatenate and minify
*/
return [
/*
For each group create an array like so
'my_group' => array(
'path/to/js/file1.js',
'path/to/js/file2.js'
),
*/
'table' => [
'lib/jquery.min.js',
'lib/table_sorter/jquery.tablesorter.min.js',
'sort_tables.js'
],
'edit' => [
'lib/jquery.min.js',
'show_message.js',
'anime_edit.js',
'manga_edit.js'
]
];
// End of js_groups.php

View File

@ -1,188 +0,0 @@
<?php
return [
// Routes on all controllers
'common' => [
'update' => [
'path' => '/update',
'action' => ['update'],
'verb' => 'post'
],
'login_form' => [
'path' => '/login',
'action' => ['login'],
'verb' => 'get'
],
'login_action' => [
'path' => '/login',
'action' => ['login_action'],
'verb' => 'post'
],
'logout' => [
'path' => '/logout',
'action' => ['logout']
],
],
// Routes on anime controller
'anime' => [
'index' => [
'path' => '/',
'action' => ['redirect'],
'params' => [
'url' => '', // Determined by config
'code' => '301'
]
],
'all' => [
'path' => '/all{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'all',
'title' => WHOSE . " Anime List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'watching' => [
'path' => '/watching{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'currently-watching',
'title' => WHOSE . " Anime List &middot; Watching"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'plan_to_watch' => [
'path' => '/plan_to_watch{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'plan-to-watch',
'title' => WHOSE . " Anime List &middot; Plan to Watch"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'on_hold' => [
'path' => '/on_hold{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'on-hold',
'title' => WHOSE . " Anime List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'dropped' => [
'path' => '/dropped{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'dropped',
'title' => WHOSE . " Anime List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'completed' => [
'path' => '/completed{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'completed',
'title' => WHOSE . " Anime List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'collection' => [
'path' => '/collection{/view}',
'action' => ['collection'],
'params' => [],
'tokens' => [
'view' => '[a-z_]+'
]
]
],
'manga' => [
'index' => [
'path' => '/',
'action' => ['redirect'],
'params' => [
'url' => '', // Determined by config
'code' => '301',
'type' => 'manga'
]
],
'all' => [
'path' => '/all{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'all',
'title' => WHOSE . " Manga List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'reading' => [
'path' => '/reading{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Reading',
'title' => WHOSE . " Manga List &middot; Reading"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'plan_to_read' => [
'path' => '/plan_to_read{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Plan to Read',
'title' => WHOSE . " Manga List &middot; Plan to Read"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'on_hold' => [
'path' => '/on_hold{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'On Hold',
'title' => WHOSE . " Manga List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'dropped' => [
'path' => '/dropped{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Dropped',
'title' => WHOSE . " Manga List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'completed' => [
'path' => '/completed{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Completed',
'title' => WHOSE . " Manga List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
]
]
]
];

View File

@ -1,121 +0,0 @@
<?php
/**
* Anime Controller
*/
namespace AnimeClient;
/**
* Controller for Anime-related pages
*/
class AnimeController extends BaseController {
/**
* The anime list model
* @var object $model
*/
protected $model;
/**
* The anime collection model
* @var object $collection_model
*/
private $collection_model;
/**
* Data to ve sent to all routes in this controller
* @var array $base_data
*/
protected $base_data;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Watching' => '/watching{/view}',
'Plan to Watch' => '/plan_to_watch{/view}',
'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}',
'Collection' => '/collection{/view}',
'All' => '/all{/view}'
];
/**
* Constructor
*/
public function __construct(Config $config, Array $web)
{
parent::__construct($config, $web);
if ($this->config->show_anime_collection === FALSE)
{
unset($this->nav_routes['Collection']);
}
$this->model = new AnimeModel();
$this->collection_model = new AnimeCollectionModel();
$this->base_data = [
'message' => '',
'url_type' => 'anime',
'other_type' => 'manga',
'nav_routes' => $this->nav_routes,
];
}
/**
* Show a portion, or all of the anime list
*
* @param string $type - The section of the list
* @param string $title - The title of the page
* @return void
*/
public function anime_list($type, $title, $view)
{
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($type != 'all')
? $this->model->get_list($type)
: $this->model->get_all_lists();
$this->outputHTML('anime/' . $view_map[$view], [
'title' => $title,
'sections' => $data
]);
}
/**
* Show the anime collection page
*
* @return void
*/
public function collection($view)
{
$view_map = [
'' => 'collection',
'list' => 'collection_list'
];
$data = $this->collection_model->get_collection();
$this->outputHTML('anime/' . $view_map[$view], [
'title' => WHOSE . " Anime Collection",
'sections' => $data
]);
}
/**
* Update an anime item
*
* @return bool
*/
public function update()
{
print_r($this->model->update($this->request->post->get()));
}
}
// End of AnimeController.php

View File

@ -1,87 +0,0 @@
<?php
/**
* Manga Controller
*/
namespace AnimeClient;
/**
* Controller for manga list
*/
class MangaController extends BaseController {
/**
* The manga model
* @var object $model
*/
protected $model;
/**
* Data to ve sent to all routes in this controller
* @var array $base_data
*/
protected $base_data;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Reading' => '/reading{/view}',
'Plan to Read' => '/plan_to_read{/view}',
'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}',
'All' => '/all{/view}'
];
/**
* Constructor
*/
public function __construct(Config $config, Array $web)
{
parent::__construct($config, $web);
$this->model = new MangaModel();
$this->base_data = [
'url_type' => 'manga',
'other_type' => 'anime',
'nav_routes' => $this->nav_routes
];
}
/**
* Update an anime item
*
* @return bool
*/
public function update()
{
$this->outputJSON($this->model->update($this->request->post->get()));
}
/**
* Get a section of the manga list
*
* @param string $status
* @param string $title
* @param string $view
* @return void
*/
public function manga_list($status, $title, $view)
{
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($status !== 'all')
? [$status => $this->model->get_list($status)]
: $this->model->get_all_lists();
$this->outputHTML('manga/' . $view_map[$view], [
'title' => $title,
'sections' => $data
]);
}
}
// End of MangaController.php

View File

View File

@ -1,206 +0,0 @@
<?php
/**
* Anime Collection DB Model
*/
namespace AnimeClient;
/**
* Model for getting anime collection data
*/
class AnimeCollectionModel extends BaseDBModel {
/**
* Anime API Model
* @var object $anime_model
*/
private $anime_model;
/**
* Whether the database is valid for querying
* @var bool
*/
private $valid_database = FALSE;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->db = \Query($this->db_config['collection']);
$this->anime_model = new AnimeModel();
// Is database valid? If not, set a flag so the
// app can be run without a valid database
$db_file = file_get_contents($this->db_config['collection']['file']);
$this->valid_database = (strpos($db_file, 'SQLite format 3') === 0);
// Do an import if an import file exists
$this->json_import();
}
/**
* Get collection from the database, and organize by media type
*
* @return array
*/
public function get_collection()
{
$raw_collection = $this->_get_collection();
$collection = [];
foreach($raw_collection as $row)
{
if (array_key_exists($row['media'], $collection))
{
$collection[$row['media']][] = $row;
}
else
{
$collection[$row['media']] = [$row];
}
}
return $collection;
}
/**
* Get full collection from the database
*
* @return array
*/
private function _get_collection()
{
if ( ! $this->valid_database) return [];
$query = $this->db->select('hummingbird_id, slug, title, alternate_title, show_type, age_rating, episode_count, episode_length, cover_image, notes, media.type as media')
->from('anime_set a')
->join('media', 'media.id=a.media_id', 'inner')
->order_by('media')
->order_by('title')
->get();
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Import anime into collection from a json file
*
* @return void
*/
private function json_import()
{
if ( ! file_exists('import.json')) return;
if ( ! $this->valid_database) return;
$anime = json_decode(file_get_contents("import.json"));
foreach($anime as $item)
{
$this->db->set([
'hummingbird_id' => $item->id,
'slug' => $item->slug,
'title' => $item->title,
'alternate_title' => $item->alternate_title,
'show_type' => $item->show_type,
'age_rating' => $item->age_rating,
'cover_image' => $this->get_cached_image($item->cover_image, $item->slug, 'anime'),
'episode_count' => $item->episode_count,
'episode_length' => $item->episode_length
])->insert('anime_set');
}
// Delete the import file
unlink('import.json');
// Update genre info
$this->update_genres();
}
/**
* Update genre information
*
* @return void
*/
private function update_genres()
{
$genres = [];
$flipped_genres = [];
$links = [];
// Get existing genres
$query = $this->db->select('id, genre')
->from('genres')
->get();
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $genre)
{
$genres[$genre['id']] = $genre['genre'];
}
// Get existing link table entries
$query = $this->db->select('hummingbird_id, genre_id')
->from('genre_anime_set_link')
->get();
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $link)
{
if (array_key_exists($link['hummingbird_id'], $links))
{
$links[$link['hummingbird_id']][] = $link['genre_id'];
}
else
{
$links[$link['hummingbird_id']] = [$link['genre_id']];
}
}
// Get the anime collection
$collection = $this->_get_collection();
foreach($collection as $anime)
{
// Get api information
$api = $this->anime_model->get_anime($anime['hummingbird_id']);
foreach($api['genres'] as $genre)
{
// Add genres that don't currently exist
if ( ! in_array($genre['name'], $genres))
{
$this->db->set('genre', $genre['name'])
->insert('genres');
$genres[] = $genre['name'];
}
// Update link table
// Get id of genre to put in link table
$flipped_genres = array_flip($genres);
$insert_array = [
'hummingbird_id' => $anime['hummingbird_id'],
'genre_id' => $flipped_genres[$genre['name']]
];
if (array_key_exists($anime['hummingbird_id'], $links))
{
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['hummingbird_id']]))
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
else
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
}
}
}
// End of AnimeCollectionModel.php

View File

@ -1,245 +0,0 @@
<?php
/**
* Anime API Model
*/
namespace AnimeClient;
/**
* Model for handling requests dealing with the anime list
*/
class AnimeModel extends BaseApiModel {
/**
* The base url for api requests
* @var string $base_url
*/
protected $base_url = "https://hummingbird.me/api/v1/";
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
}
/**
* Update the selected anime
*
* @param array $data
* @return array
*/
public function update($data)
{
$data['auth_token'] = $_SESSION['hummingbird_anime_token'];
$result = $this->client->post("libraries/{$data['id']}", [
'body' => $data
]);
return $result->json();
}
/**
* Get the full set of anime lists
*
* @return array
*/
public function get_all_lists()
{
$output = [
'Watching' => [],
'Plan to Watch' => [],
'On Hold' => [],
'Dropped' => [],
'Completed' => [],
];
$data = $this->_get_list();
foreach($data as $datum)
{
switch($datum['status'])
{
case "completed":
$output['Completed'][] = $datum;
break;
case "plan-to-watch":
$output['Plan to Watch'][] = $datum;
break;
case "dropped":
$output['Dropped'][] = $datum;
break;
case "on-hold":
$output['On Hold'][] = $datum;
break;
case "currently-watching":
$output['Watching'][] = $datum;
break;
}
}
// Sort anime by name
foreach($output as &$status_list)
{
$this->sort_by_name($status_list);
}
return $output;
}
/**
* Get a category out of the full list
*
* @param string $status
* @return array
*/
public function get_list($status)
{
$map = [
'currently-watching' => 'Watching',
'plan-to-watch' => 'Plan to Watch',
'on-hold' => 'On Hold',
'dropped' => 'Dropped',
'completed' => 'Completed',
];
$data = $this->_get_list($status);
$this->sort_by_name($data);
$output = [];
$output[$map[$status]] = $data;
return $output;
}
/**
* Get information about an anime from its id
*
* @param string $anime_id
* @return array
*/
public function get_anime($anime_id)
{
$config = [
'query' => [
'id' => $anime_id
]
];
$response = $this->client->get("anime/{$anime_id}", $config);
return $response->json();
}
/**
* Search for anime by name
*
* @param string $name
* @return array
*/
public function search($name)
{
global $defaultHandler;
$config = [
'query' => [
'query' => $name
]
];
$response = $this->client->get('search/anime', $config);
$defaultHandler->addDataTable('anime_search_response', (array)$response);
if ($response->getStatusCode() != 200)
{
throw new Exception($response->getEffectiveUrl());
}
return $response->json();
}
/**
* Actually retreive the data from the api
*
* @param string $status - Status to filter by
* @return array
*/
private function _get_list($status="all")
{
global $defaultHandler;
$cache_file = "{$this->config->data_cache_path}/anime-{$status}.json";
$config = [
'allow_redirects' => FALSE
];
if ($status != "all")
{
$config['query']['status'] = $status;
}
$response = $this->client->get("users/{$this->config->hummingbird_username}/library", $config);
$defaultHandler->addDataTable('anime_list_response', (array)$response);
if ($response->getStatusCode() != 200)
{
if ( ! file_exists($cache_file))
{
throw new Exception($response->getEffectiveUrl());
}
else
{
$output = json_decode(file_get_contents($cache_file), TRUE);
}
}
else
{
$output = $response->json();
$output_json = json_encode($output);
if (( ! file_exists($cache_file)) || file_get_contents($cache_file) !== $output_json)
{
// Attempt to create the cache folder if it doesn't exist
if ( ! is_dir($this->config->data_cache_path))
{
mkdir($this->config->data_cache_path);
}
// Cache the call in case of downtime
file_put_contents($cache_file, json_encode($output));
}
}
foreach($output as &$row)
{
$row['anime']['cover_image'] = $this->get_cached_image($row['anime']['cover_image'], $row['anime']['slug'], 'anime');
}
return $output;
}
/**
* Sort the list by title
*
* @param array $array
* @return void
*/
private function sort_by_name(&$array)
{
$sort = array();
foreach($array as $key => $item)
{
$sort[$key] = $item['anime']['title'];
}
array_multisort($sort, SORT_ASC, $array);
}
}
// End of AnimeModel.php

View File

@ -1,193 +0,0 @@
<?php
/**
* Manga API Model
*/
namespace AnimeClient;
/**
* Model for handling requests dealing with the manga list
*/
class MangaModel extends BaseApiModel {
/**
* The base url for api requests
* @var string
*/
protected $base_url = "https://hummingbird.me/";
/**
* Update the selected manga
*
* @param array $data
* @return array
*/
public function update($data)
{
$id = $data['id'];
unset($data['id']);
$result = $this->client->put("manga_library_entries/{$id}", [
'cookies' => ['token' => $_SESSION['hummingbird_anime_token']],
'json' => ['manga_library_entry' => $data]
]);
return $result->json();
}
/**
* Get the full set of anime lists
*
* @return array
*/
public function get_all_lists()
{
$data = $this->_get_list();
foreach ($data as $key => &$val)
{
$this->sort_by_name($val);
}
return $data;
}
/**
* Get a category out of the full list
*
* @param string $status
* @return array
*/
public function get_list($status)
{
$data = $this->_get_list($status);
$this->sort_by_name($data);
return $data;
}
/**
* Massage the list of manga entries into something more usable
*
* @param string $status
* @return array
*/
private function _get_list($status="all")
{
global $defaultHandler;
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
$config = [
'query' => [
'user_id' => $this->config->hummingbird_username
],
'allow_redirects' => FALSE
];
$response = $this->client->get('manga_library_entries', $config);
$defaultHandler->addDataTable('response', (array)$response);
if ($response->getStatusCode() != 200)
{
if ( ! file_exists($cache_file))
{
throw new Exception($response->getEffectiveUrl());
}
else
{
$raw_data = json_decode(file_get_contents($cache_file), TRUE);
}
}
else
{
// Reorganize data to be more usable
$raw_data = $response->json();
// Attempt to create the cache dir if it doesn't exist
if ( ! is_dir($this->config->data_cache_path))
{
mkdir($this->config->data_cache_path);
}
// Cache data in case of downtime
file_put_contents($cache_file, json_encode($raw_data));
}
// Bail out early if there isn't any manga data
if (empty($raw_data)) return [];
$data = [
'Reading' => [],
'Plan to Read' => [],
'On Hold' => [],
'Dropped' => [],
'Completed' => [],
];
$manga_data = [];
// Massage the two lists into one
foreach($raw_data['manga'] as $manga)
{
$manga_data[$manga['id']] = $manga;
}
// Filter data by status
foreach($raw_data['manga_library_entries'] as &$entry)
{
$entry['manga'] = $manga_data[$entry['manga_id']];
// Cache poster images
$entry['manga']['poster_image'] = $this->get_cached_image($entry['manga']['poster_image'], $entry['manga_id'], 'manga');
switch($entry['status'])
{
case "Plan to Read":
$data['Plan to Read'][] = $entry;
break;
case "Dropped":
$data['Dropped'][] = $entry;
break;
case "On Hold":
$data['On Hold'][] = $entry;
break;
case "Currently Reading":
$data['Reading'][] = $entry;
break;
case "Completed":
default:
$data['Completed'][] = $entry;
break;
}
}
//file_put_contents(_dir($this->config->data_cache_path, "manga-processed.json"), json_encode($data, JSON_PRETTY_PRINT));
return (array_key_exists($status, $data)) ? $data[$status] : $data;
}
/**
* Sort the manga entries by their title
*
* @param array $array
* @return void
*/
private function sort_by_name(&$array)
{
$sort = array();
foreach($array as $key => $item)
{
$sort[$key] = $item['manga']['romaji_title'];
}
array_multisort($sort, SORT_ASC, $array);
}
}
// End of MangaModel.php

View File

@ -0,0 +1,100 @@
<article
class="media"
data-kitsu-id="<?= $item['id'] ?>"
data-anilist-id="<?= $item['anilist_id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>"
>
<?php if ($_->isAuthenticated()): ?>
<button title="Increment episode count" class="plus-one" hidden>+1 Episode</button>
<?php endif ?>
<?= $_->h->img($item['anime']['cover_image'], ['width' => 220, 'loading' => 'lazy']) ?>
<div class="name">
<a href="<?= $_->urlFromRoute('anime.details', ['id' => $item['anime']['slug']]) ?>">
<span class="canonical"><?= $item['anime']['title'] ?></span>
<?php foreach ($item['anime']['titles'] as $title): ?>
<br/>
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if (isset($item['private']) || isset($item['rewatching'])): ?>
<div class="row">
<?php foreach (['private', 'rewatching'] as $attr): ?>
<?php if ($item[$attr]): ?>
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($item['rewatched'] > 0): ?>
<div class="row">
<?php if ($item['rewatched'] == 1): ?>
<div>Rewatched once</div>
<?php elseif ($item['rewatched'] == 2): ?>
<div>Rewatched twice</div>
<?php elseif ($item['rewatched'] == 3): ?>
<div>Rewatched thrice</div>
<?php else: ?>
<div>Rewatched <?= $item['rewatched'] ?> times</div>
<?php endif ?>
</div>
<?php endif ?>
<?php if (count($item['anime']['streaming_links']) > 0): ?>
<div class="row">
<?php foreach ($item['anime']['streaming_links'] as $link): ?>
<div class="cover-streaming-link">
<?php if ($link['meta']['link']): ?>
<a href="<?= $link['link'] ?>"
title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 20,
'height' => 20,
'alt' => "{$link['meta']['name']} logo",
]); ?>
</a>
<?php else: ?>
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 20,
'height' => 20,
'alt' => "{$link['meta']['name']} logo",
]); ?>
<?php endif ?>
</div>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($_->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed" title="Edit information about this anime" href="<?=
$_->urlFromRoute('edit', [
'controller' => 'anime',
'id' => $item['id'],
'status' => $item['watching_status']
]);
?>">Edit</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
<div class="completion">Episodes:
<span class="completed_number"><?= $item['episodes']['watched'] ?></span> /
<span class="total_number"><?= $item['episodes']['total'] ?></span>
</div>
</div>
<div class="row">
<div class="media_type"><?= $_->escape->html($item['anime']['show_type']) ?></div>
<div class="airing-status"><?= $_->escape->html($item['airing']['status']) ?></div>
<div class="age-rating"><?= $_->escape->html($item['anime']['age_rating']) ?></div>
</div>
</div>
</article>

View File

@ -0,0 +1,6 @@
<article class="<?= $className ?>">
<div class="name">
<a href="<?= $link ?>"><?= $name ?></a>
</div>
<a href="<?= $link ?>"><?= $picture ?></a>
</article>

View File

@ -0,0 +1,68 @@
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
<?php if ($_->isAuthenticated()): ?>
<div class="edit-buttons" hidden>
<button class="plus-one-chapter">+1 Chapter</button>
</div>
<?php endif ?>
<?= $_->h->img($item['manga']['image'], ['width' => 220, 'loading' => 'lazy']) ?>
<div class="name">
<a href="<?= $_->urlFromRoute('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $_->escape->html($item['manga']['title']) ?>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if ($_->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed"
title="Edit information about this manga"
href="<?= $_->urlFromRoute('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
]) ?>">
Edit
</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div><?= $item['manga']['type'] ?></div>
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
</div>
<?php if ($item['rereading']): ?>
<div class="row">
<?php foreach(['rereading'] as $attr): ?>
<?php if($item[$attr]): ?>
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($item['reread'] > 0): ?>
<div class="row">
<?php if ($item['reread'] == 1): ?>
<div>Reread once</div>
<?php elseif ($item['reread'] == 2): ?>
<div>Reread twice</div>
<?php elseif ($item['reread'] == 3): ?>
<div>Reread thrice</div>
<?php else: ?>
<div>Reread <?= $item['reread'] ?> times</div>
<?php endif ?>
</div>
<?php endif ?>
<div class="row">
<div class="chapter_completion">
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
</div>
</div>
</div>
</article>

12
app/templates/media.php Normal file
View File

@ -0,0 +1,12 @@
<article class="<?= $className ?>">
<a href="<?= $link ?>"><?= $picture ?></a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>

View File

@ -0,0 +1,5 @@
<section class="<?= $className ?>">
<?php foreach ($data as $tabName => $tabData): ?>
<?= $callback($tabData, $tabName) ?>
<?php endforeach ?>
</section>

32
app/templates/tabs.php Normal file
View File

@ -0,0 +1,32 @@
<div class="tabs">
<?php $i = 0; foreach ($data as $tabName => $tabData): ?>
<?php if ( ! empty($tabData)): ?>
<?php $id = "{$name}-{$i}"; ?>
<input
role='tab'
aria-controls="_<?= $id ?>"
type="radio"
name="<?= $name ?>"
id="<?= $id ?>"
<?= ($i === 0) ? 'checked="checked"' : '' ?>
/>
<label for="<?= $id ?>"><?= ucfirst($tabName) ?></label>
<?php if ($hasSectionWrapper): ?>
<div class="content full-height">
<?php endif ?>
<section
id="_<?= $id ?>"
role="tabpanel"
class="<?= $className ?>"
>
<?= $callback($tabData, $tabName) ?>
</section>
<?php if ($hasSectionWrapper): ?>
</div>
<?php endif ?>
<?php endif ?>
<?php $i++; endforeach ?>
</div>

View File

@ -0,0 +1,25 @@
<div class="vertical-tabs">
<?php $i = 0; ?>
<?php foreach ($data as $tabName => $tabData): ?>
<?php $id = "{$name}-{$i}" ?>
<div class="tab">
<input
type="radio"
role='tab'
aria-controls="_<?= $id ?>"
name="<?= $name ?>"
id="<?= $id ?>"
<?= $i === 0 ? 'checked="checked"' : '' ?>
/>
<label for="<?= $id ?>"><?= $tabName ?></label>
<section
id='_<?= $id ?>'
role="tabpanel"
class="<?= $className ?>"
>
<?= $callback($tabData, $tabName) ?>
</section>
</div>
<?php $i++; ?>
<?php endforeach ?>
</div>

View File

@ -1,7 +1,6 @@
<body>
<main>
<h1>404</h1>
<h2>Page Not Found</h2>
</main>
</body>
</html>
<main>
<h1>404</h1>
<h2><?= $message ?></h2>
<pre>(╯°□°)╯︵ ┻━┻
┬─┬ノ( º _ ºノ)</pre>
</main>

40
app/views/anime/add.php Normal file
View File

@ -0,0 +1,40 @@
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Add Anime to your List</h2>
<form action="<?= $action_url ?>" method="post">
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
<section>
<div class="cssload-loader" hidden="hidden">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for anime by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>
<br />
<table class="invisible form">
<tbody>
<tr>
<td><label for="status">Watching Status</label></td>
<td>
<select name="status" id="status">
<?php foreach($status_list as $status_key => $status_title): ?>
<option value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" name="type" value="anime" />
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
</main>
<?php endif ?>

View File

@ -1,28 +0,0 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<img src="<?= $item['cover_image'] ?>" />
<div class="name">
<?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
</div>
<div class="table">
<div class="row">
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
<div class="media_type"><?= $item['show_type'] ?></div>
<div class="age_rating"><?= $item['age_rating'] ?></div>
</div>
</div>
</article>
</a>
<?php endforeach ?>
</section>
</section>
<?php endforeach ?>
</main>

View File

@ -1,43 +0,0 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr>
<td class="align_left">
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<?= $item['title'] ?>
</a>
</td>
<td class="align_left"><?= $item['alternate_title'] ?></td>
<td><?= $item['episode_count'] ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td>
<td class="align_left"><?= $item['notes'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<br />
<?php endforeach ?>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
<script>
$(function() {
$('table').tablesorter();
});
</script>

View File

@ -1,40 +1,30 @@
<main>
<main class="media-list">
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute('anime.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<?php if (empty($items)): ?>
<section class="status">
<h2><?= $name ?></h2>
<h2><?= $_->escape->html($name) ?></h2>
<h3>There's nothing here!</h3>
</section>
<?php else: ?>
<section class="status">
<h2><?= $_->escape->html($name) ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<article class="media" id="a-<?= $item['anime']['id'] ?>">
<?php if (is_logged_in()): ?>
<button class="plus_one" hidden>+1 Episode</button>
<?php endif ?>
<img src="<?= $item['anime']['cover_image'] ?>" />
<div class="name">
<a href="<?= $item['anime']['url'] ?>">
<?= $item['anime']['title'] ?>
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
</a>
</div>
<div class="table">
<div class="row">
<div class="user_rating">Rating: <?= ($item['rating']['value'] > 0) ? (int)($item['rating']['value'] * 2) : " - " ?> / 10</div>
<div class="completion">Episodes:
<span class="completed_number"><?= $item['episodes_watched'] ?></span> /
<span class="total_number"><?= ($item['anime']['episode_count'] != 0) ? $item['anime']['episode_count'] : "-" ?></span>
</div>
</div>
<div class="row">
<div class="media_type"><?= $item['anime']['show_type'] ?></div>
<div class="airing_status"><?= $item['anime']['status'] ?></div>
<div class="age_rating"><?= $item['anime']['age_rating'] ?></div>
</div>
</div>
</article>
<?php if ($item['private'] && ! $_->isAuthenticated()) continue; ?>
<?= $_->component->animeCover($item) ?>
<?php endforeach ?>
</section>
</section>
<?php endif ?>
<?php endforeach ?>
</main>
<?php if (is_logged_in()): ?>
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
<?php endif ?>
</main>

205
app/views/anime/details.php Normal file
View File

@ -0,0 +1,205 @@
<?php
use function Aviat\AnimeClient\friendlyTime;
?>
<main class="details fixed">
<section class="flex">
<aside class="info">
<?= $_->h->img($data['cover_image'], ['width' => '390']) ?>
<br />
<table class="media-details">
<tr>
<td class="align-right">Airing Status</td>
<td><?= $data['status'] ?></td>
</tr>
<?php if ( ! empty($data['airDate'])): ?>
<tr>
<td>Original Airing</td>
<td><?= $data['airDate'] ?></td>
</tr>
<?php endif ?>
<tr>
<td>Show Type</td>
<td><?= (strlen($data['show_type']) > 3) ? ucfirst(strtolower($data['show_type'])) : $data['show_type'] ?></td>
</tr>
<?php if ($data['episode_count'] !== 1): ?>
<tr>
<td>Episode Count</td>
<td><?= $data['episode_count'] ?? '-' ?></td>
</tr>
<?php endif ?>
<?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?>
<tr>
<td>Episode Length</td>
<td><?= friendlyTime($data['episode_length']) ?></td>
</tr>
<?php endif ?>
<?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?>
<tr>
<td>Total Length</td>
<td><?= friendlyTime($data['total_length']) ?></td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
</td>
</tr>
<?php endif ?>
<?php if (count($data['links']) > 0): ?>
<tr>
<td>External Links</td>
<td>
<?php foreach ($data['links'] as $urlName => $externalUrl): ?>
<a rel='external' href="<?= $externalUrl ?>"><?= $urlName ?></a><br />
<?php endforeach ?>
</td>
</tr>
<?php endif ?>
<tr>
<td>Genres</td>
<td>
<?= implode(', ', $data['genres']) ?>
</td>
</tr>
</table>
<br />
</aside>
<article class="text">
<h2 class="toph"><?= $data['title'] ?></h2>
<?php foreach ($data['titles_more'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
<br />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['synopsis']) ?></p>
</div>
<?php if (count($data['streaming_links']) > 0): ?>
<hr />
<h4>Streaming on:</h4>
<table class="full-width invisible streaming-links">
<thead>
<tr>
<th class="align-left">Service</th>
<th>Subtitles</th>
<th>Dubs</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['streaming_links'] as $link): ?>
<tr>
<td class="align-left">
<?php if ($link['meta']['link'] !== FALSE): ?>
<a
href="<?= $link['link'] ?>"
title="Stream '<?= $data['title'] ?>' on <?= $link['meta']['name'] ?>"
>
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
'alt' => "{$link['meta']['name']} logo",
]) ?>
&nbsp;&nbsp;<?= $link['meta']['name'] ?>
</a>
<?php else: ?>
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
'alt' => "{$link['meta']['name']} logo",
]) ?>
&nbsp;&nbsp;<?= $link['meta']['name'] ?>
<?php endif ?>
</td>
<td><?= implode(', ', array_map(fn ($sub) => Locale::getDisplayLanguage($sub, 'en'), $link['subs'])) ?></td>
<td><?= implode(', ', array_map(fn ($dub) => Locale::getDisplayLanguage($dub, 'en'), $link['dubs'])) ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php if ( ! empty($data['trailer_id'])): ?>
<div class="responsive-iframe">
<h4>Trailer</h4>
<iframe
width="560"
height="315"
role='img'
src="https://www.youtube.com/embed/<?= $data['trailer_id'] ?>"
allow="autoplay; encrypted-media"
allowfullscreen
tabindex='0'
title="<?= $data['title'] ?> trailer video"
></iframe>
</div>
<?php endif ?>
</article>
</section>
<?php if (count($data['characters']) > 0): ?>
<section>
<h2>Characters</h2>
<?= $_->component->tabs('character-types', $data['characters'], static function ($characterList, $role)
use ($_) {
$rendered = [];
foreach ($characterList as $id => $character):
if (empty($character['image']))
{
continue;
}
$rendered[] = $_->component->character(
$character['name'],
$_->urlFromRoute('character', ['slug' => $character['slug']]),
$_->h->img($character['image']),
(strtolower($role) !== 'main') ? 'small-character' : 'character'
);
endforeach;
return implode('', array_map('mb_trim', $rendered));
}) ?>
</section>
<?php endif ?>
<?php if (count($data['staff']) > 0): ?>
<section>
<h2>Staff</h2>
<?= $_->component->verticalTabs('staff-role', $data['staff'], static function ($staffList)
use ($_) {
$rendered = [];
foreach ($staffList as $id => $person):
if (empty($person['image']))
{
continue;
}
$rendered[] = $_ ->component->character(
$person['name'],
$_->urlFromRoute('person', ['slug' => $person['slug']]),
$_->h->img($person['image']),
'character small-person',
);
endforeach;
return implode('', array_map('mb_trim', $rendered));
}) ?>
</section>
<?php endif ?>
</main>

View File

@ -1,8 +1,112 @@
<body>
<?php include 'nav.php' ?>
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Edit Anime List Item</h2>
<form action="<?= $action ?>" method="post">
<table class="invisible form">
<thead>
<tr>
<th>
<h3><?= $_->escape->html($item['anime']['title']) ?></h3>
<?php foreach($item['anime']['titles'] as $title): ?>
<h4><?= $_->escape->html($title) ?></h4>
<?php endforeach ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="9">
<?= $_->h->img($item['anime']['cover_image']) ?>
</td>
</tr>
<tr>
<td><label for="private">Is Private?</label></td>
<td>
<input type="checkbox" name="private" id="private"
<?php if($item['private']): ?>checked="checked"<?php endif ?>
/>
</td>
</tr>
<tr>
<td><label for="watching_status">Watching Status</label></td>
<td>
<select name="watching_status" id="watching_status">
<?php foreach($statuses as $status_key => $status_title): ?>
<option <?php if(strtolower($item['watching_status']) === $status_key): ?>selected="selected"<?php endif ?>
value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><label for="series_rating">Rating</label></td>
<td>
<input type="number" min="0" max="10" maxlength="2" name="user_rating" id="series_rating" value="<?= $item['user_rating'] ?>" id="series_rating" size="2" /> / 10
</td>
</tr>
<tr>
<td><label for="episodes_watched">Episodes Watched</label></td>
<td>
<input type="number" min="0" size="4" maxlength="4" value="<?= $item['episodes']['watched'] ?>" name="episodes_watched" id="episodes_watched" />
<?php if($item['episodes']['total'] > 0): ?>
/ <?= $item['episodes']['total'] ?>
<?php endif ?>
</td>
</tr>
<tr>
<td><label for="rewatching_flag">Rewatching?</label></td>
<td>
<input type="checkbox" name="rewatching" id="rewatching_flag"
<?php if($item['rewatching'] === TRUE): ?>checked="checked"<?php endif ?>
/>
</td>
</tr>
<tr>
<td><label for="rewatched">Rewatch Count</label></td>
<td>
<input type="number" min="0" id="rewatched" name="rewatched" value="<?= $item['rewatched'] ?>" />
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td>
<textarea name="notes" id="notes"><?= $_->escape->html($item['notes']) ?></textarea>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<?php if ( ! empty($item['mal_id'])): ?>
<input type="hidden" value="<?= $item['mal_id'] ?? '' ?>" name="mal_id" />
<?php endif ?>
<input type="hidden" value="true" name="edit" />
<button type="submit">Submit</button>
</td>
</tr>
</tbody>
</table>
</form>
<form class="js-delete" action="<?= $_->urlFromRoute('anime.delete') ?>" method="post">
<fieldset>
<legend>Danger Zone</legend>
<table class="form invisible">
<tbody>
<tr>
<td class="danger">
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<?php if (!empty($item['mal_id'])): ?>
<input type="hidden" value="<?= $item['mal_id'] ?? '' ?>" name="mal_id" />
<?php endif ?>
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</main>
</body>
</html>
<?php endif ?>

View File

@ -1,36 +1,113 @@
<main>
<?php use function Aviat\AnimeClient\colNotEmpty; ?>
<main class="media-list">
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute('anime.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Airing Status</th>
<th>Score</th>
<th>Type</th>
<th>Progress</th>
<th>Rated</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="a-<?= $item['anime']['id'] ?>">
<td class="align_left">
<a href="<?= $item['anime']['url'] ?>">
<?= $item['anime']['title'] ?>
</a>
</td>
<td class="align_left"><?= $item['anime']['alternate_title'] ?></td>
<td class="align_left"><?= $item['anime']['status'] ?></td>
<td><?= (int)($item['rating']['value'] * 2) ?> / 10 </td>
<td><?= $item['anime']['show_type'] ?></td>
<td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
<td><?= $item['anime']['age_rating'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php if (empty($items)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<?php
$hasNotes = colNotEmpty($items, 'notes');
?>
<table class='media-wrap'>
<thead>
<tr>
<?php if($_->isAuthenticated()): ?>
<td class="no-border">&nbsp;</td>
<?php endif ?>
<th>Title</th>
<th>Airing Status</th>
<th class='numeric'>Score</th>
<th>Type</th>
<th class='numeric'>Progress</th>
<th class='rating'>Age Rating</th>
<th>Attributes</th>
<?php if($hasNotes): ?><th>Notes</th><?php endif ?>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $_->isAuthenticated()) continue; ?>
<tr id="a-<?= $item['id'] ?>">
<?php if ($_->isAuthenticated()): ?>
<td>
<a class="bracketed" href="<?= $_->urlFromRoute('edit', [
'controller' => 'anime',
'id' => $item['id'],
'status' => $item['watching_status']
]) ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left justify">
<a href="<?= $_->urlFromRoute('anime.details', ['id' => $item['anime']['slug']]) ?>">
<?= $item['anime']['title'] ?>
</a>
<br />
<?= implode('<br />', $item['anime']['titles']) ?>
</td>
<td><?= $item['airing']['status'] ?></td>
<td><?= $item['user_rating'] ?> / 10 </td>
<td><?= $item['anime']['show_type'] ?></td>
<td id="<?= $item['anime']['slug'] ?>">
Episodes: <br />
<span class="completed_number"><?= $item['episodes']['watched'] ?></span>&nbsp;/&nbsp;<span class="total_number"><?= $item['episodes']['total'] ?></span>
</td>
<td><?= $item['anime']['age_rating'] ?></td>
<td>
<?php foreach($item['anime']['streaming_links'] as $link): ?>
<?php if ($link['meta']['link'] !== FALSE): ?>
<a href="<?= $link['link'] ?>" title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'small-streaming-logo',
'width' => 25,
'height' => 25,
'alt' => "{$link['meta']['name']} logo",
]) ?>
</a>
<?php else: ?>
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'small-streaming-logo',
'width' => 25,
'height' => 25,
'alt' => "{$link['meta']['name']} logo",
]) ?>
<?php endif ?>
<?php endforeach ?>
<br />
<ul>
<?php if ($item['rewatched'] > 0): ?>
<?php if ($item['rewatched'] == 1): ?>
<li>Rewatched once</li>
<?php elseif ($item['rewatched'] == 2): ?>
<li>Rewatched twice</li>
<?php elseif ($item['rewatched'] == 3): ?>
<li>Rewatched thrice</li>
<?php else: ?>
<li>Rewatched <?= $item['rewatched'] ?> times</li>
<?php endif ?>
<?php endif ?>
<?php foreach(['private','rewatching'] as $attr): ?>
<?php if($item[$attr]): ?><li><?= ucfirst($attr); ?></li><?php endif ?>
<?php endforeach ?>
</ul>
</td>
<?php if ($hasNotes): ?><td><p><?= $_->escape->html($item['notes']) ?></p></td><?php endif ?>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
</main>
<script src="<?= asset_url('js.php?g=table') ?>"></script>
<script defer="defer" src="<?= $_->assetUrl('js/tables.min.js') ?>"></script>

3
app/views/blank.php Normal file
View File

@ -0,0 +1,3 @@
<main>
<h1><?= $title ?></h1>
</main>

View File

@ -0,0 +1,162 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\Kitsu;
?>
<main class="character-page details fixed">
<section class="flex flex-no-wrap">
<aside>
<?= $_->h->img($data['image']) ?>
</aside>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
<?php foreach ($data['names'] as $name): ?>
<h3><?= $name ?></h3>
<?php endforeach ?>
<?php if ( ! empty($data['otherNames'])): ?>
<h4>Also Known As:</h4>
<ul>
<?php foreach ($data['otherNames'] as $name): ?>
<li><h5><?= $name ?></h5></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<br />
<hr />
<div class="description">
<p><?= nl2br($data['description']) ?></p>
</div>
</div>
</section>
<?php if ( ! (empty($data['media']['anime']) || empty($data['media']['manga']))): ?>
<h3>Media</h3>
<?= $_->component->tabs('character-media', $data['media'], static function ($media, $mediaType) use ($_) {
$rendered = [];
foreach ($media as $id => $item)
{
$rendered[] = $_->component->media(
array_merge([$item['title']], $item['titles']),
$_->urlFromRoute("{$mediaType}.details", ['id' => $item['slug']]),
$_->h->img(Kitsu::getPosterImage($item), ['width' => 220, 'loading' => 'lazy']),
);
}
return implode('', array_map('mb_trim', $rendered));
}, 'media-wrap content') ?>
<?php endif ?>
<section>
<?php if (count($data['castings']) > 0): ?>
<h3>Castings</h3>
<?php
$vas = $data['castings']['Voice Actor'];
unset($data['castings']['Voice Actor']);
ksort($vas)
?>
<?php foreach ($data['castings'] as $role => $entries): ?>
<h4><?= $role ?></h4>
<?php foreach ($entries as $language => $casting): ?>
<h5><?= $language ?></h5>
<table class="min-table">
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach ($casting as $cid => $c): ?>
<tr>
<td>
<article class="character">
<?php
$link = $_->urlFromRoute('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $_->h->img($c['person']['image']) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td>
<section class="align-left media-wrap">
<?php foreach ($c['series'] as $series): ?>
<article class="media">
<?php
$link = $_->urlFromRoute('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $_->h->img(Kitsu::getPosterImage($series['attributes'])) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endforeach ?>
<?php endforeach ?>
<?php if ( ! empty($vas)): ?>
<h4>Voice Actors</h4>
<?= $_->component->tabs('character-vas', $vas, static function ($casting) use ($_) {
$castings = [];
foreach ($casting as $id => $c):
$person = $_->component->character(
$c['person']['name'],
$_->urlFromRoute('person', ['slug' => $c['person']['slug']]),
$_->h->img($c['person']['image']['original']['url']),
);
$medias = array_map(fn ($series) => $_->component->media(
array_merge([$series['title']], $series['titles']),
$_->urlFromRoute('anime.details', ['id' => $series['slug']]),
$_->h->img(Kitsu::getPosterImage($series)),
), $c['series']);
$media = implode('', array_map('mb_trim', $medias));
$castings[] = <<<HTML
<tr>
<td>{$person}</td>
<td width="75%">
<section class="align-left media-wrap-flex">
{$media}
</section>
</td>
</tr>
HTML;
endforeach;
$languages = implode('', array_map('mb_trim', $castings));
return <<<HTML
<table class="borderless max-table">
<thead>
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
</thead>
<tbody>{$languages}</tbody>
</table>
HTML;
}, 'content') ?>
<?php endif ?>
<?php endif ?>
</section>
</main>

View File

@ -0,0 +1,39 @@
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Add <?= ucfirst($collection_type) ?> to your Collection</h2>
<form action="<?= $action_url ?>" method="post">
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
<section>
<div class="cssload-loader" hidden="hidden">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search-anime-collection">Search for <?= $collection_type ?> by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search-anime-collection" name="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>
<br />
<table class="invisible form">
<tbody>
<tr>
<td class="align-right"><label for="media_id">Media</label></td>
<td class='align-left'>
<?php include 'media-select-list.php' ?>
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td><textarea id="notes" name="notes"></textarea></td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
</main>
<?php endif ?>

View File

@ -0,0 +1,28 @@
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<?= $_->h->picture("images/anime/{$item['hummingbird_id']}.webp") ?>
<div class="name">
<a href="<?= $_->urlFromRoute('anime.details', ['id' => $item['slug']]) ?>">
<?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<small><br />{$item['alternate_title']}</small>" : ""; ?>
</a>
</div>
<div class="table">
<?php if ($_->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed"
href="<?= $_->urlFromRoute($collection_type . '.collection.edit.get', [
'id' => $item['hummingbird_id']
]) ?>">Edit</a>
</span>
</div>
<?php endif ?>
<div class="row">
<?php if ($item['episode_count'] > 1): ?>
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
<?php endif ?>
<div class="media_type"><?= $item['show_type'] ?></div>
<div class="age-rating"><?= $item['age_rating'] ?></div>
</div>
</div>
</article>

View File

@ -0,0 +1,26 @@
<?php use function Aviat\AnimeClient\renderTemplate; ?>
<main class="media-list">
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute($collection_type . '.collection.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?= $_->component->tabs('collection-tab', $sections, static function ($items) use ($_, $collection_type) {
$rendered = [];
foreach ($items as $item)
{
$rendered[] = renderTemplate(__DIR__ . '/cover-item.php', [
'_' => $_,
'collection_type' => $collection_type,
'item' => $item,
]);
}
return implode('', array_map('mb_trim', $rendered));
}, 'media-wrap', true) ?>
<?php endif ?>
</main>

View File

@ -0,0 +1,66 @@
<?php use function Aviat\AnimeClient\renderTemplate ?>
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Edit Anime Collection Item</h2>
<form action="<?= $action_url ?>" method="post">
<table class="invisible form">
<tbody>
<tr>
<td rowspan="6" class="align-center">
<?= $_->h->picture("images/anime/{$item['hummingbird_id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
</td>
</tr>
<tr>
<td class="align-right"><label for="title">Title</label></td>
<td class="align-left">
<input type="text" id="title" name="title" value="<?= $item['title'] ?>" />
</td>
</tr>
<tr>
<td class="align-right"><label for="alternate_title">Alternate Title</label></td>
<td class="align-left">
<input type="text" id="alternate_title" name="alternate_title" value="<?= $item['alternate_title'] ?>"/>
</td>
</tr>
<tr>
<td class="align-right"><label for="media_id">Media</label></td>
<td class="align-left">
<?php include 'media-select-list.php' ?>
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td><textarea id="notes" name="notes"><?= $_->escape->html($item['notes']) ?></textarea></td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<?php if($action === 'Edit'): ?>
<input type="hidden" name="hummingbird_id" value="<?= $item['hummingbird_id'] ?>" />
<?php endif ?>
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
<form class="js-delete" action="<?= $_->urlFromRoute($collection_type . '.collection.delete') ?>" method="post">
<fieldset>
<legend>Danger Zone</legend>
<table class="form invisible">
<tbody>
<tr>
<td class="danger">
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
</td>
<td>
<input type="hidden" value="<?= $item['hummingbird_id'] ?>" name="hummingbird_id" />
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</main>
<?php endif ?>

View File

@ -0,0 +1,23 @@
<tr>
<?php if ($_->isAuthenticated()): ?>
<td>
<a class="bracketed"
href="<?= $_->urlFromRoute($collection_type . '.collection.edit.get', ['id' => $item['hummingbird_id']]) ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left">
<a href="<?= $_->urlFromRoute('anime.details', ['id' => $item['slug']]) ?>">
<?= $item['title'] ?>
</a>
<?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?>
</td>
<?php if ($hasMedia): ?>
<td><?= implode(', ', $item['media']) ?></td>
<?php endif ?>
<td><?= ($item['episode_count'] > 1) ? $item['episode_count'] : '-' ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td>
<?php if ($hasNotes): ?><td class="align-left"><?= nl2br($item['notes'] ?? '', TRUE) ?></td><?php endif ?>
<td class="align-left"><?= implode(', ', $item['genres']) ?></td>
</tr>

View File

@ -0,0 +1,55 @@
<?php use function Aviat\AnimeClient\{colNotEmpty, renderTemplate}; ?>
<main>
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute($collection_type . '.collection.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?= $_->component->tabs('collection-tab', $sections, static function ($items, $section) use ($_, $helper, $collection_type) {
$hasNotes = colNotEmpty($items, 'notes');
$hasMedia = $section === 'All';
$firstTh = ($_->isAuthenticated()) ? '<td>&nbsp;</td>' : '';
$mediaTh = ($hasMedia) ? '<th>Media</th>' : '';
$noteTh = ($hasNotes) ? '<th>Notes</th>' : '';
$rendered = [];
foreach ($items as $item)
{
$rendered[] = renderTemplate(__DIR__ . '/list-item.php', [
'_' => $_,
'collection_type' => $collection_type,
'hasMedia' => $hasMedia,
'hasNotes' => $hasNotes,
'helper' => $helper,
'item' => $item,
]);
}
$rows = implode('', array_map('mb_trim', $rendered));
return <<<HTML
<table class="full-width media-wrap">
<thead>
<tr>
{$firstTh}
<th>Title</th>
{$mediaTh}
<th class='numeric'>Episode Count</th>
<th class='numeric'>Episode Length</th>
<th>Show Type</th>
<th class='rating'>Age Rating</th>
{$noteTh}
<th>Genres</th>
</tr>
</thead>
<tbody>{$rows}</tbody>
</table>
HTML;
}) ?>
<?php endif ?>
</main>
<script defer="defer" src="<?= $_->assetUrl('js/tables.min.js') ?>"></script>

View File

@ -0,0 +1,11 @@
<select name="media_id[]" id="media_id" multiple size="13">
<?php foreach ($media_items as $group => $items): ?>
<optgroup label='<?= $group ?>'>
<?php foreach ($items as $id => $name): ?>
<option <?= in_array($id, ($item['media_id'] ?? []), FALSE) ? 'selected="selected"' : '' ?> value="<?= $id ?>">
<?= $name ?>
</option>
<?php endforeach ?>
</optgroup>
<?php endforeach ?>
</select>

5
app/views/error.php Normal file
View File

@ -0,0 +1,5 @@
<main>
<h1><?= $title ?></h1>
<h2><?= $message ?></h2>
<div><?= $long_message ?></div>
</main>

View File

@ -1,2 +1,16 @@
<section id="loading-shadow" hidden="hidden">
<div class="loading-wrapper">
<div class="loading-content">
<h3>Updating List Item...</h3>
<div class="cssload-loader">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
</div>
</div>
</section>
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
<script async="async" defer="defer" src="<?= $_->assetUrl('js/scripts.min.js') ?>"></script>
</body>
</html>
</html>

View File

@ -1,35 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title><?= $title ?></title>
<meta charset="utf-8" />
<link rel="stylesheet" href="<?= asset_url('css.php?g=base') ?>" />
<script>
var BASE_URL = "<?= base_url($url_type) ?>";
var CONTROLLER = "<?= $url_type ?>";
</script>
<meta http-equiv="cache-control" content="no-store" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=1" />
<link rel="stylesheet" href="<?= $_->assetUrl('css/' . $_->config->get('theme') . '.min.css') ?>" />
<link rel="<?= $_->config->get('theme') === 'dark' ? '' : 'alternate ' ?>stylesheet" title="Dark Theme" href="<?= $_->assetUrl('css/dark.min.css') ?>" />
<link rel="icon" href="<?= $_->assetUrl('images/icons/favicon.ico') ?>" />
<link rel="apple-touch-icon" sizes="57x57" href="<?= $_->assetUrl('images/icons/apple-icon-57x57.png') ?>">
<link rel="apple-touch-icon" sizes="60x60" href="<?= $_->assetUrl('images/icons/apple-icon-60x60.png') ?>">
<link rel="apple-touch-icon" sizes="72x72" href="<?= $_->assetUrl('images/icons/apple-icon-72x72.png') ?>">
<link rel="apple-touch-icon" sizes="76x76" href="<?= $_->assetUrl('images/icons/apple-icon-76x76.png') ?>">
<link rel="apple-touch-icon" sizes="114x114" href="<?= $_->assetUrl('images/icons/apple-icon-114x114.png') ?>">
<link rel="apple-touch-icon" sizes="120x120" href="<?= $_->assetUrl('images/icons/apple-icon-120x120.png') ?>">
<link rel="apple-touch-icon" sizes="144x144" href="<?= $_->assetUrl('images/icons/apple-icon-144x144.png') ?>">
<link rel="apple-touch-icon" sizes="152x152" href="<?= $_->assetUrl('images/icons/apple-icon-152x152.png') ?>">
<link rel="apple-touch-icon" sizes="180x180" href="<?= $_->assetUrl('images/icons/apple-icon-180x180.png') ?>">
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_->assetUrl('images/icons/android-icon-192x192.png') ?>">
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_->assetUrl('images/icons/favicon-32x32.png') ?>">
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_->assetUrl('images/icons/favicon-96x96.png') ?>">
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_->assetUrl('images/icons/favicon-16x16.png') ?>">
</head>
<body class="<?= $url_type ?> list">
<h1 class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1"><?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]</span>
<span class="flex-no-wrap small-font">
<?php if (is_logged_in()): ?>
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>]
<?php else: ?>
[<a href="<?= full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
<?php endif ?>
</span>
</h1>
<nav>
<ul>
<?php foreach($nav_routes as $title => $nav_path): ?>
<li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= full_url($nav_path, $url_type) ?>"><?= $title ?></a></li>
<?php endforeach ?>
</ul>
<br />
<ul>
<li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= full_url($route_path, $url_type) ?>">Cover View</a></li>
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
</ul>
</nav>
<br />
<body class="<?= $_->escape->attr($url_type) ?> list">
<?php include 'setup-check.php' ?>
<header>
<?php
include 'main-menu.php';
if(isset($message) && is_array($message))
{
foreach($message as $m)
{
$message = $m['message'];
$message_type = $m['message_type'];
include 'message.php';
}
}
?>
</header>

47
app/views/history.php Normal file
View File

@ -0,0 +1,47 @@
<main class="details fixed">
<?php if (empty($items)): ?>
<h3>No recent history.</h3>
<?php else: ?>
<section>
<?php foreach ($items as $name => $item): ?>
<article class="flex flex-no-wrap flex-justify-start">
<section class="flex-self-center history-img">
<a href="<?= $item['url'] ?>">
<?= $_->h->img(
$item['coverImg'],
['width' => '110px', 'height' => '156px'],
) ?>
</a>
</section>
<section class="flex-self-center">
<?= $_->h->a($item['url'], $item['title']) ?>
<br />
<br />
<?= $item['action'] ?>
<br />
<small>
<?php if ( ! empty($item['dateRange'])):
[$startDate, $endDate] = array_map(
fn ($date) => $date->format('l, F d'),
$item['dateRange']
);
[$startTime, $endTime] = array_map(
fn ($date) => $date->format('h:i:s A'),
$item['dateRange']
);
?>
<?php if ($startDate === $endDate): ?>
<?= "{$startDate}, {$startTime} &ndash; {$endTime}" ?>
<?php else: ?>
<?= "{$startDate} {$startTime} &ndash; {$endDate} {$endTime}" ?>
<?php endif ?>
<?php else: ?>
<?= $item['updated']->format('l, F d h:i:s A') ?>
<?php endif ?>
</small>
</section>
</article>
<?php endforeach ?>
</section>
<?php endif ?>
</main>

6
app/views/js-warning.php Normal file
View File

@ -0,0 +1,6 @@
<noscript>
<div class="message error">
<span class="icon"></span>
This feature requires Javascript to function :(
</div>
</noscript>

View File

@ -1,17 +1,16 @@
<main>
<h2><?= $_->config->get('whose_list'); ?>'s Login</h2>
<?= $message ?>
<aside>
<form method="post" action="<?= full_url('/login', $url_type) ?>">
<dl>
<dt><label for="username">Username: </label></dt>
<dd><input type="text" id="username" name="username" required="required" /></dd>
<dt><label for="password">Password: </label></dt>
<dd><input type="password" id="password" name="password" required="required" /></dd>
<dt>&nbsp;</dt>
<dd><input type="submit" value="Login" /></dd>
</dl>
</form>
</aside>
<form method="post" action="<?= $_->urlFromRoute('login.post') ?>">
<table class="form invisible">
<tr>
<td><label for="password">Password: </label></td>
<td><input type="password" id="password" name="password" required="required" /></td>
</tr>
<tr>
<td>&nbsp;</td>
<td><button type="submit">Login</button></td>
</tr>
</table>
</form>
</main>

114
app/views/main-menu.php Normal file
View File

@ -0,0 +1,114 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 8.1
*
* @copyright 2015 - 2023 Timothy J. Warren <tim@timshome.page>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient;
$whose = $_->config->get('whose_list') . "'s ";
$lastSegment = $_->lastSegment();
$extraSegment = $lastSegment === 'list' ? '/list' : '';
$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( ! str_contains($route_path, 'collection')): ?>
<?= $_->h->a(
$_->defaultUrl($url_type),
$whose . ucfirst($url_type) . ' List',
['aria-current'=> 'page']
) ?>
<?php if($_->config->get("show_{$url_type}_collection")): ?>
[<?= $_->h->a(
$_->urlFromRoute("{$url_type}.collection.view") . $extraSegment,
ucfirst($url_type) . ' Collection'
) ?>]
<?php endif ?>
<?php if($_->config->get("show_{$other_type}_collection")): ?>
[<?= $_->h->a(
$_->urlFromRoute("{$other_type}.collection.view") . $extraSegment,
ucfirst($other_type) . ' Collection'
) ?>]
<?php endif ?>
[<?= $_->h->a(
$_->defaultUrl($other_type) . $extraSegment,
ucfirst($other_type) . ' List'
) ?>]
<?php else: ?>
<?= $_->h->a(
$_->urlFromRoute("{$url_type}.collection.view") . $extraSegment,
$whose . ucfirst($url_type) . ' Collection',
['aria-current'=> 'page']
) ?>
<?php if($_->config->get("show_{$other_type}_collection")): ?>
[<?= $_->h->a(
$_->urlFromRoute("{$other_type}.collection.view") . $extraSegment,
ucfirst($other_type) . ' Collection'
) ?>]
<?php endif ?>
[<?= $_->h->a($_->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
[<?= $_->h->a($_->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
<?php endif ?>
<?php if ($_->isAuthenticated() && $_->config->get(['cache', 'driver']) !== 'null'): ?>
<span class="flex-no-wrap small-font">
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
</span>
<?php endif ?>
</span>
<span class="flex-no-wrap small-font">[<?= $_->h->a(
$_->urlFromRoute('default_user_info'),
'About '. $_->config->get('whose_list')
) ?>]</span>
<?php if ($_->isAuthenticated()): ?>
<span class="flex-no-wrap small-font">
<?= $_->h->a(
$_->urlFromRoute('settings'),
'Settings',
['class' => 'bracketed']
) ?>
</span>
<span class="flex-no-wrap small-font">
<?= $_->h->a(
$_->urlFromRoute('logout'),
'Logout',
['class' => 'bracketed']
) ?>
</span>
<?php else: ?>
<span class="flex-no-wrap small-font">
[<?= $_->h->a($_->urlFromRoute('login'), "{$whose} Login") ?>]
</span>
<?php endif ?>
</div>
<?php if ($_->isViewPage() && ($hasAnime || $hasManga)): ?>
<nav>
<?= $_->h->menu($menu_name) ?>
<?php if (stripos($GLOBALS['_SERVER']['REQUEST_URI'], 'history') === FALSE): ?>
<br />
<ul>
<?php $currentView = Util::eq('list', $lastSegment) ? 'list' : 'cover' ?>
<li class="<?= Util::isNotSelected('list', $lastSegment) ?>">
<a aria-current="<?= Util::ariaCurrent($currentView === 'cover') ?>"
href="<?= $_->urlFromPath($route_path) ?>">Cover View</a>
</li>
<li class="<?= Util::isSelected('list', $lastSegment) ?>">
<a aria-current="<?= Util::ariaCurrent($currentView === 'list') ?>"
href="<?= $_->urlFromPath("{$route_path}/list") ?>">List View</a>
</li>
</ul>
<?php endif ?>
</nav>
<?php endif ?>

40
app/views/manga/add.php Normal file
View File

@ -0,0 +1,40 @@
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Add Manga to your List</h2>
<form action="<?= $action_url ?>" method="post">
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
<section>
<div class="cssload-loader" hidden="hidden">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for manga by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>
<br />
<table class="invisible form">
<tbody>
<tr>
<td><label for="status">Reading Status</label></td>
<td>
<select name="status" id="status">
<?php foreach($status_list as $status_key => $status_title): ?>
<option value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" name="type" value="manga" />
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
</main>
<?php endif ?>

View File

@ -1,49 +1,29 @@
<main>
<main class="media-list">
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute('manga.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<?php if (empty($items)): ?>
<section class="status">
<h2><?= $name ?></h2>
<h2><?= $_->escape->html($name) ?></h2>
<h3>There's nothing here!</h3>
</section>
<?php else: ?>
<section class="status">
<h2><?= $_->escape->html($name) ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<article class="media" id="manga-<?= $item['id'] ?>">
<?php if (is_logged_in()): ?>
<div class="edit_buttons" hidden>
<button class="plus_one_chapter">+1 Chapter</button>
<button class="plus_one_volume">+1 Volume</button>
</div>
<?php endif ?>
<img src="<?= $item['manga']['poster_image'] ?>" />
<div class="name">
<a href="https://hummingbird.me/manga/<?= $item['manga_id'] ?>">
<?= $item['manga']['romaji_title'] ?>
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
</a>
</div>
<div class="table">
<div class="row">
<div class="user_rating">Rating: <?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div>
</div>
<div class="row">
<div class="chapter_completion">
Chapters: <span class="chapters_read"><?= $item['chapters_read'] ?></span> /
<span class="chapter_count"><?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></span>
</div>
</div>
<div class="row">
<div class="volume_completion">
Volumes: <span class="volumes_read"><?= $item['volumes_read'] ?></span> /
<span class="volume_count"><?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></span>
</div>
</div>
</div>
<?php /*<div class="medium_metadata">
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
</div> */ ?>
</article>
<?= $component->mangaCover($item, $name) ?>
<?php endforeach ?>
</section>
</section>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
</main>
<?php if (is_logged_in()): ?>
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
<?php endif ?>

105
app/views/manga/details.php Normal file
View File

@ -0,0 +1,105 @@
<main class="details fixed">
<section class="flex flex-no-wrap">
<aside class="info">
<?= $_->h->img($data['cover_image'], ['class' => 'cover', 'width' => '350']) ?>
<br />
<table class="media-details">
<tr>
<td class="align-right">Publishing Status</td>
<td><?= $data['status'] ?></td>
</tr>
<tr>
<td>Manga Type</td>
<td><?= ucfirst(strtolower($data['manga_type'])) ?></td>
</tr>
<?php if ( ! empty($data['volume_count'])): ?>
<tr>
<td>Volume Count</td>
<td><?= $data['volume_count'] ?></td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['chapter_count'])): ?>
<tr>
<td>Chapter Count</td>
<td><?= $data['chapter_count'] ?></td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
</td>
</tr>
<?php endif ?>
<?php if (count($data['links']) > 0): ?>
<tr>
<td>External Links</td>
<td>
<?php foreach ($data['links'] as $urlName => $externalUrl): ?>
<a rel='external' href="<?= $externalUrl ?>"><?= $urlName ?></a><br />
<?php endforeach ?>
</td>
</tr>
<?php endif ?>
<tr>
<td>Genres</td>
<td>
<?= implode(', ', $data['genres']); ?>
</td>
</tr>
</table>
<br />
</aside>
<article class="text">
<h2 class="toph"><?= $data['title'] ?></h2>
<?php foreach ($data['titles_more'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
<br />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['synopsis']) ?></p>
</div>
</article>
</section>
<?php if (count($data['characters']) > 0): ?>
<h2>Characters</h2>
<?= $component->tabs('manga-characters', $data['characters'], static function($list, $role) use ($component, $helper, $_) {
$rendered = [];
foreach ($list as $id => $char)
{
$rendered[] = $component->character(
$char['name'],
$_->urlFromRoute('character', ['slug' => $char['slug']]),
$_->h->img($char['image'], ['loading' => 'lazy']),
($role !== 'main') ? 'small-character' : 'character'
);
}
return implode('', array_map('mb_trim', $rendered));
}) ?>
<?php endif ?>
<?php if (count($data['staff']) > 0): ?>
<h2>Staff</h2>
<?= $component->verticalTabs('manga-staff', $data['staff'],
fn($people) => implode('', array_map(
fn ($person) => $component->character(
$person['name'],
$_->urlFromRoute('person', ['slug' => $person['slug']]),
$_->h->img($person['image']),
),
$people
))
) ?>
<?php endif ?>
</main>

104
app/views/manga/edit.php Normal file
View File

@ -0,0 +1,104 @@
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>
Edit Manga List Item
</h2>
<form action="<?= $action ?>" method="post">
<table class="invisible form">
<thead>
<tr>
<th>
<h3><?= $_->escape->html($item['manga']['title']) ?></h3>
<?php foreach ($item['manga']['titles'] as $title): ?>
<h4><?= $_->escape->html($title) ?></h4>
<?php endforeach ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="9">
<?= $_->h->img($item['manga']['image']) ?>
</td>
</tr>
<tr>
<td><label for="status">Reading Status</label></td>
<td>
<select name="status" id="status">
<?php foreach ($status_list as $val => $status): ?>
<option <?php if ($item['reading_status'] === $val): ?>selected="selected"<?php endif ?>
value="<?= $val ?>"><?= $status ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><label for="series_rating">Rating</label></td>
<td>
<input type="number" min="0" max="10" maxlength="2" name="new_rating"
value="<?= $item['user_rating'] ?>" id="series_rating" size="2"/> / 10
</td>
</tr>
<tr>
<td><label for="chapters_read">Chapters Read</label></td>
<td>
<input type="number" min="0" name="chapters_read" id="chapters_read"
value="<?= $item['chapters']['read'] ?>"/> / <?= $item['chapters']['total'] ?>
</td>
</tr>
<tr>
<td><label for="rereading_flag">Rereading?</label></td>
<td>
<input type="checkbox" name="rereading" id="rereading_flag"
<?php if ($item['rereading'] === TRUE): ?>checked="checked"<?php endif ?>
/>
</td>
</tr>
<tr>
<td><label for="reread_count">Reread Count</label></td>
<td>
<input type="number" min="0" id="reread_count" name="reread_count"
value="<?= $item['reread'] ?>"/>
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td>
<textarea name="notes" id="notes"><?= $_->escape->html($item['notes']) ?></textarea>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id"/>
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id"/>
<input type="hidden" value="<?= $item['manga']['slug'] ?>" name="manga_id"/>
<input type="hidden" value="<?= $item['user_rating'] ?>" name="old_rating"/>
<input type="hidden" value="true" name="edit"/>
<button type="submit">Submit</button>
</td>
</tr>
</tbody>
</table>
</form>
<fieldset>
<legend>Danger Zone</legend>
<form class="js-delete" action="<?= $_->urlFromRoute('manga.delete') ?>" method="post">
<table class="form invisible">
<tbody>
<tr>
<td class="danger">
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id"/>
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id"/>
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>
</tbody>
</table>
</form>
</fieldset>
</main>
<?php endif ?>

View File

@ -1,33 +1,78 @@
<main>
<main class="media-list">
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute('manga.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Rating</th>
<th>Chapters</th>
<th>Volumes</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="manga-<?= $item['manga']['id'] ?>">
<td class="align_left">
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
<?= $item['manga']['romaji_title'] ?>
</a>
<?= (array_key_exists('english_title', $item['manga'])) ? " &middot; " . $item['manga']['english_title'] : "" ?>
</td>
<td><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</td>
<td><?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></td>
<td><?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></td>
<td><?= $item['manga']['manga_type'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php if (empty($items)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<table class='media-wrap'>
<thead>
<tr>
<?php if ($_->isAuthenticated()): ?>
<td>&nbsp;</td>
<?php endif ?>
<th>Title</th>
<th class='numeric'>Score</th>
<th class='numeric'>Completed Chapters</th>
<th>Attributes</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="manga-<?= $item['id'] ?>">
<?php if($_->isAuthenticated()): ?>
<td>
<a class="bracketed" href="<?= $_->urlFromRoute('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
]) ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left">
<a href="<?= $_->urlFromRoute('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $item['manga']['title'] ?>
</a>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><?= $title ?>
<?php endforeach ?>
</td>
<td><?= $item['user_rating'] ?> / 10</td>
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
<td>
<ul>
<?php if ($item['reread'] == 1): ?>
<li>Reread once</li>
<?php elseif ($item['reread'] == 2): ?>
<li>Reread twice</li>
<?php elseif ($item['reread'] == 3): ?>
<li>Reread thrice</li>
<?php elseif ($item['reread'] > 3): ?>
<li>Reread <?= $item['reread'] ?> times</li>
<?php endif ?>
<?php foreach(['rereading'] as $attr): ?>
<?php if($item[$attr]): ?>
<li><?= ucfirst($attr); ?></li>
<?php endif ?>
<?php endforeach ?>
</ul>
</td>
<td><?= $item['manga']['type'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
</main>
<script src="<?= asset_url('js.php?g=table') ?>"></script>
<script defer="defer" src="<?= $_->assetUrl('js/tables.min.js') ?>"></script>

View File

@ -1,5 +1,5 @@
<div class="message <?= $stat_class ?>">
<div class="message <?= $_->escape->attr($message_type) ?>">
<span class="icon"></span>
<?= $message ?>
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
<?= $_->escape->html($message) ?>
<span class="close"></span>
</div>

View File

@ -0,0 +1,104 @@
<main class="details fixed">
<section class="flex flex-no-wrap">
<div>
<?= $_->h->img($data['image'], ['class' => 'cover' ]) ?>
</div>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
<?php foreach ($data['names'] as $name): ?>
<h3><?= $name ?></h3>
<?php endforeach ?>
<?php if ( ! empty($data['birthday'])): ?>
<h4><?= $data['birthday'] ?></h4>
<?php endif ?>
<br />
<hr />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
</div>
</div>
</section>
<?php if ( ! empty($data['staff'])): ?>
<section>
<h3>Castings</h3>
<div class="vertical-tabs">
<?php $i = 0 ?>
<?php foreach ($data['staff'] as $role => $entries): ?>
<div class="tab">
<input
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>
<?php foreach ($entries as $type => $casting): ?>
<?php if (isset($entries['manga'], $entries['anime'])): ?>
<h4><?= ucfirst($type) ?></h4>
<?php endif ?>
<section class="content media-wrap flex flex-wrap flex-justify-start">
<?php foreach ($casting as $sid => $series): ?>
<?php $mediaType = in_array($type, ['anime', 'manga'], TRUE) ? $type : 'anime'; ?>
<?= $_->component->media(
$series['titles'],
$_->urlFromRoute("{$mediaType}.details", ['id' => $series['slug']]),
$_->h->img($series['image'], ['width' => 220, 'loading' => 'lazy'])
) ?>
<?php endforeach; ?>
</section>
<?php endforeach ?>
</div>
<?php $i++ ?>
<?php endforeach ?>
</div>
</section>
<?php endif ?>
<?php if ( ! empty($data['characters'])): ?>
<section>
<h3>Voice Acting Roles</h3>
<?= $component->tabs('voice-acting-roles', $data['characters'], static function ($characterList) use ($component, $helper, $_) {
$voiceRoles = [];
foreach ($characterList as $cid => $item):
$character = $component->character(
$item['character']['canonicalName'],
$_->urlFromRoute('character', ['slug' => $item['character']['slug']]),
$_->h->img($item['character']['image'], ['loading' => 'lazy']),
);
$medias = [];
foreach ($item['media'] as $sid => $series)
{
$medias[] = $component->media(
$series['titles'],
$_->urlFromRoute('anime.details', ['id' => $series['slug']]),
$_->h->img($series['image'], ['width' => 220, 'loading' => 'lazy'])
);
}
$media = implode('', array_map('mb_trim', $medias));
$voiceRoles[] = <<<HTML
<tr>
<td>{$character}</td>
<td>
<section class="align-left media-wrap">{$media}</section>
</td>
</tr>
HTML;
endforeach;
$roles = implode('', array_map('mb_trim', $voiceRoles));
return <<<HTML
<table class="borderless max-table">
<thead>
<tr>
<th>Character</th>
<th>Series</th>
</tr>
</thead>
<tbody>{$roles}</tbody>
</table>
HTML;
}) ?>
</section>
<?php endif ?>
</main>

View File

@ -0,0 +1,24 @@
<?php if ( ! $hasRequiredAnilistConfig): ?>
<p class="static-message info">See the <a href="https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient/wiki/anilist">wiki</a> to learn how to set up Anilist integration. </p>
<?php else: ?>
<?php $auth = $anilistModel->checkAuth(); ?>
<?php if (array_key_exists('errors', $auth)): ?>
<p class="static-message error">Anilist API Client is Not Authorized.</p>
<?= $_->h->a(
$_->urlFromRoute('anilist-redirect'),
'Link Anilist Account',
['class' => 'bracketed user-btn']
) ?>
<?php else: ?>
<?php $expires = $_->config->get(['anilist', 'access_token_expires']); ?>
<p class="static-message info">
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
</p>
<?php require __DIR__ . '/_form.php' ?>
<?= $_->h->a(
$_->urlFromRoute('anilist-redirect'),
'Update Access Token',
['class' => 'bracketed user-btn']
) ?>
<?php endif ?>
<?php endif ?>

View File

@ -0,0 +1,5 @@
<article>
<label for="<?= $fieldName ?>"><?= $field['title'] ?></label><br />
<small><?= $field['description'] ?></small><br />
<?= $_->h->field($fieldName, $field); ?>
</article>

View File

@ -0,0 +1,24 @@
<?php
// Higher scoped variables:
// $fields
// $hiddenFields
// $nestedPrefix
?>
<?php foreach ($fields as $name => $field): ?>
<?php
$fieldName = ($section === 'config' || $nestedPrefix !== 'config')
? "{$nestedPrefix}[{$name}]"
: "{$nestedPrefix}[{$section}][{$name}]";
?>
<?php if ($field['type'] === 'subfield'): ?>
<section>
<h4><?= $field['title'] ?></h4>
<?php include '_subfield.php'; ?>
</section>
<?php elseif ( ! empty($field['display'])): ?>
<?php include '_field.php' ?>
<?php else: ?>
<?php $hiddenFields[] = $_->h->field($fieldName, $field); ?>
<?php endif ?>
<?php endforeach ?>

View File

@ -0,0 +1,20 @@
<?php
// Higher scoped variables:
// $field
// $fields
// $hiddenFields
// $nestedPrefix
?>
<?php foreach ($field['fields'] as $name => $field): ?>
<?php
$fieldName = ($section === 'config' || $nestedPrefix !== 'config')
? "{$nestedPrefix}[{$name}]"
: "{$nestedPrefix}[{$section}][{$name}]";
?>
<?php if ( ! empty($field['display'])): ?>
<?php include '_field.php' ?>
<?php else: ?>
<?php $hiddenFields[] = $_->h->field($fieldName, $field); ?>
<?php endif ?>
<?php endforeach ?>

View File

@ -0,0 +1,46 @@
<?php
if ( ! $_->isAuthenticated())
{
echo '<h1>Not Authorized</h1>';
return;
}
$sectionMapping = [
'anilist' => 'Anilist API Integration',
'config' => 'General Settings',
'cache' => 'Caching',
'database' => 'Collection Database Settings',
];
$hiddenFields = [];
$nestedPrefix = 'config';
?>
<form action="<?= $_->urlFromRoute('settings-post') ?>" method="POST">
<main class='settings form'>
<button type="submit">Save Changes</button>
<div class="tabs">
<?php $i = 0; ?>
<?php foreach ($form as $section => $fields): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="settings-tab<?= $i ?>"
name="settings-tabs"
/>
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
<section class="content">
<?php
($section === 'anilist')
? require __DIR__ . '/_anilist.php'
: require __DIR__ . '/_form.php'
?>
</section>
<?php $i++; ?>
<?php endforeach ?>
</div>
<br />
<?php foreach ($hiddenFields as $field): ?>
<?= $field->__toString() ?>
<?php endforeach ?>
<button type="submit">Save Changes</button>
</main>
</form>

30
app/views/setup-check.php Normal file
View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
use function Aviat\AnimeClient\checkFolderPermissions;
$setupErrors = checkFolderPermissions($_->config);
?>
<?php if ( ! empty($setupErrors)): ?>
<aside class="message error">
<h1>Issues with server setup:</h1>
<?php if (array_key_exists('missing', $setupErrors)): ?>
<h3>The following folders need to be created, and writable.</h3>
<ul>
<?php foreach ($setupErrors['missing'] as $error): ?>
<li><?= $error ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<?php if (array_key_exists('writable', $setupErrors)): ?>
<h3>The following folders are not writable by the server.</h3>
<ul>
<?php foreach($setupErrors['writable'] as $error): ?>
<li><?= $error ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
</aside>
<?php endif ?>

110
app/views/user/details.php Normal file
View File

@ -0,0 +1,110 @@
<?php
use Aviat\AnimeClient\Kitsu;
?>
<main class="user-page details">
<h2 class="toph">
About
<?= $_->h->a(
"https://kitsu.io/users/{$data['slug']}",
$data['name'], [
'title' => 'View profile on Kitsu'
])
?>
</h2>
<section class="flex flex-no-wrap">
<aside class="info">
<table class="media-details invisible">
<tr>
<?php if($data['avatar'] !== null): ?>
<td><?= $_->h->img($data['avatar'], ['alt' => '', 'width' => '225']); ?></td>
<?php endif ?>
<td><?= $_->escape->html($data['about']) ?></td>
</tr>
</table>
<br />
<table class="media-details">
<?php foreach ([
'joinDate' => 'Joined',
'birthday' => 'Birthday',
'gender' => 'Gender',
'location' => 'Location'
] as $key => $label): ?>
<?php if ($data[$key] !== null): ?>
<tr>
<td><?= $label ?></td>
<td><?= $data[$key] ?></td>
</tr>
<?php endif ?>
<?php endforeach; ?>
<?php if ($data['website'] !== null): ?>
<tr>
<td>Website</td>
<td><?= $_->h->a($data['website'], $data['website']) ?></td>
</tr>
<?php endif ?>
<?php if ($data['waifu']['character'] !== null): ?>
<tr>
<td><?= $_->escape->html($data['waifu']['label']) ?></td>
<td>
<?php
$character = $data['waifu']['character'];
echo $_->component->character(
$character['names']['canonical'],
$_->urlFromRoute('character', ['slug' => $character['slug']]),
$_->h->img(Kitsu::getImage($character))
);
?>
</td>
</tr>
<?php endif ?>
</table>
<h3>User Stats</h3><br />
<table class="media-details">
<?php foreach($data['stats'] as $label => $stat): ?>
<tr>
<td><?= $label ?></td>
<td><?= $stat ?></td>
</tr>
<?php endforeach ?>
</table>
</aside>
<article>
<?php if ( ! empty($data['favorites'])): ?>
<h3>Favorites</h3>
<?= $_->component->tabs('user-favorites', $data['favorites'], static function ($items, $type) use ($_) {
if ($type === 'character')
{
uasort($items, fn ($a, $b) => $a['names']['canonical'] <=> $b['names']['canonical']);
}
else
{
uasort($items, fn ($a, $b) => $a['titles']['canonical'] <=> $b['titles']['canonical']);
}
$rendered = array_map(fn ($item) => match ($type) {
'character' => $_->component->character(
$item['names']['canonical'],
$_->urlFromRoute('character', ['slug' => $item['slug']]),
$_->h->img(Kitsu::getImage($item))
),
default => $_->component->media(
array_merge(
[$item['titles']['canonical']],
Kitsu::getFilteredTitles($item['titles']),
),
$_->urlFromRoute("{$type}.details", ['id' => $item['slug']]),
$_->h->img(Kitsu::getPosterImage($item), ['width' => 220]),
),
}, $items);
return implode('', array_map('mb_trim', $rendered));
}, 'content full-width media-wrap') ?>
<?php endif ?>
</article>
</section>
</main>

84
build/phpcs.xml Normal file
View File

@ -0,0 +1,84 @@
<?xml version="1.0"?>
<ruleset name="Tim's Coding Standard">
<description>A variation of the CodeIgniter standard</description>
<file>../src/</file>
<encoding>utf-8</encoding>
<rule ref="Generic.Files.LineEndings">
<properties>
<property name="eolChar" value="\n"/>
</properties>
</rule>
<!-- PHP files should OMIT the closing PHP tag -->
<rule ref="Zend.Files.ClosingTag"/>
<!-- Always use full PHP opening tags -->
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
<!-- Constants should always be fully uppercase -->
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
<!-- TRUE, FALSE, and NULL keywords should always be fully uppercase -->
<rule ref="Generic.PHP.UpperCaseConstant"/>
<!-- One statement per line -->
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
<!-- Classes and functions should be commented -->
<rule ref="PEAR.Commenting.ClassComment">
<exclude name="PEAR.Commenting.ClassComment.MissingCategoryTag" />
<exclude name="PEAR.Commenting.ClassComment.MissingPackageTag" />
<exclude name="PEAR.Commenting.ClassComment.MissingAuthorTag" />
<exclude name="PEAR.Commenting.ClassComment.MissingLicenseTag" />
<exclude name="PEAR.Commenting.ClassComment.MissingLinkTag" />
</rule>
<rule ref="PEAR.Commenting.FunctionComment">
<!-- Exclude this sniff because it doesn't understand multiple types -->
<exclude name="PEAR.Commenting.FunctionComment.MissingParamComment" />
<exclude name="PEAR.Commenting.FunctionComment.SpacingAfterParamType" />
<exclude name="PEAR.Commenting.FunctionComment.SpacingAfterParamName" />
</rule>
<!-- Use warnings for docblock comments for files and variables, since nothing is clearly explained -->
<rule ref="PEAR.Commenting.FileComment">
<exclude name="PEAR.Commenting.FileComment.InvalidVersion" />
<exclude name="PEAR.Commenting.FileComment.MissingCategoryTag" />
<properties>
<property name="error" value="false"/>
</properties>
</rule>
<rule ref="Squiz.Commenting.FunctionCommentThrowTag"/>
<rule ref="Squiz.Commenting.VariableComment">
<properties>
<property name="error" value="false"/>
</properties>
</rule>
<!-- Use Allman style indenting. With the exception of Class declarations,
braces are always placed on a line by themselves, and indented at the same level as the control statement that "owns" them. -->
<rule ref="Generic.Functions.OpeningFunctionBraceBsdAllman"/>
<rule ref="PEAR.WhiteSpace.ScopeClosingBrace">
<exclude name="PEAR.WhiteSpace.ScopeClosingBrace.BreakIndent" />
</rule>
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
<!-- Use only short array syntax -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax" />
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array" value="create_function=>null,eval=>null" />
</properties>
</rule>
<!-- Inherit CodeIgniter Rules -->
<rule ref="./CodeIgniter">
<properties>
<property name="error" value="false" />
</properties>
</rule>
</ruleset>

32
build/phpunit.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
<coverage>
<report>
<clover outputFile="logs/clover.xml"/>
<html outputDirectory="../coverage"/>
</report>
</coverage>
<testsuites>
<testsuite name="AnimeClient">
<directory>../tests/AnimeClient</directory>
</testsuite>
<testsuite name="Ion">
<directory>../tests/Ion</directory>
</testsuite>
</testsuites>
<logging>
<junit outputFile="logs/junit.xml"/>
</logging>
<php>
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0"/>
<server name="HTTP_HOST" value="localhost"/>
<server name="SERVER_NAME" value="localhost"/>
<server name="REQUEST_URI" value="/"/>
<server name="REQUEST_METHOD" value="GET"/>
</php>
<source>
<include>
<directory suffix=".php">../src</directory>
</include>
</source>
</phpunit>

View File

@ -1,11 +1,78 @@
{
"require": {
"guzzlehttp/guzzle": "5.3.*",
"filp/whoops": "1.1.*",
"aura/router": "2.2.*",
"aura/web": "2.0.*",
"aviat4ion/query": "2.0.*",
"robmorgan/phinx": "*",
"abeautifulsite/simpleimage": "*"
"name": "aviat/hummingbird-anime-client",
"description": "A self-hosted anime/manga client for Kitsu.",
"license": "MIT",
"authors": [
{
"name": "Timothy J. Warren",
"email": "tim@timshomepage.net",
"homepage": "https://timshomepage.net",
"role": "Developer"
}
}
],
"autoload": {
"files": [
"src/Ion/functions.php",
"src/AnimeClient.php",
"src/AnimeClient/constants.php"
],
"psr-4": {
"Aviat\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Aviat\\AnimeClient\\Tests\\": "tests/AnimeClient",
"Aviat\\Ion\\Tests\\": "tests/Ion"
}
},
"config": {
"lock": false
},
"require": {
"amphp/http-client": "^v5.0.0",
"aura/html": "^2.5.0",
"aura/router": "^3.3.0",
"aura/session": "^2.1.0",
"aviat/banker": "^4.1.2",
"aviat/query": "^4.1.0",
"ext-dom": "*",
"ext-gd": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"laminas/laminas-diactoros": "^3.0.0",
"laminas/laminas-httphandlerrunner": "^2.6.1",
"maximebf/consolekit": "^1.0.3",
"monolog/monolog": "^3.0.0",
"php": ">= 8.2.0",
"psr/http-message": "^1.0.1 || ^2.0.0",
"symfony/polyfill-mbstring": "^1.0.0",
"symfony/polyfill-util": "^1.0.0",
"tracy/tracy": "^2.8.0",
"yosymfony/toml": "^1.0.4"
},
"require-dev": {
"phpstan/phpstan": "^1.2.0",
"phpunit/phpunit": "^10.0.0",
"roave/security-advisories": "dev-master",
"spatie/phpunit-snapshot-assertions": "^5.0.1"
},
"scripts": {
"build:css": "cd public && npm run build:css && cd ..",
"build:js": "cd public && npm run build:js && cd ..",
"coverage": "php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/phpunit -c build",
"phpstan": "phpstan analyse -c phpstan.neon",
"watch:css": "cd public && npm run watch:css",
"watch:js": "cd public && npm run watch:js",
"test": "vendor/bin/phpunit -c build --no-coverage",
"test-update": "vendor/bin/phpunit -c build --no-coverage -d --update-snapshots"
},
"scripts-descriptions": {
"build:css": "Generate browser css",
"coverage": "Generate a test coverage report",
"phpstan": "Run PHP Static analysis",
"test": "Run the unit tests"
}
}

33
console Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
// Set up autoloader for third-party dependencies
require_once __DIR__ . '/vendor/autoload.php';
use Aviat\AnimeClient\Command;
use ConsoleKit\Console;
$GLOBALS['_SERVER']['HTTP_HOST'] = 'localhost';
const APP_DIR = __DIR__ . '/app';
const TEMPLATE_DIR = APP_DIR . '/templates';
// -----------------------------------------------------------------------------
// Start console script
// -----------------------------------------------------------------------------
try
{
(new Console([
'clear:cache' => Command\CacheClear::class,
'clear:thumbnails' => Command\ClearThumbnails::class,
'refresh:cache' => Command\CachePrime::class,
'refresh:thumbnails' => Command\UpdateThumbnails::class,
'lists:sync' => Command\SyncLists::class,
'sync:lists' => Command\SyncLists::class
]))->run();
}
catch (Throwable)
{
}

70
frontEndSrc/css.js Normal file
View File

@ -0,0 +1,70 @@
/**
* Script for optimizing css
*/
const fs = require('fs');
const postcss = require('postcss');
const atImport = require('postcss-import');
const cssNext = require('postcss-preset-env');
const cssNano = require('cssnano');
const lightCss = fs.readFileSync('css/light.css', 'utf-8');
const darkCss = fs.readFileSync('css/src/dark-override.css', 'utf-8');
const fullDarkCss = fs.readFileSync('css/dark.css', 'utf-8');
const minOptions = {
autoprefixer: false,
colormin: false,
minifyFontValues: false,
options: {
sourcemap: false
}
};
const processOptions = {
browser: '> 0.5%',
features: {
'custom-properties': true,
},
stage: 0,
};
try {
(async () => {
// Basic theme
const lightMin = await postcss()
.use(atImport())
.use(cssNext(processOptions))
.use(cssNano(minOptions))
.process(lightCss, {
from: 'css/light.css',
to: '/public/css/light.min.css',
}).catch(console.error);
fs.writeFileSync('../public/css/light.min.css', lightMin.css);
// Dark theme
const darkFullMin = await postcss()
.use(atImport())
.use(cssNext(processOptions))
.use(cssNano(minOptions))
.process(fullDarkCss, {
from: 'css/dark.css',
to: '/public/css/dark.min.css',
});
fs.writeFileSync('../public/css/dark.min.css', darkFullMin.css);
// Dark override
const darkMin = await postcss()
.use(atImport())
.use(cssNext(processOptions))
.use(cssNano(minOptions))
.process(darkCss, {
from: 'css/dark-override.css',
to: '/public/css/dark.min.css',
}).catch(console.error);
const autoDarkCss = `${lightMin} @media (prefers-color-scheme: dark) { ${darkMin.css} }`
fs.writeFileSync('../public/css/auto.min.css', autoDarkCss)
})();
} catch (e) {
console.error(e)
}

3
frontEndSrc/css/auto.css Normal file
View File

@ -0,0 +1,3 @@
@media (prefers-color-scheme: dark) {
@import "src/dark-override.css";
}

5
frontEndSrc/css/dark.css Normal file
View File

@ -0,0 +1,5 @@
@import "src/-marx-.css";
@import "src/general.css";
@import "src/components.css";
@import "src/responsive.css";
@import "src/dark-override.css";

View File

@ -0,0 +1,4 @@
@import "src/-marx-.css";
@import "src/general.css";
@import "src/components.css";
@import "src/responsive.css";

View File

@ -0,0 +1,531 @@
:root {
--default-font-list: system-ui,sans-serif;
--monospace-font-list:'Anonymous Pro','Fira Code',Menlo,Monaco,Consolas,'Courier New',monospace;
--serif-font-list:Georgia,Times,'Times New Roman',serif;
-ms-text-size-adjust:100%;
-webkit-text-size-adjust:100%;
box-sizing:border-box;
cursor:default;
font-family:var(--default-font-list);
line-height:1.4;
overflow-y:scroll;
text-size-adjust:100%;
scroll-behavior:smooth;
}
audio:not([controls]) {
display:none;
}
details {
display:block;
}
input[type=search] {
-webkit-appearance:textfield;
}
input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration {
-webkit-appearance:none;
}
main {
display:block;
margin:0 auto;
padding:0 1.6em 1.6em;
padding:0 1.6rem 1.6rem;
}
summary {
display:block;
}
pre {
background:#efefef;
color:#444;
display:block;
font-family:var(--monospace-font-list);
font-size:1.4em;
font-size:1.4rem;
margin:1.6em 0;
margin:1.6rem 0;
overflow:auto;
padding:1.6em;
padding:1.6rem;
word-break:break-all;
word-wrap:break-word;
}
progress {
display:inline-block;
}
small {
color:#777;
font-size:75%;
}
big {
font-size:125%;
}
template {
display:none;
}
textarea {
border:.1rem solid #ccc;
border-radius:0;
display:block;
margin-bottom:.8rem;
overflow:auto;
padding:.8rem;
resize:vertical;
vertical-align:middle;
}
[hidden] {
display:none;
}
[unselectable] {
-moz-user-select:none;
-ms-user-select:none;
-webkit-user-select:none;
user-select:none;
}
*,::before,::after {
/* border-style:solid;
border-width:0; */
box-sizing:inherit;
}
* {
font-size:inherit;
line-height:inherit;
margin:0;
padding:0;
}
::before,::after {
text-decoration:inherit;
vertical-align:inherit;
}
a {
-webkit-transition:.25s ease;
color:#1271db;
text-decoration:none;
transition:.25s ease;
}
audio,canvas,iframe,img,svg,video {
vertical-align:middle;
}
input,/*select*/,textarea {
border:.1rem solid #ccc;
color:inherit;
font-family:inherit;
font-style:inherit;
font-weight:inherit;
min-height:1.4em;
}
code,kbd,pre,samp {
font-family:var(--monospace-font-list);
}
table {
border-collapse:collapse;
border-spacing:0;
margin-bottom:1.6rem;
}
::-moz-selection {
background-color:#b3d4fc;
text-shadow:none;
}
::selection {
background-color:#b3d4fc;
text-shadow:none;
}
button::-moz-focus-inner {
border:0;
}
body {
color:#444;
font-family:var(--default-font-list);
font-size:1.6rem;
font-style:normal;
font-weight:400;
padding:0;
}
p {
margin:0 0 1.6rem;
}
h1,h2,h3,h4,h5,h6 {
font-family:var(--default-font-list);
margin:2em 0 1.6em;
margin:2rem 0 1.6rem;
}
h1 {
border-bottom:.1rem solid rgba(0,0,0,0.2);
font-size:3.6em;
font-size:3.6rem;
font-style:normal;
font-weight:500;
}
h2 {
font-size:3em;
font-size:3rem;
font-style:normal;
font-weight:500;
}
h3 {
font-size:2.4em;
font-size:2.4rem;
font-style:normal;
font-weight:500;
margin:1.6rem 0 .4rem;
}
h4 {
font-size:1.8em;
font-size:1.8rem;
font-style:normal;
font-weight:600;
margin:1.6rem 0 .4rem;
}
h5 {
font-size:1.6em;
font-size:1.6rem;
font-style:normal;
font-weight:600;
margin:1.6rem 0 .4rem;
}
h6 {
color:#777;
font-size:1.4em;
font-size:1.4rem;
font-style:normal;
font-weight:600;
margin:1.6rem 0 .4rem;
}
code {
background:#efefef;
color:#444;
font-family:var(--monospace-font-list);
font-size:1.4rem;
word-break:break-all;
word-wrap:break-word;
}
a:hover,a:focus {
text-decoration:none;
}
dl {
margin-bottom:1.6rem;
}
dd {
margin-left:4rem;
}
ul,ol {
margin-bottom:.8rem;
padding-left:2rem;
}
blockquote {
border-left:.2rem solid #1271db;
font-family:var(--serif-font-list);
font-style:italic;
margin:1.6rem 0;
padding-left:1.6rem;
}
figcaption {
font-family:var(--serif-font-list);
}
html {
font-size:62.5%;
}
main,header,footer,article,section,aside,details,summary {
display:block;
height:auto;
margin:0 auto;
width:100%;
}
footer {
border-top:.1rem solid rgba(0,0,0,0.2);
clear:both;
display:inline-block;
float:left;
max-width:100%;
padding:1rem 0;
text-align:center;
}
hr {
border-top:.1rem solid rgba(0,0,0,0.2);
display:block;
margin-bottom:1.6rem;
width:100%;
}
img {
height:auto;
/* max-width:100%; */
vertical-align:baseline;
}
input[type=text],input[type=password],input[type=email],input[type=url],input[type=date],input[type=month],input[type=time],input[type=datetime],input[type=datetime-local],input[type=week],input[type=number],input[type=search],input[type=tel],input[type=color]/*,select */ {
border:.1rem solid #ccc;
border-radius:0;
display:inline-block;
padding:.8rem;
vertical-align:middle;
}
input:not([type]) {
-webkit-appearance:none;
background-clip:padding-box;
background-color:#fff;
border:.1rem solid #ccc;
border-radius:0;
color:#444;
display:inline-block;
padding:.8rem;
text-align:left;
}
input[type=color] {
padding:.8rem 1.6rem;
}
input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=url]:focus,input[type=date]:focus,input[type=month]:focus,input[type=time]:focus,input[type=datetime]:focus,input[type=datetime-local]:focus,input[type=week]:focus,input[type=number]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=color]:focus,/* select:focus */,textarea:focus {
border-color:#b3d4fc;
}
input:not([type]):focus {
border-color:#b3d4fc;
}
input[type=radio],input[type=checkbox] {
vertical-align:middle;
}
input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus {
outline:.1rem solid thin #444;
}
input[type=text][disabled],input[type=password][disabled],input[type=email][disabled],input[type=url][disabled],input[type=date][disabled],input[type=month][disabled],input[type=time][disabled],input[type=datetime][disabled],input[type=datetime-local][disabled],input[type=week][disabled],input[type=number][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=color][disabled],/*select[disabled]*/,textarea[disabled] {
background-color:#efefef;
color:#777;
cursor:not-allowed;
}
input:not([type])[disabled] {
background-color:#efefef;
color:#777;
cursor:not-allowed;
}
input[readonly],/*select[readonly]*/,textarea[readonly] {
background-color:#efefef;
border-color:#ccc;
color:#777;
}
input:focus:invalid,textarea:focus:invalid/*,select:focus:invalid*/ {
border-color:#e9322d;
color:#b94a48;
}
input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus,input[type=checkbox]:focus:invalid:focus {
outline-color:#ff4136;
}
/* select {
background-color:#fff;
border:.1rem solid #ccc;
}*/
select[multiple] {
height:auto;
}
label {
line-height:2;
}
fieldset {
border:0;
margin:0;
padding:.8rem 0;
}
legend {
border-bottom:.1rem solid #ccc;
color:#444;
display:block;
margin-bottom:.8rem;
padding:.8rem 0;
width:100%;
}
input[type=submit],button {
-moz-user-select:none;
-ms-user-select:none;
-webkit-transition:.25s ease;
-webkit-user-drag:none;
-webkit-user-select:none;
border:.2rem solid #444;
border-radius:0;
color:#444;
cursor:pointer;
display:inline-block;
margin-bottom:.8rem;
margin-right:.4rem;
padding:.8rem 1.6rem;
text-align:center;
text-decoration:none;
text-transform:uppercase;
transition:.25s ease;
user-select:none;
vertical-align:baseline;
}
input[type=submit] a,button a {
color:#444;
}
input[type=submit]::-moz-focus-inner,button::-moz-focus-inner {
padding:0;
}
input[type=submit]:hover,button:hover {
background:#444;
border-color:#444;
color:#fff;
}
input[type=submit]:hover a,button:hover a {
color:#fff;
}
input[type=submit]:active,button:active {
background:#6a6a6a;
border-color:#6a6a6a;
color:#fff;
}
input[type=submit]:active a,button:active a {
color:#fff;
}
input[type=submit]:disabled,button:disabled {
box-shadow:none;
cursor:not-allowed;
opacity:.4;
}
nav ul {
list-style:none;
margin:0;
padding:0;
text-align:center;
}
nav ul li {
display:inline;
}
nav a {
-webkit-transition:.25s ease;
border-bottom:.2rem solid transparent;
color:#444;
padding:.8rem 1.6rem;
text-decoration:none;
transition:.25s ease;
}
nav a:hover,nav li.selected a {
border-color:rgba(0,0,0,0.2);
}
nav a:active {
border-color:rgba(0,0,0,0.56);
}
caption {
padding:.8rem 0;
}
thead th {
background:#efefef;
color:#444;
}
tr {
background:#fff;
margin-bottom:.8rem;
}
th,td {
border:.1rem solid #ccc;
padding:.8rem 1.6rem;
text-align:center;
vertical-align:inherit;
}
tfoot tr {
background:none;
}
tfoot td {
color:#efefef;
font-size:.8rem;
font-style:italic;
padding:1.6rem .4rem;
}
@media screen {
[hidden~=screen] {
display:inherit;
}
[hidden~=screen]:not(:active):not(:focus):not(:target) {
clip:rect(0000)!important;
position:absolute!important;
}
}
@media screen and max-width 40rem {
article,section,aside {
clear:both;
display:block;
max-width:100%;
}
img {
margin-right:1.6rem;
}
}

View File

@ -0,0 +1,271 @@
/* -----------------------------------------------------------------------------
CSS loading icon
------------------------------------------------------------------------------*/
.cssload-loader {
position: relative;
left: calc(50% - 31px);
width: 62px;
height: 62px;
border-radius: 50%;
perspective: 780px;
}
.cssload-inner {
position: absolute;
width: 100%;
height: 100%;
box-sizing: border-box;
border-radius: 50%;
}
.cssload-inner.cssload-one {
left: 0%;
top: 0%;
animation: cssload-rotate-one 1.15s linear infinite;
border-bottom: 3px solid rgb(0, 0, 0);
}
.cssload-inner.cssload-two {
right: 0%;
top: 0%;
animation: cssload-rotate-two 1.15s linear infinite;
border-right: 3px solid rgb(0, 0, 0);
}
.cssload-inner.cssload-three {
right: 0%;
bottom: 0%;
animation: cssload-rotate-three 1.15s linear infinite;
border-top: 3px solid rgb(0, 0, 0);
}
@keyframes cssload-rotate-one {
0% {
transform: rotateX(35deg) rotateY(-45deg) rotateZ(0deg);
}
100% {
transform: rotateX(35deg) rotateY(-45deg) rotateZ(360deg);
}
}
@keyframes cssload-rotate-two {
0% {
transform: rotateX(50deg) rotateY(10deg) rotateZ(0deg);
}
100% {
transform: rotateX(50deg) rotateY(10deg) rotateZ(360deg);
}
}
@keyframes cssload-rotate-three {
0% {
transform: rotateX(35deg) rotateY(55deg) rotateZ(0deg);
}
100% {
transform: rotateX(35deg) rotateY(55deg) rotateZ(360deg);
}
}
/* ----------------------------------------------------------------------------
Loading overlay
-----------------------------------------------------------------------------*/
#loading-shadow {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 500;
}
#loading-shadow .loading-wrapper {
position: fixed;
z-index: 501;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#loading-shadow .loading-content {
position: relative;
color: #fff
}
.loading-content .cssload-inner.cssload-one,
.loading-content .cssload-inner.cssload-two,
.loading-content .cssload-inner.cssload-three {
border-color: #fff
}
/* ----------------------------------------------------------------------------
CSS Tabs
-----------------------------------------------------------------------------*/
.tabs {
display: inline-block;
display: flex;
flex-wrap: wrap;
background: #efefef;
box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3);
margin-top: 1.5em;
}
.tabs > label {
border: 1px solid #e5e5e5;
width: 100%;
padding: 20px 30px;
background: #e5e5e5;
cursor: pointer;
font-weight: bold;
font-size: 18px;
color: #7f7f7f;
transition: background 0.1s, color 0.1s;
/* margin-left: 4em; */
}
.tabs > label:hover {
background: #d8d8d8;
}
.tabs > label:active {
background: #ccc;
}
.tabs > [type=radio]:focus + label {
box-shadow: inset 0px 0px 0px 3px #2aa1c0;
z-index: 1;
}
.tabs > [type=radio] {
position: absolute;
opacity: 0;
}
.tabs > [type=radio]:checked + label {
border-bottom: 1px solid #fff;
background: #fff;
color: #000;
}
.tabs > [type=radio]:checked + label + .content {
border: 1px solid #e5e5e5;
border-top: 0;
display: block;
padding: 15px;
background: #fff;
width: 100%;
margin: 0 auto;
overflow: auto;
/* text-align: center; */
}
.tabs .content, .single-tab {
display: none;
max-height: 950px;
border: 1px solid #e5e5e5;
border-top: 0;
padding: 15px;
background: #fff;
width: 100%;
margin: 0 auto;
overflow: auto;
}
.single-tab {
display: block;
border: 1px solid #e5e5e5;
box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3);
margin-top: 1.5em;
}
.tabs .content.full-height, .single-tab.full-height {
max-height: none;
}
@media (min-width: 800px) {
.tabs > label {
width: auto;
}
.tabs .content {
order: 99;
}
}
/* ---------------------------------------------------------------------------
Vertical Tabs
----------------------------------------------------------------------------*/
.vertical-tabs {
border: 1px solid #e5e5e5;
box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3);
margin: 0 auto;
position: relative;
width: 100%;
}
.vertical-tabs input[type="radio"] {
position: absolute;
opacity: 0;
}
.vertical-tabs .tab {
align-items: center;
display: inline-block;
display: flex;
flex-wrap: nowrap;
}
.vertical-tabs .tab label {
align-items: center;
background: #e5e5e5;
border: 1px solid #e5e5e5;
color: #7f7f7f;
cursor: pointer;
font-size: 18px;
font-weight: bold;
padding: 0 20px;
width: 28%;
}
.vertical-tabs .tab label:hover {
background: #d8d8d8;
}
.vertical-tabs .tab label:active {
background: #ccc;
}
.vertical-tabs .tab .content {
display: none;
border: 1px solid #e5e5e5;
border-left: 0;
border-right: 0;
max-height: 950px;
overflow: auto;
}
.vertical-tabs .tab .content.full-height {
max-height: none;
}
.vertical-tabs [type=radio]:checked + label {
border: 0;
background: #fff;
color: #000;
width: 38%;
}
.vertical-tabs [type=radio]:focus + label {
box-shadow: inset 0px 0px 0px 3px #2aa1c0;
z-index: 1;
}
.vertical-tabs [type=radio]:checked ~ .content {
display: block;
}

View File

@ -0,0 +1,172 @@
a {
color: rgb(25, 120, 226);
text-shadow: var(--link-shadow);
}
a:hover {
color: #9e34fd;
}
body,
legend,
nav ul li a {
background: #333;
color: #eee;
}
nav a:hover, nav li.selected a {
border-color: #fff;
}
header button {
background: transparent;
}
table {
box-shadow: none;
}
td, th {
border-color: #111;
}
thead td,
thead th {
background: #333;
color: #eee;
}
tbody > tr:nth-child(2n) {
background: #555;
color: #eee;
}
tbody > tr:nth-child(2n+1) {
background: #333;
}
footer, legend, hr {
border-color: #ddd;
}
small {
color: #fff;
}
input, input[type], select, textarea {
border-color: #bbb;
color: #bbb;
background: #333;
padding:.8em;
}
button {
background: #444;
background: linear-gradient(#666, #555, #444, #555, #666);
border-radius: 0.5em;
margin: 0;
text-transform: none;
border-color: #ddd;
color: #ddd;
}
button:hover {
background: #222;
background: linear-gradient(#444, #333, #222, #333, #444);
border-color: #ddd;
color: #ddd;
}
button:active {
background: #333;
background: linear-gradient(#333, #333);
}
.media:hover button {
background: linear-gradient(#666, #555, #444, #555, #666);
}
.media:hover button:hover {
background: linear-gradient(#444, #555, #666, #555, #444);
}
.message, .static-message {
text-shadow: var(--white-link-shadow);
}
.message.success, .static-message.success {
background: #1f8454;
border-color: #70dda9;
}
.message.error, .static-message.error {
border-color:#f3e6e6;
background: #924949;
}
.message.info, .static-message.info {
border-color: #FFFFCC;
background: #bfbe3a;
}
.invisible tr,
.invisible td,
.invisible th,
.invisible tbody > tr:nth-child(2n),
.invisible tbody > tr:nth-child(2n+1) {
background: transparent;
}
#main-nav {
border-bottom: .1rem solid #ddd;
}
.tabs,
.vertical-tabs{
background: #333;
}
.tabs > label,
.vertical-tabs .tab label {
background: #222;
border: 0;
color: #eee;
}
.vertical-tabs .tab label {
width: 100%;
}
.tabs > label:hover,
.vertical-tabs .tab > label:hover {
background: #888;
}
.tabs > label:active,
.vertical-tabs .tab > label:active {
background: #999;
}
.tabs > [type="radio"]:checked + label,
.tabs > [type="radio"]:checked + label + .content,
.vertical-tabs [type="radio"]:checked + label,
.vertical-tabs [type="radio"]:checked ~ .content,
.single-tab {
/* border-color: #333; */
border: 0;
background: #666;
color: #eee;
}
.vertical-tabs {
background: #222;
border: 1px solid #444;
}
.vertical-tabs .tab {
background: #666;
border-bottom: 1px solid #444;
}
.streaming-logo {
-webkit-filter: drop-shadow(0 0 2px #fff);
filter: drop-shadow(0 0 2px #fff);
}

View File

@ -0,0 +1,931 @@
:root {
--blue-link: rgb(18, 113, 219);
--link-shadow: 1px 1px 1px #000;
--white-link-shadow: 1px 1px 1px #fff;
--shadow: 2px 2px 2px #000;
--title-overlay: rgba(0, 0, 0, 0.45);
--title-overlay-fallback: #000;
--text-color: #ffffff;
--normal-padding: 0.25em 0.125em;
--link-hover-color: #7d12db;
--edit-link-hover-color: #db7d12;
--edit-link-color: #12db18;
--radius: 5px;
}
template, [hidden="hidden"], .media[hidden] {
display: none
}
body {
margin: 0.5em;
}
button {
background: #fff;
background: linear-gradient(#ddd, #eee, #fff, #eee, #ddd);
border-radius: 0.5em;
margin: 0;
text-transform: none;
border-color: #555;
color: #555;
}
button:hover {
background: #bbb;
background: linear-gradient(#cfcfcf, #dfdfdf, #efefef, #dfdfdf, #cfcfcf);
border-color: #555;
color: #555;
}
button:active {
background: #ddd;
background: linear-gradient(#ddd, #ddd);
}
.media:hover button {
background: linear-gradient(#bbb, #ccc, #ddd, #ccc, #bbb);
}
.media:hover button:hover {
background: linear-gradient(#afafaf, #bfbfbf, #cfcfcf, #bfbfbf, #afafaf);
}
table {
/* min-width: 85%; */
box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3);
margin: 0 auto;
}
td {
padding: 1em;
padding: 1rem;
}
thead td, thead th {
padding: 0.5em;
padding: 0.5rem;
}
input[type=number] {
min-width: 0;
width: 4.5em;
}
input[type=checkbox], input[type=radio] {
min-width: auto;
vertical-align: inherit;
}
input, textarea {
min-width: 30em;
min-width: 30rem;
}
tbody > tr:nth-child(odd) {
background: #ddd;
}
a:hover, a:active {
color: var(--link-hover-color)
}
iframe {
display: block;
margin: 0 auto;
border: 0;
}
/* -----------------------------------------------------------------------------
Utility classes
------------------------------------------------------------------------------*/
.bracketed {
color: var(--edit-link-color);
}
.bracketed, #main-nav a {
text-shadow: var(--link-shadow);
}
.bracketed:before {
content: '[\00a0'
}
.bracketed:after {
content: '\00a0]'
}
.bracketed:hover, .bracketed:active {
color: var(--edit-link-hover-color)
}
.grow-1 {
flex-grow: 1
}
.flex-wrap {
flex-wrap: wrap
}
.flex-no-wrap {
flex-wrap: nowrap
}
.flex-align-start {
align-content: flex-start;
}
.flex-align-end {
align-items: flex-end
}
.flex-align-space-around {
align-content: space-around
}
.flex-justify-start {
justify-content: flex-start;
}
.flex-justify-space-around {
justify-content: space-around
}
.flex-center {
justify-content: center;
}
.flex-self-center {
align-self: center
}
.flex-space-evenly {
justify-content: space-evenly;
}
.flex {
display: inline-block;
display: flex
}
.small-font {
font-size: 1.6rem;
}
.justify {
text-align: justify
}
.align-center {
text-align: center !important
}
.align-left {
text-align: left !important
}
.align-right {
text-align: right !important
}
.valign-top {
vertical-align: top
}
.no-border {
border: none
}
.media-wrap {
text-align: center;
margin: 0 auto;
position: relative;
}
.media-wrap-flex {
display: inline-block;
display: flex;
flex-wrap: wrap;
align-content: space-evenly;
justify-content: space-between;
position: relative;
}
td .media-wrap-flex {
justify-content: center;
}
.danger {
background-color: #ff4136;
border-color: #924949;
color: #924949;
/* color: #fff; */
}
.danger:hover, .danger:active {
background-color: #924949;
border-color: #ff4136;
color: #ff4136;
/* color: #fff; */
}
td.danger, td.danger:hover, td.danger:active {
background-color: transparent;
color: #924949;
}
.user-btn {
background: transparent;
border-color: var(--edit-link-color);
color: var(--edit-link-color);
text-shadow: var(--link-shadow);
padding: 0 0.5em;
padding: 0 0.5rem;
}
.user-btn:hover, .user-btn:active {
background: transparent;
border-color: var(--edit-link-hover-color);
color: var(--edit-link-hover-color);
}
.user-btn:active {
background: var(--edit-link-hover-color);
color: #fff;
}
.full-width {
width: 100%;
}
.full-height {
max-height: none;
}
.toph {
margin-top: 0;
}
/* -----------------------------------------------------------------------------
Main Nav
------------------------------------------------------------------------------*/
#main-nav {
font-family: var(--default-font-list);
margin: 2em 0 1.6em;
margin: 2rem 0 1.6rem;
border-bottom: .1rem solid rgba(0, 0, 0, 0.2);
font-size: 3.6em;
font-size: 3.6rem;
font-style: normal;
font-weight: 500;
}
/* -----------------------------------------------------------------------------
Table sorting and form styles
------------------------------------------------------------------------------*/
.sorting,
.sorting-asc,
.sorting-desc {
vertical-align: text-bottom;
}
.sorting::before {
content: " ↕\00a0";
}
.sorting-asc::before {
content: " ↑\00a0";
}
.sorting-desc::before {
content: " ↓\00a0";
}
.form {
/* width: 100%; */
}
.form thead th, .form thead tr {
background: inherit;
border: 0;
}
.form tr > td:nth-child(odd) {
text-align: right;
min-width: 25px;
max-width: 30%;
}
.form tr > td:nth-child(even) {
text-align: left;
/* width: 70%; */
}
.invisible tbody > tr:nth-child(odd) {
background: inherit;
}
.borderless,
.borderless tr,
.borderless td,
.borderless th,
.invisible tr,
.invisible td,
.invisible th,
table.invisible {
box-shadow: none;
border: 0;
}
/* -----------------------------------------------------------------------------
Message boxes
------------------------------------------------------------------------------*/
.message, .static-message {
position: relative;
margin: 0.5em auto;
padding: 0.5em;
width: 95%;
}
.message .close {
width: 1em;
height: 1em;
position: absolute;
right: 0.5em;
top: 0.5em;
text-align: center;
vertical-align: middle;
line-height: 1em;
}
.message:hover .close:after {
content: '☒';
}
.message:hover {
cursor: pointer;
}
.message .icon {
left: 0.5em;
top: 0.5em;
margin-right: 1em;
}
.message.error, .static-message.error {
border: 1px solid #924949;
background: #f3e6e6;
}
.message.error .icon::after {
content: '✘';
}
.message.success, .static-message.success {
border: 1px solid #1f8454;
background: #70dda9;
}
.message.success .icon::after {
content: '✔'
}
.message.info, .static-message.info {
border: 1px solid #bfbe3a;
background: #FFFFCC;
}
.message.info .icon::after {
content: '⚠';
}
/* -----------------------------------------------------------------------------
Base list styles
------------------------------------------------------------------------------*/
.media, .character, .small-character {
position: relative;
vertical-align: top;
display: inline-block;
text-align: center;
width: 220px;
height: 312px;
margin: var(--normal-padding);
z-index: 0;
background: rgba(0, 0, 0, 0.15);
}
.details picture.cover,
picture.cover {
display: initial;
width: 100%;
}
.media > img,
.character > img,
.small-character > img {
width: 100%;
}
.media .edit-buttons > button {
margin: 0.5em auto;
}
.name,
.media-metadata > div,
.medium-metadata > div,
.row {
text-shadow: var(--shadow);
color: var(--text-color);
padding: var(--normal-padding);
text-align: right;
z-index: 2;
}
.media-type, .age-rating {
text-align: left;
}
.media > .media-metadata {
position: absolute;
bottom: 0;
right: 0;
}
.media > .medium-metadata {
position: absolute;
bottom: 0;
left: 0;
}
.media > .name {
position: absolute;
top: 0;
}
.media > .name a {
display: inline-block;
transition: none;
}
.media .name a::before {
/* background: var(--title-overlay-fallback);
background: var(--title-overlay); */
content: '';
display: block;
height: 312px;
left: 0;
position: absolute;
top: 0;
width: 220px;
z-index: -1; /* Put the pseudo-element behind its parent */
}
.media-list .media:hover .name a::before {
/* transition: .25s ease; */
background: rgba(0, 0, 0, 0.75);
}
.media > .name span.canonical {
font-weight: bold;
}
.media > .name small {
font-weight: normal;
}
.media:hover .name {
background: rgba(0, 0, 0, 0.75);
}
.media-list .media > .name a:hover,
.media-list .media > .name a:hover small {
color: var(--blue-link);
}
.media:hover > button[hidden],
.media:hover > .edit-buttons[hidden] {
transition: .25s ease;
display: block;
}
.media:hover {
transition: .25s ease;
}
.small-character > .name a,
.small-character > .name a small,
.character > .name a,
.character > .name a small,
.media > .name a,
.media > .name a small {
background: none;
color: #fff;
text-shadow: var(--shadow);
}
/* -----------------------------------------------------------------------------
Anime-list-specific styles
------------------------------------------------------------------------------*/
.anime .name, .manga .name {
background: var(--title-overlay-fallback);
background: var(--title-overlay);
text-align: center;
width: 100%;
padding: 0.5em 0.25em;
}
.anime .media-type,
.anime .airing-status,
.anime .user-rating,
.anime .completion,
.anime .age-rating,
.anime .edit,
.anime .delete {
background: none;
text-align: center;
}
.anime .table, .manga .table {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
}
.anime .row, .manga .row {
width: 100%;
display: inline-block;
display: flex;
align-content: space-around;
justify-content: space-around;
text-align: center;
padding: 0 inherit;
}
.anime .row > span, .manga .row > span {
text-align: left;
z-index: 2;
}
.anime .row > div, .manga .row > div {
font-size: 0.8em;
display: inline-block;
display: flex-item;
align-self: center;
text-align: center;
vertical-align: middle;
z-index: 2;
}
.anime .media > button.plus-one {
border-color: hsla(0, 0%, 100%, .65);
position: absolute;
top: 138px;
top: calc(50% - 21.2px);
left: 44px;
left: calc(50% - 57.8px);
z-index: 50;
}
/* -----------------------------------------------------------------------------
Manga-list-specific styles
------------------------------------------------------------------------------*/
.manga .row {
padding: 1px;
}
.manga .media {
/* border: 1px solid #ddd; */
height: 310px;
margin: 0.25em;
}
.manga .media > .edit-buttons {
position: absolute;
top: 86px;
/* top: calc(50% - 58.5px); */
top: calc(50% - 21.2px);
left: 43.5px;
left: calc(50% - 57.8px);
z-index: 40;
}
.manga .media > .edit-buttons button {
border-color: hsla(0, 0%, 100%, .65);
}
/* -----------------------------------------------------------------------------
Search page styles
------------------------------------------------------------------------------*/
.media.search > .name {
background-color: #555;
background-color: rgba(000, 000, 000, 0.35);
background-size: cover;
background-size: contain;
background-repeat: no-repeat;
}
/* There are two .name elements, just darken them both in this case! */
.media.search.disabled .name {
background-color: #000;
background-color: rgba(0, 0, 0, 0.75);
background-size: cover;
background-size: contain;
background-repeat: no-repeat;
}
.media.search > .row {
z-index: 6;
}
.big-check, .mal-check {
display: none;
}
.big-check:checked + label {
transition: .25s ease;
background: rgba(0, 0, 0, 0.75);
}
.big-check:checked + label:after {
content: '✓';
font-size: 15em;
font-size: 15rem;
text-align: center;
color: greenyellow;
position: absolute;
top: 147px;
left: 0;
width: 100%;
z-index: 5;
}
#series-list article.media {
position: relative;
}
#series-list .name, #series-list .name label {
position: absolute;
display: block;
top: 0;
left: 0;
height: 100%;
width: 100%;
vertical-align: middle;
line-height: 1.25em;
}
#series-list .name small {
color: #fff;
}
/* ----------------------------------------------------------------------------
Details page styles
-----------------------------------------------------------------------------*/
.details {
margin: 1.5rem auto 0 auto;
padding: 1rem;
font-size: inherit;
}
/* .description {
max-width: 80rem;
columns: 4 28rem;
columns: 4 28em;
margin-bottom: 1.6em;
margin-bottom: 1.6rem;
}
p.description br + br {
page-break-before: avoid;
page-break-after: auto;
page-break-inside: avoid;
break-inside: avoid;
break-after: auto;
break-before: avoid;
} */
.fixed {
max-width: 115em;
max-width: 115rem;
/* max-width: 80%; */
margin: 0 auto;
}
.details .cover {
display: block;
}
.details .flex > * {
margin: 1rem;
}
.details .media-details td {
padding: 0 1.5rem;
}
.details p {
text-align: justify;
}
.details .media-details td:nth-child(odd) {
width: 1%;
white-space: nowrap;
text-align: right;
}
.details .media-details td:nth-child(even) {
text-align: left;
}
.details a h1,
.details a h2 {
margin-top: 0;
}
.character,
.small-character,
.person {
/* background: rgba(0,0,0,0.5); */
width: 225px;
height: 350px;
vertical-align: middle;
white-space: nowrap;
position: relative;
}
.person {
width: 225px;
height: 338px;
}
.small-person {
width: 200px;
height: 300px;
}
.character a {
height: 350px;
}
.character:hover .name,
.small-character:hover .name {
background: rgba(0, 0, 0, 0.8);
}
.small-character a {
display: inline-block;
width: 100%;
height: 100%;
}
.small-character .name,
.character .name {
position: absolute;
bottom: 0;
left: 0;
z-index: 10;
}
.small-character img,
.character img,
.small-character picture,
.character picture,
.person img,
.person picture {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 5;
max-height: 350px;
max-width: 225px;
}
.person img,
.person picture {
max-height: 338px;
}
.small-person img,
.small-person picture {
max-height: 300px;
max-width: 200px;
}
.min-table {
min-width: 0;
margin-left: 0;
}
.max-table {
min-width: 100%;
margin: 0;
}
aside.info {
/* max-width: 390px; */
max-width: 33%;
}
.fixed aside {
max-width: 390px;
}
aside picture, aside img {
display: block;
margin: 0 auto;
}
/* ----------------------------------------------------------------------------
User page styles
-----------------------------------------------------------------------------*/
.small-character {
width: 160px;
height: 250px;
}
.small-character img,
.small-character picture {
max-height: 250px;
max-width: 160px;
}
.user-page .media-wrap {
text-align: left;
}
.media a {
display: inline-block;
width: 100%;
height: 100%;
}
/* ----------------------------------------------------------------------------
Images / Logos
-----------------------------------------------------------------------------*/
.streaming-logo {
width: 50px;
height: 50px;
vertical-align: middle;
}
.small-streaming-logo {
width: 25px;
height: 25px;
vertical-align: middle;
}
.cover-streaming-link {
display: none;
}
.media:hover .cover-streaming-link {
display: block;
}
.cover-streaming-link .streaming-logo {
width: 20px;
height: 20px;
-webkit-filter: drop-shadow(0 -1px 4px #fff);
filter: drop-shadow(0 -1px 4px #fff);
}
.history-img {
width: 110px;
height: 156px;
}
/* ----------------------------------------------------------------------------
Settings Form
-----------------------------------------------------------------------------*/
.settings.form .content article {
margin: 1em;
display: inline-block;
width: auto;
}
/* ----------------------------------------------------------------------------
iFrame container
-----------------------------------------------------------------------------*/
.responsive-iframe {
margin-top: 1em;
overflow: hidden;
padding-bottom: 56.25%;
position: relative;
height: 0;
}
.responsive-iframe iframe {
left: 0;
top: 0;
height: 100%;
width: 100%;
position: absolute;
}

View File

@ -0,0 +1,137 @@
/* ----------------------------------------------------------------------------
Viewport-based styles
-----------------------------------------------------------------------------*/
@media screen and (max-width: 1100px) {
.flex {
flex-wrap: wrap;
}
aside.info,
aside.info + article,
.fixed aside.info,
.fixed aside.info + article {
max-width: none;
width: 100%;
}
/* aside.info {
order: 1;
} */
}
@media screen and (max-width: 800px) {
* {
max-width: none;
}
table {
box-shadow: none;
}
body,
.details .flex > * {
margin: 0;
}
table,
table th,
table td,
table .align-right,
table.align-center {
border: 0;
/* display: block; */
margin-left: auto;
margin-right: auto;
text-align: left;
width: 100%;
}
table td {
display: inline-block;
}
table tbody,
table.media-details {
width: 100%;
}
table.media-details td {
display: block;
text-align: left !important;
width: 100%;
}
table thead {
display: none;
}
.details .media-details td:nth-child(2n+1) {
font-weight: bold;
width: 100%;
}
table.streaming-links tr td:not(:first-child) {
display:none;
}
}
@media screen and (max-width: 40em) {
nav a {
line-height: 4em;
line-height: 4rem;
}
img,
picture {
width: 100%;
}
main {
padding: 0 0, 5em 0.5em;
padding: 0 0.5rem 0.5rem;
}
.media {
margin: 2px 0;
}
.details {
padding: 0.5em;
padding: 0.5rem;
}
/* Expand tabs */
.tabs > [type="radio"]:checked + label {
background: #fff;
}
/* Expand vertical tabs */
.vertical-tabs .tab {
flex-wrap: wrap;
}
.tabs .content,
.tabs > [type="radio"]:checked + label + .content,
.vertical-tabs .tab .content {
display: block;
border: 0;
max-height: none;
}
.tabs > label,
.tabs > label:active,
.tabs > label:hover,
.tabs > [type="radio"]:checked + label,
.vertical-tabs .tab label,
.vertical-tabs .tab label:active,
.vertical-tabs .tab label:hover,
.vertical-tabs [type=radio]:focus + label,
.vertical-tabs [type=radio]:checked + label {
background: #fff;
border: 0;
width: 100%;
cursor: default;
color: #000;
}
}

3
frontEndSrc/cssfilter.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = function filter(filename) {
return ! String(filename).includes('min');
}

View File

@ -0,0 +1,353 @@
// -------------------------------------------------------------------------
// ! 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 {Element} [context]
* @return 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|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|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 {Element} current - the current Element
* @param {string} parentSelector - selector for the parent element
* @return {Element|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|Element} sel - the parent selector to bind to
* @param {string} event - event name(s) to bind
* @param {string|Element|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
});
};
// -------------------------------------------------------------------------
// Export
// -------------------------------------------------------------------------
export default AnimeClient;

128
frontEndSrc/js/anime.js Normal file
View File

@ -0,0 +1,128 @@
import _ from './anime-client.js'
import { renderSearchResults } from './template-helpers.js'
import { getNestedProperty, hasNestedProperty } from "./fns";
const search = (query, isCollection = false) => {
// Show the loader
_.show('.cssload-loader');
// Do the api search
return _.get(_.url('/anime-collection/search'), { query }, (searchResults, status) => {
searchResults = JSON.parse(searchResults);
// Hide the loader
_.hide('.cssload-loader');
// Show the results
_.$('#series-list')[ 0 ].innerHTML = renderSearchResults('anime', searchResults, isCollection);
});
};
// Anime list search
if (_.hasElement('.anime #search')) {
let prevRequest = null;
_.on('#search', 'input', _.throttle(250, (e) => {
const query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}
if (prevRequest !== null) {
prevRequest.abort();
}
prevRequest = search(query);
}));
}
// Anime collection search
if (_.hasElement('#search-anime-collection')) {
let prevRequest = null;
_.on('#search-anime-collection', 'input', _.throttle(250, (e) => {
const query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}
if (prevRequest !== null) {
prevRequest.abort();
}
prevRequest = search(query, true);
}));
}
// Action to increment episode count
_.on('body.anime.list', 'click', '.plus-one', (e) => {
let parentSel = _.closestParent(e.target, 'article');
let watchedCount = parseInt(_.$('.completed_number', parentSel)[ 0 ].textContent, 10) || 0;
let totalCount = parseInt(_.$('.total_number', parentSel)[ 0 ].textContent, 10);
let title = _.$('.name a', parentSel)[ 0 ].textContent;
// Setup the update data
let data = {
id: parentSel.dataset.kitsuId,
anilist_id: parentSel.dataset.anilistId,
mal_id: parentSel.dataset.malId,
data: {
progress: watchedCount + 1
}
};
const displayMessage = (type, message) => {
_.hide('#loading-shadow');
_.showMessage(type, `${message} ${title}`);
_.scrollToTop();
}
const showError = () => displayMessage('error', 'Failed to update');
// 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';
}
_.show('#loading-shadow');
// okay, lets actually make some changes!
_.ajax(_.url('/anime/increment'), {
data,
dataType: 'json',
type: 'POST',
success: (res) => {
try {
const resData = JSON.parse(res);
// Do a rough sanity check for weird errors
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
showError();
return;
}
// We've completed the series
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
_.hide(parentSel);
displayMessage('success', 'Completed')
return;
}
// Just a normal update
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
displayMessage('success', 'Updated');
} catch (_) {
showError();
}
},
error: showError,
});
});

View File

@ -0,0 +1,83 @@
const LightTableSorter = (() => {
let th = null;
let cellIndex = null;
let order = '';
const text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();
const sort = (a, b) => {
let textA = text(a);
let textB = text(b);
console.log("Comparing " + textA + " and " + textB)
if(th.classList.contains("numeric")){
let arrayA = textA.replace('episodes: ','').replace('-',0).split("/");
let arrayB = textB.replace('episodes: ','').replace('-',0).split("/");
if(arrayA.length > 1) {
textA = parseInt(arrayA[0],10) / parseInt(arrayA[1],10);
textB = parseInt(arrayB[0],10) / parseInt(arrayB[1],10);
}
else{
textA = parseInt(arrayA[0],10);
textB = parseInt(arrayB[0],10);
}
}
else if (parseInt(textA, 10)) {
textA = parseInt(textA, 10);
textB = parseInt(textB, 10);
}
if (textA > textB) {
return 1;
}
if (textA < textB) {
return -1;
}
return 0;
};
const toggle = () => {
const c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';
th.className = (th.className.replace(order, '') + ' ' + c).trim();
return order = c;
};
const reset = () => {
th.classList.remove('sorting-asc', 'sorting-desc');
th.classList.add('sorting');
return order = '';
};
const onClickEvent = (e) => {
if (th && (cellIndex !== e.target.cellIndex)) {
reset();
}
th = e.target;
if (th.nodeName.toLowerCase() === 'th') {
cellIndex = th.cellIndex;
const tbody = th.offsetParent.getElementsByTagName('tbody')[0];
let rows = Array.from(tbody.rows);
if (rows) {
rows.sort(sort);
if (order === 'sorting-asc') {
rows.reverse();
}
toggle();
tbody.innerHtml = '';
rows.forEach(row => {
tbody.appendChild(row);
});
}
}
};
return {
init: () => {
let ths = document.getElementsByTagName('th');
let results = [];
for (let i = 0, len = ths.length; i < len; i++) {
let th = ths[i];
th.classList.add('sorting');
th.classList.add('testing');
results.push(th.onclick = onClickEvent);
}
return results;
}
};
})();
LightTableSorter.init();

Some files were not shown because too many files have changed in this diff Show More