Compare commits
1178 Commits
Author | SHA1 | Date |
---|---|---|
Timothy Warren | 7afdab80ac | |
Timothy Warren | 144718866d | |
Timothy Warren | 1dd9fdd0c2 | |
Timothy Warren | 3794ed20a8 | |
Timothy Warren | ff8c837fd9 | |
Timothy Warren | fe1caffc0f | |
Timothy Warren | 8e7b2a04fd | |
Timothy Warren | 0e684736bd | |
Timothy Warren | 1ad4427584 | |
Timothy Warren | b0a16e7730 | |
Timothy Warren | 7559f79ef6 | |
Timothy Warren | c8b642be1c | |
Timothy Warren | 91c435cdac | |
Timothy Warren | 57249882ab | |
Timothy Warren | c28dfc09b3 | |
Timothy Warren | cd5841c4c8 | |
Timothy Warren | c83b30f43a | |
Kevin Colwell | 011160a3c2 | |
Timothy Warren | 067d888f4c | |
Kevin Colwell | 1b5a5080f2 | |
Timothy Warren | 1a97277ec4 | |
Timothy Warren | e426fdd4a3 | |
Timothy Warren | 5758de7667 | |
Timothy Warren | d21d9a86d4 | |
Timothy Warren | a435162a2b | |
Timothy Warren | 4fef2b2942 | |
Timothy Warren | 77c6419c80 | |
Timothy Warren | 97fe3b4b40 | |
Timothy Warren | 5396091b83 | |
Timothy Warren | 655ad119ce | |
Timothy Warren | 351446a2ee | |
Timothy Warren | f2b7f61030 | |
Timothy Warren | b678a3401e | |
Timothy Warren | 465cd99165 | |
Timothy Warren | 2e67b49447 | |
Timothy Warren | 45449b6907 | |
Timothy Warren | 05d4fb1ad7 | |
Timothy Warren | 81d81f4393 | |
Timothy Warren | 60ddcaa08e | |
Timothy Warren | 0e780f26b9 | |
Timothy Warren | bedd504b64 | |
Timothy Warren | 2f657dc20b | |
Timothy Warren | 4f8eefe71e | |
Timothy Warren | dd63767ddf | |
Timothy Warren | e1ff1c6e21 | |
Timothy Warren | f673a84cf6 | |
Timothy Warren | 4a2273c93c | |
Timothy Warren | 3cb90acb13 | |
Timothy Warren | ab38e03af9 | |
Timothy Warren | 2139f031f4 | |
Timothy Warren | ec280cd76d | |
Timothy Warren | 5e7f57eb0a | |
Timothy Warren | 26cbe4b592 | |
Timothy Warren | e8aa2bd42b | |
Timothy Warren | 501062ac37 | |
Timothy Warren | 4dc0bb29d2 | |
Timothy Warren | b1c4c8cb5e | |
Timothy Warren | e4fe5bbfec | |
Timothy Warren | 4b35d25849 | |
Timothy Warren | 3b57d08c0a | |
Timothy Warren | 29e70a8e3f | |
Timothy Warren | a0d30c002a | |
Timothy Warren | 327065498b | |
Timothy Warren | 047ee4cb37 | |
Timothy Warren | 9b945ca0a5 | |
Timothy Warren | e70b0fdb40 | |
Timothy Warren | c8a38d5785 | |
Timothy Warren | 10eb7794e9 | |
Timothy Warren | 0dee5f52fc | |
Timothy Warren | 7195258d7e | |
Timothy Warren | 9750d63e55 | |
Timothy Warren | e8a14fedf2 | |
Timothy Warren | eb897adbd1 | |
Timothy Warren | 5fb1b87e67 | |
Timothy Warren | a9b24f0cf7 | |
Timothy Warren | f40ee254c9 | |
Timothy Warren | 2b047634a0 | |
Timothy Warren | 7baa5bfe91 | |
Timothy Warren | 2d6058a6b3 | |
Timothy Warren | d34c79b4cd | |
Timothy Warren | ed558a6484 | |
Timothy Warren | 854987bd44 | |
Timothy Warren | 2977f7385e | |
Timothy Warren | 760cc71768 | |
Kevin Colwell | 573eac78f1 | |
Timothy Warren | 02fa04d19d | |
Timothy Warren | 6e1d190230 | |
Timothy Warren | 80b2495c11 | |
Kevin Colwell | 3d9d8202d9 | |
Kevin Colwell | 188baa5cc8 | |
Kevin Colwell | 3dbaf7ef32 | |
Timothy Warren | 545984bb18 | |
Timothy Warren | f9a5716002 | |
Timothy Warren | 818fcf114d | |
Timothy Warren | 3ca606d6f5 | |
Timothy Warren | 288e64f357 | |
Timothy Warren | 6ed1b81451 | |
Timothy Warren | 823ca8a805 | |
Timothy Warren | 7301c4852d | |
Timothy Warren | 602f0fc9c5 | |
Timothy Warren | 694e7cc01c | |
Timothy Warren | 7efa34efee | |
Timothy Warren | 49675ffbee | |
Timothy Warren | b82b7d74fc | |
Timothy Warren | 5102c7c459 | |
Timothy Warren | 1d022bc8ae | |
Timothy Warren | 489ec27602 | |
Timothy Warren | 6e4e065b75 | |
Timothy Warren | 69db87e305 | |
Timothy Warren | 2dacca5d06 | |
Timothy Warren | 1abac0ac0e | |
Timothy Warren | ba6ed8967c | |
Timothy Warren | 3d80f755a1 | |
Timothy Warren | 7a541b609f | |
Timothy Warren | 0f4383563f | |
Timothy Warren | 7b33d40de4 | |
Timothy Warren | 4c85c22c30 | |
Timothy Warren | 7839cf1515 | |
Timothy Warren | d2a9aaee54 | |
Timothy Warren | ff85cb6153 | |
Timothy Warren | 4c396ba9c6 | |
Timothy Warren | d2c397f6b9 | |
Timothy Warren | e679322122 | |
Timothy Warren | e6ae6c9e9c | |
Timothy Warren | d387b793ea | |
Timothy Warren | 50bb525f60 | |
Timothy Warren | 8de60b332d | |
Timothy Warren | 51eb460ce9 | |
Timothy Warren | 78a37c736f | |
Timothy Warren | 380c455332 | |
Timothy Warren | 6c35ade209 | |
Timothy Warren | 633f30d365 | |
Timothy Warren | 8c1d882404 | |
Timothy Warren | 6af73cea55 | |
Timothy Warren | d3732d1a54 | |
Timothy Warren | 3aecaf9161 | |
Timothy Warren | b12e94cee4 | |
Timothy Warren | 3d5d2c05ce | |
Timothy Warren | f01cc77f92 | |
Timothy Warren | c81271864d | |
Timothy Warren | 10ea494594 | |
Timothy Warren | 836b1d17e6 | |
Timothy Warren | 5004a9f332 | |
Timothy Warren | 40f134d7bc | |
Timothy Warren | 391708a49c | |
Timothy Warren | 566c9fbd1e | |
Timothy Warren | 56bda5ed71 | |
Timothy Warren | d2fbd3b56a | |
Timothy Warren | 9f680a75e3 | |
Timothy Warren | 8fadf9d589 | |
Timothy Warren | b64d8c6c5b | |
Timothy Warren | b393c695a5 | |
Timothy Warren | 1ab4dedddb | |
Timothy Warren | 73ee1a41e1 | |
Timothy Warren | 44a7d36174 | |
Timothy Warren | 77f314ee55 | |
Timothy Warren | 8d742e62ed | |
Timothy Warren | e1fd2bed59 | |
Timothy Warren | 61d44146cd | |
Timothy Warren | b2761541ec | |
Timothy Warren | 6412f1108b | |
Timothy Warren | c37f50d06e | |
Timothy Warren | c900e379c5 | |
Timothy Warren | 326436c0b5 | |
Timothy Warren | 66df53bf43 | |
Timothy Warren | 2cd9f99011 | |
Timothy Warren | 35ec3c8bfa | |
Timothy Warren | 52a0ff275d | |
Timothy Warren | a26c90ff86 | |
Timothy Warren | c173a8c196 | |
Timothy Warren | 73cf8ccd5a | |
Timothy Warren | 244372ff8c | |
Timothy Warren | 74ef13713d | |
Timothy Warren | 057d4bfae7 | |
Timothy Warren | 68bc4693cb | |
Timothy Warren | ce3260a2f3 | |
Timothy Warren | 4c5aa1e3de | |
Timothy Warren | 3c8b564ab9 | |
Timothy Warren | 7505907976 | |
Timothy Warren | 05455a518b | |
Timothy Warren | c39bc23061 | |
Timothy Warren | 9ba1bd4c90 | |
Timothy Warren | 2f789cc4cf | |
Timothy Warren | a18c0bd7b5 | |
Timothy Warren | 2b31cae57b | |
Timothy Warren | 48dcaa4bcb | |
Timothy Warren | 4342b2e795 | |
Timothy Warren | b2361c57c2 | |
Timothy Warren | e6fdea28d4 | |
Timothy Warren | 15dcdfe39c | |
Timothy Warren | 0250a50731 | |
Timothy Warren | f2aca2b76f | |
Timothy Warren | 97a7d501d0 | |
Timothy Warren | 8c3b583f92 | |
Timothy Warren | d6e174a014 | |
Timothy Warren | df7a9e311b | |
Timothy Warren | ebf22643ef | |
Timothy Warren | 3039f412aa | |
Timothy Warren | 37ab6034ba | |
Timothy Warren | d5931ff53b | |
Kevin Colwell | fe1250732c | |
Timothy Warren | 144e3f5229 | |
Timothy Warren | 5f494aa9bd | |
Timothy Warren | 7e0cbe8b83 | |
Timothy Warren | 31ed9d11ab | |
Timothy Warren | 6e3a70f9f6 | |
Timothy Warren | 3c47570cce | |
Timothy Warren | b4d9e9f21f | |
Timothy Warren | 1ea5750a76 | |
Timothy Warren | 36b396be71 | |
Timothy Warren | bf4f86a010 | |
Timothy Warren | 45b0209d8a | |
Timothy Warren | f37ec8022e | |
Timothy Warren | 415778295f | |
Timothy Warren | ad0dcb5750 | |
Timothy Warren | e0ad68b9d2 | |
Timothy Warren | 608251452f | |
Timothy Warren | 8c5547d69d | |
Timothy Warren | 6c29af4533 | |
Timothy Warren | 898dfebbde | |
Timothy Warren | 2d5ae3b1c6 | |
Timothy Warren | 8256815032 | |
Timothy Warren | fe6f737815 | |
Timothy Warren | 87d15024bb | |
Timothy Warren | 470d25f269 | |
Timothy Warren | 70a33e36c0 | |
Timothy Warren | 94d227b08e | |
Timothy Warren | ecb913322f | |
Timothy Warren | b001af868f | |
Timothy Warren | 1fbf0283ba | |
Timothy Warren | 5bcc046a12 | |
Timothy Warren | 9009da4b86 | |
Timothy Warren | 47a4be2cf9 | |
Timothy Warren | 52aabc2b12 | |
Timothy Warren | 7b1217bafe | |
Timothy Warren | a79ab842ee | |
Timothy Warren | 810731dfbd | |
Timothy Warren | c224c8d977 | |
Timothy Warren | ce3e3427dc | |
Timothy Warren | 7211aa0de7 | |
Timothy Warren | 02bd0288f2 | |
Timothy Warren | a15496e4a5 | |
Timothy Warren | a14ac3a122 | |
Timothy Warren | 1a3f1e9654 | |
Timothy Warren | 0c936b3fa7 | |
Timothy Warren | ccb9c9d331 | |
Timothy Warren | 738e39ba92 | |
Timothy Warren | 18e8d47167 | |
Timothy Warren | c429ce64d3 | |
Timothy Warren | 9003c15929 | |
Timothy Warren | eb56ab4c4f | |
Timothy Warren | 29a79577d9 | |
Timothy Warren | e890f978db | |
Timothy Warren | e944ddc75c | |
Timothy Warren | 778cda6efc | |
Timothy Warren | e912c83079 | |
Timothy Warren | 78b9146249 | |
Timothy Warren | e40a1d028f | |
Timothy Warren | edb022be13 | |
Timothy Warren | b75a99a145 | |
Timothy Warren | 7aeb74874b | |
Timothy Warren | 9749c59549 | |
Timothy Warren | 5da0ba87a7 | |
Timothy Warren | c749c7c923 | |
Timothy Warren | 9b4c9ad76f | |
Timothy Warren | 681a70fd92 | |
Timothy Warren | 67d3b7c1dc | |
Timothy Warren | 79aee53524 | |
Timothy Warren | 56f7d5142d | |
Timothy Warren | 5f7f4b6bdd | |
Timothy Warren | 0c3ff2ef11 | |
Timothy Warren | 5997ce8a0f | |
Timothy Warren | 687831efd5 | |
Timothy Warren | 5a65c7b645 | |
Timothy Warren | 9dc6643b78 | |
Timothy Warren | c7beb76404 | |
Timothy Warren | c132766486 | |
Timothy Warren | 9a112dc413 | |
Timothy Warren | 1c3216e26a | |
Timothy Warren | 78b195f966 | |
Timothy Warren | a35bce8a4b | |
Timothy Warren | 93faf7d88c | |
Timothy Warren | a0e7ebd2a0 | |
Timothy Warren | 2b54ab5497 | |
Timothy Warren | 7bfdd74f22 | |
Timothy Warren | 4582e2e917 | |
Timothy Warren | b0c75d989f | |
Timothy Warren | a3bae9255b | |
Timothy Warren | 3ab34a64d0 | |
Timothy Warren | 8110f10c3d | |
Timothy Warren | 7dae2dd6eb | |
Timothy Warren | 7c0ea492e1 | |
Timothy Warren | 9135598649 | |
Timothy Warren | 0b0e06af00 | |
Timothy Warren | 1ae99d2189 | |
Timothy Warren | 7275d81468 | |
Timothy Warren | dbfdd1c239 | |
Timothy Warren | 9eec7123a3 | |
Timothy Warren | 710d18a43b | |
Timothy Warren | 6d66ad1ea4 | |
Timothy Warren | 8d87d2fb2b | |
Timothy Warren | 61fcffdcbe | |
Timothy Warren | 057216a21c | |
Timothy Warren | abb17844fd | |
Timothy Warren | 891d8af469 | |
Timothy Warren | c701999af1 | |
Timothy Warren | af0b392e78 | |
Timothy Warren | 2cc85049f3 | |
Timothy Warren | 21a98dc48e | |
Timothy Warren | 3ecccb6ad8 | |
Timothy Warren | e724f885c8 | |
Timothy Warren | 43f07dac6c | |
Timothy Warren | 7bcff79d6e | |
Timothy Warren | f9f868be9d | |
Timothy Warren | 4a70422b23 | |
Timothy Warren | d8167ed075 | |
Timothy Warren | b6c0db7636 | |
Timothy Warren | ffd7fb8745 | |
Timothy Warren | 75bd011a2c | |
Timothy Warren | 03638991a3 | |
Timothy Warren | a7e6b3f198 | |
Timothy Warren | d5e3dcaff8 | |
Timothy Warren | 9108fe066a | |
Timothy Warren | f810e2573e | |
Timothy Warren | a371a334d0 | |
Timothy Warren | ed72eaef84 | |
Timothy Warren | bcbd76d4a9 | |
Timothy Warren | 754cf80c0b | |
Timothy Warren | ce0935333b | |
Timothy Warren | 050ff98d2c | |
Timothy Warren | 44d2c0e29d | |
Timothy Warren | c14bf3a8af | |
Timothy Warren | 42ffef32fe | |
Timothy Warren | 1cc5703cd7 | |
Timothy Warren | 62be0beae6 | |
Timothy Warren | e2e23a290f | |
Timothy Warren | 541b59bb28 | |
Timothy Warren | d81fba030c | |
Timothy Warren | 209655adc3 | |
Timothy Warren | 8be0fceb69 | |
Timothy Warren | 8094ff5927 | |
Timothy Warren | b614505499 | |
Timothy Warren | e17846f4a4 | |
Timothy Warren | 59f2d21a7f | |
Timothy Warren | 0a83184db6 | |
Timothy Warren | e3e32b4408 | |
Timothy Warren | c424e3a65a | |
Timothy Warren | 2325c8f4ec | |
Timothy Warren | 5a3d9547ae | |
Timothy Warren | bc529e57e8 | |
Timothy Warren | f71a1ee1ae | |
Timothy Warren | 797e66e520 | |
Timothy Warren | 308a564a2d | |
Timothy Warren | 174877ec81 | |
Timothy Warren | 83bb85615a | |
Timothy Warren | 570c18a069 | |
Timothy Warren | 462e93292b | |
Timothy Warren | 82cd204469 | |
Timothy Warren | 6d55d4136e | |
Timothy Warren | 546789ce40 | |
Timothy Warren | 97be2e40f8 | |
Timothy Warren | dd708bb1fa | |
Timothy Warren | 93a6dbe7d6 | |
Timothy Warren | fae3314b56 | |
Timothy Warren | dbb61372c6 | |
Timothy Warren | 0b4c2c81c3 | |
Timothy Warren | 0aee62c174 | |
Timothy Warren | 7fd881c8e9 | |
Timothy Warren | 9158d01fdf | |
Timothy Warren | ae8df3e6d6 | |
Timothy Warren | f6e00d4336 | |
Timothy Warren | 34b454c175 | |
Timothy Warren | 5ce34200c9 | |
Timothy Warren | 1a6a30ef5d | |
Timothy Warren | 8faf33c438 | |
Timothy Warren | 6e16632988 | |
Timothy Warren | 14db3f1ec9 | |
Timothy Warren | a4fe28f7b5 | |
Timothy Warren | 7c796b3d7b | |
Timothy Warren | 06529d7c92 | |
Timothy Warren | 42948017a4 | |
Timothy Warren | 055ec80236 | |
Timothy Warren | 986ff6de0b | |
Timothy Warren | e6a216704c | |
Timothy Warren | beaeb13353 | |
Timothy Warren | 0ea35cb421 | |
Timothy Warren | 0feb44a836 | |
Timothy Warren | 9b33a45189 | |
Timothy Warren | e0fa618b4e | |
Timothy Warren | 7672976f47 | |
Timothy Warren | 8036104731 | |
Timothy Warren | 76cb8ca00b | |
Timothy Warren | cc3b999bc5 | |
Timothy Warren | fb327f0c58 | |
Timothy Warren | ee914d048b | |
Timothy Warren | f57466d42c | |
Timothy Warren | 3ce928a67d | |
Timothy Warren | 622b435337 | |
Timothy Warren | 6acee9ca7a | |
Timothy Warren | 16ecfe3eb5 | |
Timothy Warren | c0fa4cfed2 | |
Timothy Warren | 00ef5c3706 | |
Timothy Warren | 618328a4c1 | |
Timothy Warren | e5ef054f5b | |
Timothy Warren | d9e81a7cf1 | |
Timothy Warren | b334a60486 | |
Timothy Warren | 17b01f6d48 | |
Timothy Warren | ef7c1da5f2 | |
Timothy Warren | 6718fc78e9 | |
Timothy Warren | 5216b60789 | |
Timothy Warren | 37c3d6ecf0 | |
Timothy Warren | eb12f57e7d | |
Timothy Warren | a03b9be329 | |
Timothy Warren | 07de5ff79b | |
Timothy Warren | 8aa94f7c14 | |
Timothy Warren | 8842df76be | |
Timothy Warren | 6047444077 | |
Timothy Warren | 95e8b7920a | |
Timothy Warren | 66b13ef7ba | |
Timothy Warren | b32968588a | |
Timothy Warren | fafd75b791 | |
Timothy Warren | 70eb4f11b3 | |
Timothy Warren | ae70eab9ea | |
Timothy Warren | 926179a72d | |
Timothy Warren | 143229bea4 | |
Timothy Warren | 3978c4d5cb | |
Timothy Warren | 705d48abad | |
Timothy Warren | 6044a676a6 | |
Timothy Warren | 245e1b4344 | |
Timothy Warren | ec9edff2f3 | |
Timothy Warren | 3fa5b7ab88 | |
Timothy Warren | ceb8159dae | |
Timothy Warren | 8b677ab7a7 | |
Timothy Warren | b4b5c63d65 | |
Timothy Warren | 347674f9e5 | |
Timothy Warren | d435a17ec8 | |
Timothy Warren | fde9b05bdf | |
Timothy Warren | 7d9d2e8990 | |
Timothy Warren | 995690a341 | |
Timothy Warren | 808b704383 | |
Timothy Warren | cd835055ec | |
Timothy Warren | 117427ced0 | |
Timothy Warren | aebb349543 | |
Timothy Warren | 37f7616ef4 | |
Timothy Warren | 28d4ce9e86 | |
Timothy Warren | 0e893f06ba | |
Timothy Warren | 58bb1ab0ba | |
Timothy Warren | 46041ccfc6 | |
Timothy Warren | 625edf5d0c | |
Timothy Warren | 4edfd9f62c | |
Timothy Warren | e5baccbf56 | |
Timothy Warren | bfd5ff32c1 | |
Timothy Warren | e1fd639ba9 | |
Timothy Warren | 4e88c27cfb | |
Timothy Warren | 0153271a62 | |
Timothy Warren | 7a529619ed | |
Timothy Warren | a6020df023 | |
Timothy Warren | cda711607a | |
Timothy Warren | c5bb555695 | |
Timothy Warren | ea5eb21941 | |
Timothy Warren | 038e61bf37 | |
Timothy Warren | bce1afa546 | |
Timothy Warren | 4502c2f183 | |
Timothy Warren | b070282899 | |
Timothy Warren | aa6965e98f | |
Timothy Warren | b2c8bff967 | |
Timothy Warren | ea2a368100 | |
Timothy Warren | 523fcbd0d9 | |
Timothy Warren | 003492a964 | |
Timothy Warren | e98699acbc | |
Timothy Warren | eb31096bff | |
Timothy Warren | d1a0147ae2 | |
Timothy Warren | 6c81ddbfd4 | |
Timothy Warren | 2fdca56501 | |
Timothy Warren | 47d6314178 | |
Timothy Warren | e73326fd6c | |
Timothy Warren | 251bbadd96 | |
Timothy Warren | 6ee198c742 | |
Timothy Warren | dfdc6b7356 | |
Timothy Warren | 94b15f455c | |
Timothy Warren | f6278a1304 | |
Timothy Warren | 04c5b135a7 | |
Timothy Warren | 8a8ea0b470 | |
Timothy Warren | 4e2437f2bc | |
Timothy Warren | 27c7f08d7f | |
Timothy Warren | b6f12ff2f6 | |
Timothy Warren | 317d8fd29b | |
Timothy Warren | b66a35843d | |
Timothy Warren | ac382a96a8 | |
Timothy Warren | 87d0960424 | |
Timothy Warren | be16ceecb2 | |
Timothy Warren | 105b0f52ca | |
Timothy Warren | 63a50f7ed8 | |
Timothy Warren | 4b9f97f49e | |
Timothy Warren | 8272cc7240 | |
Timothy Warren | 538201ef6f | |
Timothy Warren | 5e9780aad7 | |
Timothy Warren | f3265484da | |
Timothy Warren | 4c3f987b85 | |
Timothy Warren | 5e3fb46159 | |
Timothy Warren | 2c73e721d0 | |
Timothy Warren | 431f6e7d21 | |
Timothy Warren | c0e16c6d07 | |
Timothy Warren | 9c0b1e73ef | |
Timothy Warren | 05842baccb | |
Timothy Warren | 7f6b0178a8 | |
Timothy Warren | 8b938add27 | |
Timothy Warren | 8672112bdc | |
Timothy Warren | 033ea46754 | |
Timothy Warren | 24f80bc18c | |
Timothy Warren | 4b75987f21 | |
Timothy Warren | ca487901c2 | |
Timothy Warren | e195987436 | |
Timothy Warren | 350dae0109 | |
Timothy Warren | d514c319c0 | |
Timothy Warren | 4ace9b6806 | |
Timothy Warren | 64478d4507 | |
Timothy Warren | f314538972 | |
Timothy Warren | 155650961b | |
Timothy Warren | b3366131b8 | |
Timothy Warren | 040b7f3fdc | |
Timothy Warren | ef1e435c6b | |
Timothy Warren | a9c3a44f37 | |
Timothy Warren | 3842df13db | |
Timothy Warren | be2f7708ad | |
Timothy Warren | 29a4114e8c | |
Timothy Warren | 1690d8c1e0 | |
Timothy Warren | e84cbe1bb2 | |
Timothy Warren | d0af6fd9e8 | |
Timothy Warren | bcc7815ae6 | |
Timothy Warren | 5d87bd044c | |
Timothy Warren | e2b4fae83b | |
Timothy Warren | 019fff5d62 | |
Timothy Warren | cf1c495f90 | |
Timothy Warren | cee5a28816 | |
Timothy Warren | 83a6629f03 | |
Timothy Warren | 5810405f12 | |
Timothy Warren | 7b765c6d0b | |
Timothy Warren | a9acf23a15 | |
Timothy Warren | b14a827715 | |
Timothy Warren | 1cd8386559 | |
Timothy Warren | 1fa11cd50d | |
Timothy Warren | 8a7223593d | |
Timothy Warren | ccc9ad0c14 | |
Timothy Warren | 509db151e7 | |
Timothy Warren | 0bbc4fe4fb | |
Timothy Warren | 86c311dddf | |
Timothy Warren | eaf3554611 | |
Timothy Warren | 99aaf0303b | |
Timothy Warren | 6d1df75889 | |
Timothy Warren | 9c8e396c9a | |
Timothy Warren | c9ec11c2df | |
Timothy Warren | 587d5fa14e | |
Timothy Warren | 229703a6a6 | |
Timothy Warren | 5b8f0c4a9e | |
Timothy Warren | 41d71dac0c | |
Timothy Warren | 6dfa66dbde | |
Timothy Warren | 324abc0f61 | |
Timothy Warren | 3c0fd79195 | |
Timothy Warren | 0d30f57e83 | |
Timothy Warren | 247a9d0e5b | |
Timothy Warren | d6800dbc46 | |
Timothy Warren | ae283cd898 | |
Timothy Warren | a8f898822a | |
Timothy Warren | da936b325e | |
Timothy Warren | 8b3ce0f079 | |
Timothy Warren | c9ed90acb4 | |
Timothy Warren | 17a9539e94 | |
Timothy Warren | 6f717e6ab7 | |
Timothy Warren | 0f31a5e10a | |
Timothy Warren | e0376c78d1 | |
Timothy Warren | a6c253b969 | |
Timothy Warren | 5a607db93e | |
Timothy Warren | e7dc1e8e53 | |
Timothy Warren | 38a5b78295 | |
Timothy Warren | 034213fccc | |
Timothy Warren | 020f561773 | |
Timothy Warren | 2fc26bf4c6 | |
Timothy Warren | 09530efefd | |
Timothy Warren | 0624c3be67 | |
Timothy Warren | a71fb185bd | |
Timothy Warren | 98ae142757 | |
Timothy Warren | cd150d7fef | |
Timothy Warren | 3bca049cd8 | |
Timothy Warren | 95b06a7e7e | |
Timothy Warren | 81f02ad622 | |
Timothy Warren | 64d3c241f6 | |
Timothy Warren | 4a91a5cb5d | |
Timothy Warren | 226f0ced83 | |
Timothy Warren | bc2122dd98 | |
Timothy Warren | 2a2ff87b3b | |
Timothy Warren | 7c0d02758b | |
Timothy Warren | e6761807b8 | |
Timothy Warren | bfb5d6323d | |
Timothy Warren | b3a3e19146 | |
Timothy Warren | b5f8413ceb | |
Timothy Warren | b2554378e7 | |
Timothy Warren | 1fa5cce5ae | |
Timothy Warren | ea31131e0f | |
Timothy Warren | 675f8ed9b2 | |
Timothy Warren | 0dcf25e16c | |
Timothy Warren | dd46e292c4 | |
Timothy Warren | e9888c762e | |
Timothy Warren | 49295148d1 | |
Timothy Warren | d1421d2eb2 | |
Timothy Warren | 69c2482fc1 | |
Timothy Warren | 571bbf7595 | |
Timothy Warren | e01a96b8fb | |
Timothy Warren | 2c7d866677 | |
Timothy Warren | be2b387391 | |
Timothy Warren | 06c55a2094 | |
Timothy Warren | 9a7084078f | |
Timothy Warren | f71e9dbe4d | |
Timothy Warren | cecca5f9f0 | |
Timothy Warren | 79be0ebb34 | |
Timothy Warren | c7a77a2eb5 | |
Timothy Warren | c0c72e40e4 | |
Timothy Warren | 01bf7144c2 | |
Timothy Warren | f5f59b8382 | |
Timothy Warren | 75a5727a2e | |
Timothy Warren | 4f0c3f7984 | |
Timothy Warren | 83e0310ca7 | |
Timothy Warren | 9cac51bd82 | |
Timothy Warren | a434c032a2 | |
Timothy Warren | ad0154f431 | |
Timothy Warren | 080b112608 | |
Timothy Warren | 92ad051f6a | |
Timothy Warren | da8f4acb29 | |
Timothy Warren | 43ab033aec | |
Timothy Warren | 83f9d14630 | |
Timothy Warren | 634335187e | |
Timothy Warren | f06ba6e3cd | |
Timothy Warren | 675b1ef2f1 | |
Timothy Warren | a46a85bf71 | |
Timothy Warren | 3a739e3920 | |
Timothy Warren | 231babe218 | |
Timothy Warren | 4532bd1865 | |
Timothy Warren | 7b9adbf52e | |
Timothy Warren | f607403111 | |
Timothy Warren | 34996f009b | |
Timothy Warren | 39031ccf3e | |
Timothy Warren | 4451544389 | |
Timothy Warren | 5aa750cde7 | |
Timothy Warren | 11068029e8 | |
Timothy Warren | e75c52d9dc | |
Timothy Warren | 09f9aa2069 | |
Timothy Warren | bbe2fd0a5d | |
Timothy Warren | 2ad0a24483 | |
Timothy Warren | 0c52831ec6 | |
Timothy Warren | 55fca3f92f | |
Timothy Warren | 034174418b | |
Timothy Warren | e8f53542e8 | |
Timothy Warren | ae52f8737a | |
Timothy Warren | 888dbe7187 | |
Timothy Warren | 37f1391a07 | |
Timothy Warren | 8b8ece3dad | |
Timothy Warren | 015b3d0f34 | |
Timothy Warren | ee57f72ca6 | |
Timothy Warren | 9497f5c3df | |
Timothy Warren | 63fe8684c4 | |
Timothy Warren | a2fbe471e3 | |
Timothy Warren | c6f2981d63 | |
Timothy Warren | 410ff4fcc1 | |
Timothy Warren | 7e8e9a3141 | |
Timothy Warren | 98c24d4704 | |
Timothy Warren | c9533db6a1 | |
Timothy Warren | a4555ca908 | |
Timothy Warren | 45e14a7503 | |
Timothy Warren | 5eaa33ba82 | |
Timothy Warren | 38eee85752 | |
Timothy Warren | 2b9adb0395 | |
Timothy Warren | 5fb042a773 | |
Timothy Warren | 04ec5b2fd6 | |
Timothy Warren | b0ee397994 | |
Timothy Warren | 200ff1339c | |
Timothy Warren | a8e2049d08 | |
Timothy Warren | 07152cc3be | |
Timothy Warren | 89816dc062 | |
Timothy Warren | 3c4e34f1ed | |
Timothy Warren | 372b616101 | |
Timothy Warren | d93d22f7df | |
Timothy Warren | 6493f33faf | |
Timothy Warren | e66a9f885a | |
Timothy Warren | fa74e59854 | |
Timothy Warren | a15a97370b | |
Timothy Warren | 3fd7c84774 | |
Timothy Warren | 428a77b93d | |
Timothy Warren | 8e8ee81397 | |
Timothy Warren | cf12dfee76 | |
Timothy Warren | d2fc955260 | |
Timothy Warren | 4d991629b1 | |
Timothy Warren | 6111dfe6f9 | |
Timothy Warren | 921d594931 | |
Timothy Warren | ac13d57634 | |
Timothy Warren | 42d36ff4bb | |
Timothy Warren | 28da32f2ac | |
Timothy Warren | 08aff2ffe8 | |
Timothy Warren | 8b43dee20f | |
Timothy Warren | 0d2cde37a0 | |
Timothy Warren | 5cca3cf335 | |
Timothy Warren | 679560e185 | |
Timothy Warren | d157f097d1 | |
Timothy Warren | ec6f9b1189 | |
Timothy Warren | 7825e46321 | |
Timothy Warren | 6436ca2e9c | |
Timothy Warren | 81a1a927b1 | |
Timothy Warren | b210954874 | |
Timothy Warren | 472be3c4ed | |
Timothy Warren | edc6e6227e | |
Timothy Warren | 32b3617fed | |
Timothy Warren | 45bf1e1136 | |
Timothy Warren | fb3805b789 | |
Timothy Warren | b861db5d1f | |
Timothy Warren | e49ed606f5 | |
Timothy Warren | 8172d1a593 | |
Timothy Warren | a413d7d9ca | |
Timothy Warren | 8ceec846a5 | |
Timothy Warren | d2d48905d7 | |
Timothy Warren | 7d7ae73f5e | |
Timothy Warren | 82c8d36144 | |
Timothy Warren | c50b1da53b | |
Timothy Warren | ddd30fc713 | |
Timothy Warren | 1e28a1795d | |
Timothy Warren | b2300f4cfb | |
Timothy Warren | 92fe6b7146 | |
Timothy Warren | fa4ee22100 | |
Timothy Warren | 5ae6864a3f | |
Timothy Warren | fb567e85e9 | |
Timothy Warren | 2d33663318 | |
Timothy Warren | 528d3584b8 | |
Timothy Warren | 69b4d0c88b | |
Timothy Warren | e79021da29 | |
Timothy Warren | 79980683d1 | |
Timothy Warren | ec7e0cc93b | |
Timothy Warren | 15b26d8e39 | |
Timothy Warren | 377e102650 | |
Timothy Warren | 4a9e0f0293 | |
Timothy Warren | 0b18c06058 | |
Timothy Warren | 8d289f9eb5 | |
Timothy Warren | 394c1241fb | |
Timothy Warren | 0c4c0a436c | |
Timothy Warren | b8b5beeae1 | |
Timothy Warren | be0baac962 | |
Timothy Warren | cf0db8b9fa | |
Timothy Warren | dd90dd541c | |
Timothy Warren | cdd3878e55 | |
Timothy Warren | 51c26e6b30 | |
Timothy Warren | 0c2cc0e32b | |
Timothy Warren | 828b7a2154 | |
Timothy Warren | be4f54b99d | |
Timothy Warren | 25e57eb493 | |
Timothy Warren | 707a36fe53 | |
Timothy Warren | cd58e083bf | |
Timothy Warren | a259ea7b18 | |
Timothy Warren | 74602f60fa | |
Timothy Warren | 53b8ce44bf | |
Timothy Warren | e99205be54 | |
Timothy Warren | b52d301b2a | |
Timothy Warren | 52bd2773c0 | |
Timothy Warren | c9768855a5 | |
Timothy Warren | 1a45e57b7c | |
Timothy Warren | cd242596bc | |
Timothy Warren | bc6854a8e5 | |
Timothy Warren | 0d4b26e493 | |
Timothy Warren | 0e6a1b6591 | |
Timothy Warren | 84f0a27d86 | |
Timothy Warren | 8645926006 | |
Timothy Warren | 960537f8e0 | |
Timothy Warren | 5d9b7e9e63 | |
Timothy Warren | 6bf107e1ad | |
Timothy Warren | fa7651faf9 | |
Timothy Warren | 8b2cd7dd1e | |
Timothy Warren | 5d2dac5b99 | |
Timothy Warren | df7102d13d | |
Timothy Warren | 82b596ec3c | |
Timothy Warren | 497216cffa | |
Timothy Warren | 969ff75078 | |
Timothy Warren | f932a80e58 | |
Timothy Warren | ae6b1cb209 | |
Timothy Warren | d0a236e7ee | |
Timothy Warren | ed01b28c0d | |
Timothy Warren | 52e1d1822a | |
Timothy Warren | 5e9b3db1f2 | |
Timothy Warren | e0e1b59777 | |
Timothy Warren | 443ffaa132 | |
Timothy Warren | 4b0226838c | |
Timothy Warren | 79ce8c7790 | |
Timothy Warren | 0fa4a3c963 | |
Timothy Warren | 14967e9ad0 | |
Timothy Warren | b4573296d8 | |
Timothy Warren | 0127d65dfc | |
Timothy Warren | 14e8fa9f03 | |
Timothy Warren | adc331d60d | |
Timothy Warren | 65227e82ac | |
Timothy Warren | 56ae9ed80e | |
Timothy Warren | f3d9af311e | |
Timothy Warren | 136b7dab66 | |
Timothy Warren | cd7a836db0 | |
Timothy Warren | e87e5cb47c | |
Timothy Warren | 7122085590 | |
Timothy Warren | 9f0484a93b | |
Timothy Warren | 93038e61e5 | |
Timothy Warren | 30b43fd27c | |
Timothy Warren | 6efe1ffbc8 | |
Timothy Warren | 8898655a49 | |
Timothy Warren | 07b1422fad | |
Timothy Warren | 0232d18f1f | |
Timothy Warren | 0cef44c986 | |
Timothy Warren | 6ed755e252 | |
Timothy Warren | 85cd77267b | |
Timothy Warren | 488a01f8a5 | |
Timothy Warren | ee2760b2b5 | |
Timothy Warren | 84cb1cb520 | |
Timothy Warren | db07976403 | |
Timothy Warren | c96a3bbd50 | |
Timothy Warren | 8cbfaf3646 | |
Timothy Warren | 29c04e62be | |
Timothy Warren | 1224882092 | |
Timothy Warren | df8b64cff9 | |
Timothy Warren | ae42fafe84 | |
Timothy Warren | da9ebe8867 | |
Timothy Warren | 0a72e60f68 | |
Timothy Warren | 36874cbe55 | |
Timothy Warren | d94a280437 | |
Timothy Warren | 470c39cf79 | |
Timothy Warren | 941a15c4f4 | |
Timothy Warren | 3caad13577 | |
Timothy Warren | acbae86a6b | |
Timothy Warren | 34d0aaa8e5 | |
Timothy Warren | 7941303987 | |
Timothy Warren | da8b34a867 | |
Timothy Warren | 9bf362e08e | |
Timothy Warren | 6ee319b78c | |
Timothy Warren | 90f1b39db5 | |
Timothy Warren | 85a6fafd4f | |
Timothy Warren | 2bdf4be682 | |
Timothy Warren | 455adf4b11 | |
Timothy Warren | 9e49566641 | |
Timothy Warren | d1f0ab0c73 | |
Timothy Warren | 79153ae433 | |
Timothy Warren | 9c44b5189f | |
Timothy Warren | 312125f182 | |
Timothy Warren | 652eac5be0 | |
Timothy Warren | dba0d47789 | |
Timothy Warren | 4a3be8b4bf | |
Timothy Warren | 7f5966a147 | |
Timothy Warren | 8f8f528823 | |
Timothy Warren | 1ec7322b18 | |
Timothy Warren | 8f8c413927 | |
Timothy Warren | 50c543218b | |
Timothy Warren | 18af49f1f4 | |
Timothy Warren | 906a1f1efa | |
Timothy Warren | deecb5a912 | |
Timothy Warren | 02838c5024 | |
Timothy Warren | bf7f6973a4 | |
Timothy Warren | c0b54e11e1 | |
Timothy Warren | 0fe01e14e1 | |
Timothy Warren | 2f71a97327 | |
Timothy Warren | e73ea09ffd | |
Timothy Warren | bc0c3774eb | |
Timothy Warren | b10487ac13 | |
Timothy Warren | 64ed6cc0ef | |
Timothy Warren | b6c2aee17a | |
Timothy Warren | 5eea985828 | |
Timothy Warren | 1835e34690 | |
Timothy Warren | 2d0fa51c40 | |
Timothy Warren | 108c649a91 | |
Timothy Warren | c26a4ca8e8 | |
Timothy Warren | 9d9c1e2cce | |
Timothy Warren | 2cacff6b9b | |
Timothy Warren | 47a43517cb | |
Timothy Warren | 1b5590bda7 | |
Timothy Warren | 91a6d76c4b | |
Timothy Warren | 03964c446a | |
Timothy Warren | 74897faa78 | |
Timothy Warren | 2b2ec71edd | |
Timothy Warren | a80a860f8d | |
Timothy Warren | 3d54a0d62c | |
Timothy Warren | aabdf4de30 | |
Timothy Warren | 20c3d69717 | |
Timothy Warren | f57c24abe4 | |
Timothy Warren | 0ae52a13f3 | |
Timothy Warren | ece4f343e2 | |
Timothy Warren | b2fb562de6 | |
Timothy Warren | 19c2d0fddc | |
Timothy Warren | b2544fab16 | |
Timothy Warren | 96cbf78e28 | |
Timothy Warren | e30a0b867d | |
Timothy Warren | 11f9e41254 | |
Timothy Warren | a45eba3c56 | |
Timothy Warren | 9bc22baa80 | |
Timothy Warren | 7bad51d867 | |
Timothy Warren | f441b7680a | |
Timothy Warren | 10368fabe4 | |
Timothy Warren | a42a9bc785 | |
Timothy Warren | 42f152b366 | |
Timothy Warren | 0ec8d6d6b3 | |
Timothy Warren | 1eecff0178 | |
Timothy Warren | 3e72a66297 | |
Timothy Warren | 79c0c6cf90 | |
Timothy Warren | c1724397d3 | |
Timothy Warren | 9e30783ecb | |
Timothy Warren | 496eb68078 | |
Timothy Warren | 2fe45a6b57 | |
Timothy Warren | aca66ae86d | |
Timothy Warren | f619c78232 | |
Timothy Warren | d91432d960 | |
Timothy Warren | 62c06b7731 | |
Timothy Warren | e060e1b107 | |
Timothy Warren | 08c40de381 | |
Timothy Warren | c76bb4d32a | |
Timothy Warren | 93e58874de | |
Timothy Warren | 712956d564 | |
Timothy Warren | 39118d63e5 | |
Timothy Warren | 9f048b739e | |
Timothy Warren | 6e818b7f45 | |
Timothy Warren | e085754955 | |
Timothy Warren | 98bf1e455f | |
Timothy Warren | 9d84398ee7 | |
Timothy Warren | 09338e9132 | |
Timothy Warren | 239b0c055c | |
Timothy Warren | 444e18c1d9 | |
Timothy Warren | f1893d9708 | |
Timothy Warren | 3030b6b908 | |
Timothy Warren | cdf9ad0105 | |
Timothy Warren | 05c6f22f67 | |
Timothy Warren | e74fe9d2f5 | |
Timothy Warren | c9f5964d5e | |
Timothy Warren | e097b02457 | |
Timothy Warren | f2fcc8ee93 | |
Timothy Warren | 513ba4c70f | |
Timothy Warren | 66e51af8d7 | |
Timothy Warren | ba9c41f495 | |
Timothy Warren | 07ebb3e988 | |
Timothy Warren | c915ea871d | |
Timothy Warren | 656688a5f3 | |
Timothy Warren | 60c9c86580 | |
Timothy Warren | b0c49ca19c | |
Timothy Warren | dd2d25d54c | |
Timothy Warren | 230c80459e | |
Timothy Warren | f7915ba6f2 | |
Timothy Warren | ac971c5248 | |
Timothy Warren | eaa0e517c1 | |
Timothy Warren | 4e4ac58263 | |
Timothy Warren | 88e06a0052 | |
Timothy Warren | e0e63cf094 | |
Timothy Warren | 410d45029f | |
Timothy Warren | 8f3ce089a4 | |
Timothy Warren | 7e05a4dcc2 | |
Timothy Warren | 7b9b62b200 | |
Timothy Warren | 6bdd33da2a | |
Timothy Warren | 3894ba8312 | |
Timothy Warren | 473a046ed8 | |
Timothy Warren | 47e67d5d1a | |
Timothy Warren | e62558d607 | |
Timothy Warren | 6b9770698b | |
Timothy Warren | 893584696b | |
Timothy Warren | a108adfa23 | |
Timothy Warren | f00acbe2d6 | |
Timothy Warren | 563adace2f | |
Timothy Warren | cc7046f0ec | |
Timothy Warren | b415938583 | |
Timothy Warren | 2a41106638 | |
Timothy Warren | cb046dbde2 | |
Timothy Warren | 96d3dfdbb4 | |
Timothy Warren | fa22f7b493 | |
Timothy Warren | e2c6c14af8 | |
Timothy Warren | ca62a411e1 | |
Timothy Warren | 10bb167ff4 | |
Timothy Warren | ead6f8b487 | |
Timothy Warren | 672552a1e8 | |
Timothy Warren | f940606f0c | |
Timothy Warren | b271fd48a6 | |
Timothy Warren | 8e00f3a59d | |
Timothy Warren | ebc8006e04 | |
Timothy Warren | c84e76a869 | |
Timothy Warren | a178ac86c6 | |
Timothy Warren | 1e18344990 | |
Timothy Warren | 6b31e759c9 | |
Timothy Warren | 1842e1a30b | |
Timothy Warren | dd20c774ed | |
Timothy Warren | b2990e8457 | |
Timothy Warren | 4e48c8c4fa | |
Timothy Warren | 3605ec6d0b | |
Timothy Warren | 2151d64f25 | |
Timothy Warren | 89080171c9 | |
Timothy Warren | 733d3f3871 | |
Timothy Warren | a14d268652 | |
Timothy Warren | b5949466e4 | |
Timothy Warren | 1f9afd07f3 | |
Timothy Warren | 717c296e52 | |
Timothy Warren | b528b8dc17 | |
Timothy Warren | 98f709c36e | |
Timothy Warren | 330ac65e18 | |
Timothy Warren | e26e9f10b6 | |
Timothy Warren | 7d6c6fe2a0 | |
Timothy Warren | 5d0b879623 | |
Timothy Warren | 1a182a62a7 | |
Timothy Warren | 429c9861e3 | |
Timothy Warren | 99a5907e1b | |
Timothy Warren | 1e42d431c8 | |
Timothy Warren | 05a3d0e729 | |
Timothy Warren | 4b4a259f8a | |
Timothy Warren | 454324aba6 | |
Timothy Warren | 470795f21e | |
Timothy Warren | 95abe28322 | |
Timothy Warren | e35373a114 | |
Timothy Warren | ea1d342db5 | |
Timothy Warren | 83a200871d | |
Timothy Warren | 0f6c998109 | |
Timothy Warren | 440a999c2e | |
Timothy Warren | 7711765563 | |
Timothy Warren | 7efa180bbf | |
Timothy Warren | 84e4e6ce1b | |
Timothy Warren | 972d96c0e0 | |
Timothy Warren | 3bcb0442ca | |
Timothy Warren | 2afbe84afd | |
Timothy Warren | 06f07978dc | |
Timothy Warren | 2dde6f8a7d | |
Timothy Warren | c8789dc267 | |
Timothy Warren | ed16fd8d45 | |
Timothy Warren | dd74e85626 | |
Timothy Warren | ab19e9db08 | |
Timothy Warren | 98f3026a74 | |
Timothy Warren | 1ba302999d | |
Timothy Warren | 197a1f8326 | |
Timothy Warren | 9cc491a05e | |
Timothy Warren | bcd28acfc5 | |
Timothy Warren | d99f1e7595 | |
Timothy Warren | 1fb95adfbf | |
Timothy Warren | e8cc479a1e | |
Timothy Warren | fb29c90691 | |
Timothy Warren | 3027c8a4a8 | |
Timothy Warren | 0b6269edd6 | |
Timothy Warren | 12d05dd71a | |
Timothy Warren | ecf3bce14b | |
Timothy Warren | 5563902b69 | |
Timothy Warren | 874ad521a7 | |
Timothy Warren | a9f5e48bb2 | |
Timothy Warren | cbc7555cf2 | |
Timothy Warren | e08161aec2 | |
Timothy Warren | 7754377ecb | |
Timothy Warren | 0b2a0d4ea9 | |
Timothy Warren | 94f54366a7 | |
Timothy Warren | f5dc15659f | |
Timothy Warren | 1a21a23e73 | |
Timothy Warren | 4533ea0b26 | |
Timothy Warren | 540a82fe22 | |
Timothy Warren | 467763f8a6 | |
Timothy Warren | 99f2adf491 | |
Timothy Warren | 3fa8e7d8e8 | |
Timothy Warren | 9080f45601 | |
Timothy Warren | 08348fd349 | |
Timothy Warren | e109c6a06c | |
Timothy Warren | 457c2680b6 | |
Timothy Warren | a85f0e28c6 | |
Timothy Warren | b611d02a2c | |
Timothy Warren | e51159e7f3 | |
Timothy Warren | 2391ca14ca | |
Timothy Warren | 212c779552 | |
Timothy Warren | 0c2243a4f3 | |
Timothy Warren | 3981b8471a | |
Timothy Warren | 276a14a80c | |
Timothy Warren | b4949eaea2 | |
Timothy Warren | 993b625042 | |
Timothy Warren | 557b27ef77 | |
Timothy Warren | 45b04105f1 | |
Timothy Warren | ae4530b5d2 | |
Timothy Warren | 00fd23895d | |
Timothy Warren | aa67b941d4 | |
Timothy Warren | b94bf01dee | |
Timothy Warren | ba94f439bb | |
Timothy Warren | e3af767246 | |
Timothy Warren | b0f5bdf668 | |
Scrutinizer Auto-Fixer | 2b5d650ef8 | |
Timothy Warren | e9e16dd2b1 | |
Timothy Warren | 6b9be7e4d0 | |
Scrutinizer Auto-Fixer | 8b528d8659 | |
Timothy Warren | 9c81836648 | |
Timothy Warren | c04c85b999 | |
Timothy Warren | 6953cd08e6 | |
Timothy Warren | c82576e4dd | |
Scrutinizer Auto-Fixer | b70ff95f03 | |
Timothy Warren | 30b6afb601 | |
Timothy Warren | 6ca086d85b | |
Timothy Warren | bfb8500386 | |
Timothy Warren | 70bfe2bab7 | |
Timothy Warren | 7d1c8c383c | |
Timothy Warren | 3b876f2c42 | |
Timothy Warren | 56f9fa28aa | |
Timothy Warren | 2577bad0af | |
Timothy Warren | ed1e888c58 | |
Timothy Warren | 254afc990e | |
Timothy Warren | 8d1986d13b | |
Timothy Warren | 5d2cad4690 | |
Timothy Warren | 49b3f507a6 | |
Timothy Warren | 912f1b2ff2 | |
Timothy Warren | f75016ca69 | |
Scrutinizer Auto-Fixer | 4e1bcd962a | |
Timothy Warren | 69f35d222c | |
Timothy Warren | 898905c21c | |
Timothy Warren | f26adc1c28 | |
Timothy Warren | dfe91d0f1c | |
Timothy Warren | 05912cd540 | |
Timothy Warren | ab955f5154 | |
Timothy Warren | 8343fa9182 | |
Timothy Warren | 406c7c13cb | |
Scrutinizer Auto-Fixer | cd8185960b | |
Timothy Warren | 87f5324761 | |
Timothy Warren | a18f1926d9 | |
Timothy Warren | 17a335275c | |
Timothy Warren | ff13f2ce05 | |
Timothy Warren | 4937d4c099 | |
Timothy Warren | 1de8b46657 | |
Timothy Warren | 6aa51e5915 | |
Timothy Warren | 8936944743 | |
Timothy Warren | 76672d5a60 | |
Scrutinizer Auto-Fixer | 025bc4ef64 | |
Timothy Warren | a741526da1 | |
Timothy Warren | 799a3652d4 | |
Timothy Warren | 2f8886c28f | |
Timothy Warren | c3643565e3 | |
Timothy Warren | 28e03f6fb2 | |
Timothy Warren | 4ef2d6df57 | |
Timothy Warren | 779f4a00eb | |
Timothy Warren | d281c26a1c | |
Timothy Warren | 77399e3fa4 | |
Timothy Warren | 5f5bf66c75 | |
Timothy Warren | 385037b669 | |
Timothy Warren | fee09c50ae | |
Timothy Warren | 285a132d35 | |
Timothy Warren | 0b79ac3596 | |
Timothy Warren | de444857dd | |
Timothy Warren | d231b39fdc | |
Timothy Warren | d5c76a0f01 | |
Timothy Warren | 485ba46838 | |
Timothy Warren | 7f2000f180 | |
Timothy Warren | 34acf00b5e | |
Timothy Warren | bc6aed51c8 | |
Timothy Warren | 011bdda777 | |
Scrutinizer Auto-Fixer | 80f83e5f53 | |
Timothy Warren | b687dcf2a8 | |
Timothy Warren | bb3e5a643f | |
Scrutinizer Auto-Fixer | 4dddd3238c | |
Timothy Warren | a8e3c594e3 | |
Timothy Warren | 728850da08 | |
Timothy Warren | 443802332b | |
Scrutinizer Auto-Fixer | bf6550d0d9 | |
Timothy Warren | 37ef0f5621 | |
Timothy Warren | 38cfaa023d | |
Timothy Warren | f3772528c5 | |
Timothy Warren | dbbea163d1 | |
Timothy Warren | 7269c5c393 | |
Scrutinizer Auto-Fixer | 7e695edd29 | |
Timothy Warren | 6b322d18da | |
Timothy Warren | be96e2c6af | |
Timothy Warren | 5de88986ed | |
Timothy Warren | b68cbe5a26 | |
Timothy Warren | 2462e09205 | |
Timothy Warren | acb0a1e8a2 | |
Timothy Warren | a73e150ee3 | |
Timothy Warren | b8f753a424 | |
Timothy Warren | 23c16fc9c3 | |
Timothy Warren | 082b5296b9 | |
Timothy Warren | f87dd2636d | |
Timothy Warren | 60109b3531 | |
Timothy Warren | e2731db7ea | |
Timothy Warren | 156461a0b9 | |
Timothy Warren | 8a68559f0e | |
Timothy Warren | 97a5abe665 | |
Timothy Warren | d93f5a82a0 | |
Timothy Warren | 9d139a7d1c | |
Timothy Warren | e53f9abf3f | |
Timothy Warren | c5f3093a78 | |
Timothy Warren | 4622b6efa9 | |
Timothy Warren | 3e39aa0277 | |
Timothy Warren | 3e53ec1526 | |
Timothy Warren | 2abb5f3c3a |
|
@ -1,21 +1,157 @@
|
|||
|
||||
# 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
|
||||
app/cache/*
|
||||
app/logs/*
|
||||
public/images/*
|
||||
public/js/cache/*
|
||||
**/cache/**
|
||||
**/logs/**
|
||||
**/coverage/**
|
||||
**/docs/**
|
||||
**/node_modules/**
|
||||
composer.lock
|
||||
*.sqlite
|
||||
*.db
|
||||
*.sqlite3
|
||||
docs/*
|
||||
coverage/*
|
||||
apidocs/**
|
||||
tests/test_data/sessions/*
|
||||
build/coverage/*
|
||||
build/logs/*
|
||||
build/pdepend/*
|
||||
build/phpdox/*
|
||||
cache.properties
|
||||
tests/test_data/cache/*
|
||||
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
|
|
@ -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]
|
|
@ -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,
|
||||
],
|
||||
]);
|
20
.travis.yml
20
.travis.yml
|
@ -1,23 +1,17 @@
|
|||
language: php
|
||||
|
||||
install:
|
||||
- composer install
|
||||
- composer install --ignore-platform-reqs
|
||||
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm
|
||||
- 8.0
|
||||
- 8.1
|
||||
- nightly
|
||||
|
||||
script:
|
||||
- mkdir -p build/logs
|
||||
- phpunit -c build
|
||||
- php vendor/bin/phpunit -c build
|
||||
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.clover
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
#matrix:
|
||||
# allow_failures:
|
||||
# - php: nightly
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
56
README.md
56
README.md
|
@ -1,9 +1,9 @@
|
|||
# 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://jenkins.timshomepage.net/buildStatus/icon?job=animeclient)](https://jenkins.timshomepage.net/job/animeclient/)
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/timw4mail/HummingBirdAnimeClient/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/timw4mail/HummingBirdAnimeClient/?branch=master)
|
||||
[![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://list.timshomepage.net)]
|
||||
|
||||
|
@ -15,7 +15,7 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||
* On Hold
|
||||
* Dropped
|
||||
* Completed
|
||||
* All of the above
|
||||
* Combined View
|
||||
|
||||
* Manga List views (Each with list and cover views):
|
||||
* Reading
|
||||
|
@ -23,7 +23,7 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||
* On Hold
|
||||
* Dropped
|
||||
* Completed
|
||||
* All of the above
|
||||
* Combined View
|
||||
|
||||
* Anime collection view (segmented by media type):
|
||||
* Cover Images
|
||||
|
@ -31,28 +31,36 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||
|
||||
### Requirements
|
||||
|
||||
* PHP 5.5+
|
||||
* 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. Configure settings in `app/config/config.php` to your liking
|
||||
3. Create the following directories if they don't exist, and make sure they are world writable
|
||||
* app/cache
|
||||
* public/images/manga
|
||||
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/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. Login
|
||||
2. Use the form to select your media
|
||||
3. Save & Repeat as needed
|
||||
* For bulk 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
|
||||
|
||||
|
|
|
@ -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',
|
||||
]);
|
|
@ -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'
|
|
@ -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;
|
|
@ -1,95 +1,199 @@
|
|||
<?php
|
||||
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Bootstrap / Dependency Injection
|
||||
* 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;
|
||||
|
||||
use Aura\Html\HelperLocatorFactory;
|
||||
use Aura\Web\WebFactory;
|
||||
use Aura\Router\RouterFactory;
|
||||
use Aura\Router\RouterContainer;
|
||||
use Aura\Session\SessionFactory;
|
||||
use Monolog\Logger;
|
||||
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\Handler\BrowserConsoleHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
use Aviat\Ion\Di\Container;
|
||||
use Aviat\AnimeClient\Auth\HummingbirdAuth;
|
||||
use Aviat\AnimeClient\Model;
|
||||
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 DI container
|
||||
// -----------------------------------------------------------------------------
|
||||
return function(array $config_array = []) {
|
||||
return static function (array $configArray = []): Container {
|
||||
$container = new Container();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Logging
|
||||
// -------------------------------------------------------------------------
|
||||
$LOG_DIR = _dir(HB_APP_DIR, 'logs');
|
||||
|
||||
$app_logger = new Logger('animeclient');
|
||||
$app_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE));
|
||||
$app_logger->pushHandler(new BrowserConsoleHandler(Logger::DEBUG));
|
||||
$container->setLogger($app_logger);
|
||||
$appLogger = new Logger('animeclient');
|
||||
$appLogger->pushHandler(new RotatingFileHandler(_dir($LOG_DIR, 'app.log'), 2, Logger::WARNING));
|
||||
$container->setLogger($appLogger);
|
||||
|
||||
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);
|
||||
|
||||
$container->setLogger($logger, $channel);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Injected Objects
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Create Config Object
|
||||
$config = new Config($config_array);
|
||||
$container->set('config', $config);
|
||||
$container->set('config', static fn () => new Config($configArray));
|
||||
|
||||
// Create Cache Object
|
||||
$container->set('cache', static function (ContainerInterface $container): CacheInterface {
|
||||
$logger = $container->getLogger();
|
||||
$config = $container->get('config')->get('cache');
|
||||
|
||||
return new Teller($config, $logger);
|
||||
});
|
||||
|
||||
// Create Aura Router Object
|
||||
$aura_router = (new RouterFactory())->newInstance();
|
||||
$container->set('aura-router', $aura_router);
|
||||
$container->set('aura-router', static fn () => new RouterContainer());
|
||||
|
||||
// Create Html helper Object
|
||||
$html_helper = (new HelperLocatorFactory)->newInstance();
|
||||
$html_helper->set('menu', function() use ($container) {
|
||||
$menu_helper = new Helper\Menu();
|
||||
$menu_helper->setContainer($container);
|
||||
return $menu_helper;
|
||||
// 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;
|
||||
});
|
||||
$container->set('html-helper', $html_helper);
|
||||
|
||||
// Create Request/Response Objects
|
||||
$web_factory = new WebFactory([
|
||||
'_GET' => $_GET,
|
||||
'_POST' => $_POST,
|
||||
'_COOKIE' => $_COOKIE,
|
||||
'_SERVER' => $_SERVER,
|
||||
'_FILES' => $_FILES
|
||||
]);
|
||||
$container->set('request', $web_factory->newRequest());
|
||||
$container->set('response', $web_factory->newResponse());
|
||||
// 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
|
||||
$session = (new SessionFactory())->newInstance($_COOKIE);
|
||||
$container->set('session', $session);
|
||||
|
||||
$container->set('url-generator', new UrlGenerator($container));
|
||||
|
||||
|
||||
// Miscellaneous helper methods
|
||||
$anime_client = new AnimeClient();
|
||||
$anime_client->setContainer($container);
|
||||
$container->set('anime_client', $anime_client);
|
||||
$container->set('session', static fn () => (new SessionFactory())->newInstance($_COOKIE));
|
||||
|
||||
// Models
|
||||
$container->set('api-model', new Model\API($container));
|
||||
$container->set('anime-model', new Model\Anime($container));
|
||||
$container->set('manga-model', new Model\Manga($container));
|
||||
$container->set('anime-collection-model', new Model\AnimeCollection($container));
|
||||
$container->set('kitsu-model', static function (ContainerInterface $container): Kitsu\Model {
|
||||
$requestBuilder = new Kitsu\RequestBuilder($container);
|
||||
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
|
||||
|
||||
$container->set('auth', new HummingbirdAuth($container));
|
||||
$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->set('dispatcher', new Dispatcher($container));
|
||||
$container->setSimple('dispatcher', Dispatcher::class);
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
// End of bootstrap.php
|
||||
// End of bootstrap.php
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
################################################################################
|
||||
# Anilist API #
|
||||
################################################################################
|
||||
client_id = "your_client_id"
|
||||
client_secret = "your_client_secret"
|
||||
username = "user123"
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Lower level configuration
|
||||
//
|
||||
// You shouldn't generally need to change anything below this line
|
||||
// ----------------------------------------------------------------------------
|
||||
$base_config = [
|
||||
// Template file path
|
||||
'view_path' => _dir(APP_DIR, 'views'),
|
||||
|
||||
// Cache paths
|
||||
'data_cache_path' => _dir(APP_DIR, 'cache'),
|
||||
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
|
||||
|
||||
// Included config files
|
||||
'database' => require __DIR__ . '/database.php',
|
||||
'menus' => require __DIR__ . '/menus.php',
|
||||
'routes' => require __DIR__ . '/routes.php',
|
||||
];
|
|
@ -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
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
$config = [
|
||||
// ----------------------------------------------------------------------------
|
||||
// Username for anime and manga lists
|
||||
// ----------------------------------------------------------------------------
|
||||
'hummingbird_username' => 'timw4mail',
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Whose list is it?
|
||||
// ----------------------------------------------------------------------------
|
||||
'whose_list' => 'Tim',
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// General config
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// do you wish to show the anime collection?
|
||||
'show_anime_collection' => TRUE,
|
||||
|
||||
// do you wish to show the manga collection?
|
||||
'show_manga_collection' => FALSE,
|
||||
|
||||
// path to public directory on the server
|
||||
'asset_dir' => realpath(__DIR__ . '/../../public'),
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Included config files
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Routing paths and options
|
||||
'routing' => require __DIR__ . '/routing.php',
|
||||
];
|
|
@ -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
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
return [
|
||||
'collection' => [
|
||||
'type' => 'sqlite',
|
||||
'host' => '',
|
||||
'user' => '',
|
||||
'pass' => '',
|
||||
'port' => '',
|
||||
'name' => 'default',
|
||||
'database' => '',
|
||||
'file' => __DIR__ . '/../../anime_collection.sqlite',
|
||||
]
|
||||
];
|
|
@ -0,0 +1,11 @@
|
|||
################################################################################
|
||||
# Database Configuration #
|
||||
################################################################################
|
||||
|
||||
type = "sqlite"
|
||||
host = ""
|
||||
user = ""
|
||||
pass = ""
|
||||
port = ""
|
||||
database = ""
|
||||
file = "anime_collection.sqlite3"
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
return [
|
||||
'anime_list' => [
|
||||
'route_prefix' => '/anime',
|
||||
'items' => [
|
||||
'watching' => '/watching',
|
||||
'plan_to_watch' => '/plan_to_watch',
|
||||
'on_hold' => '/on_hold',
|
||||
'dropped' => '/dropped',
|
||||
'completed' => '/completed',
|
||||
'all' => '/all'
|
||||
]
|
||||
],
|
||||
'manga_list' => [
|
||||
'route_prefix' => '/manga',
|
||||
'items' => [
|
||||
'reading' => '/reading',
|
||||
'plan_to_read' => '/plan_to_read',
|
||||
'on_hold' => '/on_hold',
|
||||
'dropped' => '/dropped',
|
||||
'completed' => '/completed',
|
||||
'all' => '/all'
|
||||
]
|
||||
]
|
||||
];
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/* $config = */require 'config.php';
|
||||
|
||||
// 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/';
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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'
|
||||
],
|
||||
'anime_collection' => [
|
||||
'lib/jquery.min.js',
|
||||
'lib/jquery.throttle-debounce.js',
|
||||
'lib/jsrender.js',
|
||||
'anime_collection.js'
|
||||
],
|
||||
'manga_collection' => [
|
||||
'lib/jquery.min.js',
|
||||
'lib/jquery.throttle-debounce.js',
|
||||
'lib/jsrender.js',
|
||||
'manga_collection.js'
|
||||
]
|
||||
];
|
||||
|
||||
// End of js_groups.php
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
return [
|
||||
'convention' => [
|
||||
'default_namespace' => '\\Aviat\\AnimeClient\\Controller',
|
||||
'default_controller' => '\\Aviat\\AnimeClient\\Controller\\Anime',
|
||||
'default_method' => 'index',
|
||||
'404_method' => 'not_found'
|
||||
],
|
||||
// Routes on all controllers
|
||||
'common' => [
|
||||
'update' => [
|
||||
'path' => '/{controller}/update',
|
||||
'action' => 'update',
|
||||
'verb' => 'post',
|
||||
'tokens' => [
|
||||
'controller' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
],
|
||||
// Routes on anime collection controller
|
||||
'collection' => [
|
||||
'collection_search' => [
|
||||
'path' => '/collection/search',
|
||||
'action' => 'search'
|
||||
],
|
||||
'collection_add_form' => [
|
||||
'path' => '/collection/add',
|
||||
'action' => 'form',
|
||||
'params' => [],
|
||||
],
|
||||
'collection_edit_form' => [
|
||||
'path' => '/collection/edit/{id}',
|
||||
'action' => 'form',
|
||||
'tokens' => [
|
||||
'id' => '[0-9]+'
|
||||
]
|
||||
],
|
||||
'collection_add' => [
|
||||
'path' => '/collection/add',
|
||||
'action' => 'add',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'collection_edit' => [
|
||||
'path' => '/collection/edit',
|
||||
'action' => 'edit',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'collection' => [
|
||||
'path' => '/collection/view{/view}',
|
||||
'action' => 'index',
|
||||
'params' => [],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Routing
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
// Subfolder prefix for url, if in a subdirectory of the web root
|
||||
'subfolder_prefix' => '',
|
||||
|
||||
// Path to public directory, where images/css/javascript are located,
|
||||
// appended to the url
|
||||
'asset_path' => '/public',
|
||||
|
||||
// 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' => "all", // reading|plan_to_read|on_hold|dropped|completed|all
|
||||
|
||||
// Default view type (cover_view/list_view)
|
||||
'default_view_type' => 'cover_view',
|
||||
];
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
<article class="<?= $className ?>">
|
||||
<div class="name">
|
||||
<a href="<?= $link ?>"><?= $name ?></a>
|
||||
</div>
|
||||
<a href="<?= $link ?>"><?= $picture ?></a>
|
||||
</article>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
|||
<section class="<?= $className ?>">
|
||||
<?php foreach ($data as $tabName => $tabData): ?>
|
||||
<?= $callback($tabData, $tabName) ?>
|
||||
<?php endforeach ?>
|
||||
</section>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,4 +1,6 @@
|
|||
<main>
|
||||
<h1>404</h1>
|
||||
<h2>Page Not Found</h2>
|
||||
<h2><?= $message ?></h2>
|
||||
<pre>(╯°□°)╯︵ ┻━┻
|
||||
┬─┬ノ( º _ ºノ)</pre>
|
||||
</main>
|
||||
|
|
|
@ -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: <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> </td>
|
||||
<td>
|
||||
<input type="hidden" name="type" value="anime" />
|
||||
<button type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<?php endif ?>
|
|
@ -1,44 +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><?= $escape->html($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="<?= $item['anime']['slug'] ?>">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<button class="plus_one" hidden>+1 Episode</button>
|
||||
<?php endif ?>
|
||||
<?= $helper->img($item['anime']['image']); ?>
|
||||
<div class="name">
|
||||
<a href="<?= $escape->attr($item['anime']['url']) ?>">
|
||||
<?= $escape->html($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['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']['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>
|
||||
<?php if ($item['private'] && ! $_->isAuthenticated()) continue; ?>
|
||||
<?= $_->component->animeCover($item) ?>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -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",
|
||||
]) ?>
|
||||
<?= $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",
|
||||
]) ?>
|
||||
<?= $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>
|
|
@ -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> </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 ?>
|
|
@ -1,50 +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>Airing Status</th>
|
||||
<th>Score</th>
|
||||
<th>Type</th>
|
||||
<th>Progress</th>
|
||||
<th>Rated</th>
|
||||
<th>Notes</th>
|
||||
<th>Genres</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $item): ?>
|
||||
<tr id="a-<?= $item['id'] ?>">
|
||||
<td class="align_left">
|
||||
<a href="<?= $item['anime']['url'] ?>">
|
||||
<?= $item['anime']['title'] ?>
|
||||
</a>
|
||||
<?= ( ! empty($item['anime']['alternate_title'])) ? " <br /> " . $item['anime']['alternate_title'] : "" ?>
|
||||
</td>
|
||||
<td class="align_left"><?= $item['airing']['status'] ?></td>
|
||||
<td><?= $item['user_rating'] ?> / 10 </td>
|
||||
<td><?= $item['anime']['type'] ?></td>
|
||||
<td>Episodes: <?= $item['episodes']['watched'] ?> / <?= $item['episodes']['total'] ?></td>
|
||||
<td><?= $item['anime']['age_rating'] ?></td>
|
||||
<td><?= $item['notes'] ?></td>
|
||||
<td class="align-left">
|
||||
<ul>
|
||||
<?php sort($item['anime']['genres']) ?>
|
||||
<?php foreach($item['anime']['genres'] as $genre): ?>
|
||||
<li><?= $genre ?></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</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"> </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> / <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="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
||||
<script defer="defer" src="<?= $_->assetUrl('js/tables.min.js') ?>"></script>
|
|
@ -0,0 +1,3 @@
|
|||
<main>
|
||||
<h1><?= $title ?></h1>
|
||||
</main>
|
|
@ -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>
|
|
@ -1,38 +1,39 @@
|
|||
<?php if ($auth->is_authenticated()): ?>
|
||||
<?php if ($_->isAuthenticated()): ?>
|
||||
<main>
|
||||
<h2>Add <?= ucfirst($collection_type) ?> to your Collection</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<dl>
|
||||
<dt>Series</dt>
|
||||
<dd><label>Search for anime name: <input type="search" id="search" /></label></dd>
|
||||
<dd>
|
||||
<section id="series_list" class="media-wrap">
|
||||
<?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: <input type="search" id="search-anime-collection" name="search" /></label>
|
||||
<section id="series-list" class="media-wrap">
|
||||
</section>
|
||||
</dd>
|
||||
|
||||
<dt><label for="media_id">Media</label></dt>
|
||||
<dd>
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</dd>
|
||||
|
||||
<dt><label for="notes">Notes</label></dt>
|
||||
<dd><textarea id="notes" name="notes"></textarea></dd>
|
||||
|
||||
<dt> </dt>
|
||||
<dd>
|
||||
<button type="submit">Save</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</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> </td>
|
||||
<td>
|
||||
<button type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<template id="show_list">
|
||||
<article class="media">
|
||||
<div class="name"><label><input type="radio" name="id" value="{{:id}}" /> <span>{{:title}}<br />{{:alternate_title}}</span></label></div>
|
||||
<img src="{{:cover_image}}" alt="{{:title}}" />
|
||||
</article>
|
||||
</template>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=anime_collection') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -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>
|
|
@ -1,38 +1,26 @@
|
|||
<main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
[<a href="<?= $urlGenerator->url('collection/add', 'anime') ?>">Add Item</a>]
|
||||
<?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: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
||||
<img src="<?= $urlGenerator->asset_url('images', 'anime', basename($item['cover_image'])) ?>" />
|
||||
<div class="name">
|
||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
||||
<?= $item['title'] ?>
|
||||
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
|
||||
</a>
|
||||
</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>
|
||||
<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,
|
||||
]);
|
||||
}
|
||||
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<span>[<a href="<?= $urlGenerator->url("collection/edit/{$item['hummingbird_id']}", "anime") ?>">Edit</a>]</span>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
</section>
|
||||
<?php endforeach ?>
|
||||
return implode('', array_map('mb_trim', $rendered));
|
||||
}, 'media-wrap', true) ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
|
|
|
@ -1,37 +1,66 @@
|
|||
<?php if ($auth->is_authenticated()): ?>
|
||||
<?php use function Aviat\AnimeClient\renderTemplate ?>
|
||||
<?php if ($_->isAuthenticated()): ?>
|
||||
<main>
|
||||
<h2>Edit Anime Collection Item</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<dl>
|
||||
<h2><?= $item['title'] ?></h2>
|
||||
<h3><?= $item['alternate_title'] ?></h3>
|
||||
|
||||
<dt><label for="media_id">Media</label></dt>
|
||||
<dd>
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option <?= $item['media_id'] == $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</dd>
|
||||
|
||||
<dt><label for="notes">Notes</label></dt>
|
||||
<dd><textarea id="notes" name="notes"><?= $item['notes'] ?></textarea></dd>
|
||||
|
||||
<dt> </dt>
|
||||
<dd>
|
||||
<?php if($action === 'Edit'): ?>
|
||||
<input type="hidden" name="hummingbird_id" value="<?= $item['hummingbird_id'] ?>" />
|
||||
<?php endif ?>
|
||||
<button type="submit">Save</button>
|
||||
</dd>
|
||||
</dl>
|
||||
<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> </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>
|
||||
<template id="show_list">
|
||||
<article class="media">
|
||||
<div class="name"><label><input type="radio" name="id" value="{{:id}}" /> <span>{{:title}}<br />{{:alternate_title}}</span></label></div>
|
||||
<img src="{{:cover_image}}" alt="{{:title}}" />
|
||||
</article>
|
||||
</template>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=anime_collection') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -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>
|
|
@ -1,56 +1,55 @@
|
|||
<?php use function Aviat\AnimeClient\{colNotEmpty, renderTemplate}; ?>
|
||||
<main>
|
||||
<?php /* if (is_logged_in()): ?>
|
||||
[<a href="<?= $urlGenerator->full_url('collection/add', 'anime') ?>">Add Item</a>]
|
||||
<?php endif */ ?>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<?php /*<th>Alternate Title</th>*/ ?>
|
||||
<th>Episode Count</th>
|
||||
<th>Episode Length</th>
|
||||
<th>Show Type</th>
|
||||
<th>Age Rating</th>
|
||||
<th>Notes</th>
|
||||
<?php /*if (is_logged_in()): ?>
|
||||
<th> </th>
|
||||
<?php endif*/ ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $item): ?>
|
||||
<tr>
|
||||
<td class="align_left">
|
||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
||||
<?= $item['title'] ?>
|
||||
</a>
|
||||
<?= ( ! empty($item['alternate_title'])) ? " · " . $item['alternate_title'] : "" ?>
|
||||
</td>
|
||||
<?php /*<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>
|
||||
<?php /* if (is_logged_in()): ?>
|
||||
<td>[<a href="<?= $urlGenerator->full_url("collection/edit/{$item['hummingbird_id']}", "anime") ?>">Edit</a>]</td>
|
||||
<?php endif */ ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
<?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> </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 src="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
||||
<script defer="defer" src="<?= $_->assetUrl('js/tables.min.js') ?>"></script>
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
|||
<main>
|
||||
<h1><?= $title ?></h1>
|
||||
<h2><?= $message ?></h2>
|
||||
<div><?= $long_message ?></div>
|
||||
</main>
|
|
@ -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>
|
||||
|
|
|
@ -1,50 +1,42 @@
|
|||
<?php namespace Aviat\AnimeClient ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title><?= $title ?></title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="<?= $urlGenerator->asset_url('css.php?g=base') ?>" />
|
||||
<script>
|
||||
var BASE_URL = "<?= $urlGenerator->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="<?= $escape->attr($url_type) ?> list">
|
||||
<h1 class="flex flex-align-end flex-wrap">
|
||||
<span class="flex-no-wrap grow-1">
|
||||
<?php if(strpos($route_path, 'collection') === FALSE): ?>
|
||||
<a href="<?= $escape->attr($urlGenerator->default_url($url_type)) ?>">
|
||||
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> List
|
||||
</a>
|
||||
<?php if($config->get("show_{$url_type}_collection")): ?>
|
||||
[<a href="<?= $urlGenerator->url('collection/view') ?>"><?= ucfirst($url_type) ?> Collection</a>]
|
||||
<?php endif ?>
|
||||
[<a href="<?= $urlGenerator->default_url($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
|
||||
<?php else: ?>
|
||||
<a href="<?= $urlGenerator->url('collection/view') ?>">
|
||||
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> Collection
|
||||
</a>
|
||||
[<a href="<?= $urlGenerator->default_url('anime') ?>">Anime List</a>]
|
||||
[<a href="<?= $urlGenerator->default_url('manga') ?>">Manga List</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
[<a href="<?= $urlGenerator->url("/{$url_type}/logout", $url_type) ?>">Logout</a>]
|
||||
<?php else: ?>
|
||||
[<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= $config->get('whose_list') ?>'s Login</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</h1>
|
||||
<nav>
|
||||
<?= $helper->menu($menu_name) ?>
|
||||
<?php if ($container->get('anime_client')->is_view_page()): ?>
|
||||
<br />
|
||||
<ul>
|
||||
<li class="<?= AnimeClient::is_not_selected('list', $urlGenerator->last_segment()) ?>"><a href="<?= $urlGenerator->url($route_path) ?>">Cover View</a></li>
|
||||
<li class="<?= AnimeClient::is_selected('list', $urlGenerator->last_segment()) ?>"><a href="<?= $urlGenerator->url("{$route_path}/list") ?>">List View</a></li>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
</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>
|
|
@ -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} – {$endTime}" ?>
|
||||
<?php else: ?>
|
||||
<?= "{$startDate} {$startTime} – {$endDate} {$endTime}" ?>
|
||||
<?php endif ?>
|
||||
<?php else: ?>
|
||||
<?= $item['updated']->format('l, F d h:i:s A') ?>
|
||||
<?php endif ?>
|
||||
</small>
|
||||
</section>
|
||||
</article>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
</main>
|
|
@ -0,0 +1,6 @@
|
|||
<noscript>
|
||||
<div class="message error">
|
||||
<span class="icon"></span>
|
||||
This feature requires Javascript to function :(
|
||||
</div>
|
||||
</noscript>
|
|
@ -1,17 +1,16 @@
|
|||
<main>
|
||||
<h2><?= $_->config->get('whose_list'); ?>'s Login</h2>
|
||||
<?= $message ?>
|
||||
<aside>
|
||||
<form method="post" action="<?= $urlGenerator->full_url($urlGenerator->path(), $url_type) ?>">
|
||||
<dl>
|
||||
<?php /*<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> </dt>
|
||||
<dd><button type="submit">Login</button></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> </td>
|
||||
<td><button type="submit">Login</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
|
@ -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 ?>
|
|
@ -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: <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> </td>
|
||||
<td>
|
||||
<input type="hidden" name="type" value="manga" />
|
||||
<button type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<?php endif ?>
|
|
@ -1,50 +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><?= $escape->html($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 ($auth->is_authenticated()): ?>
|
||||
<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="<?= $escape->attr($item['manga']['image']) ?>" />
|
||||
<div class="name">
|
||||
<a href="<?= $item['manga']['url'] ?>">
|
||||
<?= $escape->html($item['manga']['title']) ?>
|
||||
<?= (isset($item['manga']['alternate_title'])) ? "<br />({$item['manga']['alternate_title']})" : ""; ?>
|
||||
</a>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="row">
|
||||
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||
</div>
|
||||
<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 class="row">
|
||||
<div class="volume_completion">
|
||||
Volumes: <span class="volumes_read"><?= $item['volumes']['read'] ?></span> /
|
||||
<span class="volume_count"><?= $item['volumes']['total'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?= $component->mangaCover($item, $name) ?>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -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>
|
|
@ -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> </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 ?>
|
|
@ -1,37 +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['id'] ?>">
|
||||
<td class="align_left">
|
||||
<a href="<?= $item['manga']['url'] ?>">
|
||||
<?= $item['manga']['title'] ?>
|
||||
</a>
|
||||
<?= ( ! is_null($item['manga']['alternate_title'])) ? " · " . $item['manga']['alternate_title'] : "" ?>
|
||||
</td>
|
||||
<td><?= $item['user_rating'] ?> / 10</td>
|
||||
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
|
||||
<td><?= $item['volumes']['read'] ?> / <?= $item['volumes']['total'] ?></td>
|
||||
<td><?= $item['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> </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="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
||||
<script defer="defer" src="<?= $_->assetUrl('js/tables.min.js') ?>"></script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="message <?= $escape->attr($stat_class) ?>">
|
||||
<div class="message <?= $_->escape->attr($message_type) ?>">
|
||||
<span class="icon"></span>
|
||||
<?= $escape->html($message) ?>
|
||||
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
|
||||
<?= $_->escape->html($message) ?>
|
||||
<span class="close"></span>
|
||||
</div>
|
|
@ -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>
|
|
@ -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 ?>
|
|
@ -0,0 +1,5 @@
|
|||
<article>
|
||||
<label for="<?= $fieldName ?>"><?= $field['title'] ?></label><br />
|
||||
<small><?= $field['description'] ?></small><br />
|
||||
<?= $_->h->field($fieldName, $field); ?>
|
||||
</article>
|
|
@ -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 ?>
|
|
@ -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 ?>
|
|
@ -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>
|
|
@ -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 ?>
|
|
@ -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>
|
251
build.xml
251
build.xml
|
@ -1,251 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="animeclient" default="full-build">
|
||||
<!-- By default, we assume all tools to be on the $PATH -->
|
||||
<property name="pdepend" value="pdepend"/>
|
||||
<property name="phpcpd" value="phpcpd"/>
|
||||
<property name="phpcs" value="phpcs"/>
|
||||
<property name="phpdox" value="phpdox"/>
|
||||
<property name="phploc" value="phploc"/>
|
||||
<property name="phpmd" value="phpmd"/>
|
||||
<property name="phpunit" value="phpunit"/>
|
||||
<property name="sonar" value="sonar-runner"/>
|
||||
|
||||
<!-- Use this when the tools are located as PHARs in ${basedir}/build/tools
|
||||
<property name="pdepend" value="${basedir}/build/tools/pdepend.phar"/>
|
||||
<property name="phpcpd" value="${basedir}/build/tools/phpcpd.phar"/>
|
||||
<property name="phpcs" value="${basedir}/build/tools/phpcs.phar"/>
|
||||
<property name="phpdox" value="${basedir}/build/tools/phpdox.phar"/>
|
||||
<property name="phploc" value="${basedir}/build/tools/phploc.phar"/>
|
||||
<property name="phpmd" value="${basedir}/build/tools/phpmd.phar"/>
|
||||
<property name="phpunit" value="${basedir}/build/tools/phpunit.phar"/> -->
|
||||
|
||||
<!-- Use this when the tools are managed by Composer in ${basedir}/vendor/bin
|
||||
<property name="pdepend" value="${basedir}/vendor/bin/pdepend"/>
|
||||
<property name="phpcpd" value="${basedir}/vendor/bin/phpcpd"/>
|
||||
<property name="phpcs" value="${basedir}/vendor/bin/phpcs"/>
|
||||
<property name="phpdox" value="${basedir}/vendor/bin/phpdox"/>
|
||||
<property name="phploc" value="${basedir}/vendor/bin/phploc"/>
|
||||
<property name="phpmd" value="${basedir}/vendor/bin/phpmd"/>
|
||||
<property name="phpunit" value="${basedir}/vendor/bin/phpunit"/> -->
|
||||
|
||||
<target name="full-build"
|
||||
depends="prepare,static-analysis,phpunit,phpdox,sonar,-check-failure"
|
||||
description="Performs static analysis, runs the tests, and generates project documentation"/>
|
||||
|
||||
<target name="full-build-parallel"
|
||||
depends="prepare,static-analysis-parallel,phpunit,phpdox,-check-failure"
|
||||
description="Performs static analysis (executing the tools in parallel), runs the tests, and generates project documentation"/>
|
||||
|
||||
<target name="quick-build"
|
||||
depends="prepare,lint,phpunit-no-coverage"
|
||||
description="Performs a lint check and runs the tests (without generating code coverage reports)"/>
|
||||
|
||||
<target name="static-analysis"
|
||||
depends="lint,phploc-ci,pdepend,phpcs-ci,phpcpd-ci"
|
||||
description="Performs static analysis" />
|
||||
|
||||
<!-- Adjust the threadCount attribute's value to the number of CPUs -->
|
||||
<target name="static-analysis-parallel"
|
||||
description="Performs static analysis (executing the tools in parallel)">
|
||||
<parallel threadCount="2">
|
||||
<sequential>
|
||||
<antcall target="pdepend"/>
|
||||
</sequential>
|
||||
<antcall target="lint"/>
|
||||
<antcall target="phpcpd-ci"/>
|
||||
<antcall target="phpcs-ci"/>
|
||||
<antcall target="phploc-ci"/>
|
||||
</parallel>
|
||||
</target>
|
||||
|
||||
<target name="clean"
|
||||
unless="clean.done"
|
||||
description="Cleanup build artifacts">
|
||||
<delete dir="${basedir}/build/api"/>
|
||||
<delete dir="${basedir}/build/coverage"/>
|
||||
<delete dir="${basedir}/build/logs"/>
|
||||
<delete dir="${basedir}/build/pdepend"/>
|
||||
<delete dir="${basedir}/build/phpdox"/>
|
||||
<property name="clean.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="prepare"
|
||||
unless="prepare.done"
|
||||
depends="clean"
|
||||
description="Prepare for build">
|
||||
<mkdir dir="${basedir}/build/api"/>
|
||||
<mkdir dir="${basedir}/build/coverage"/>
|
||||
<mkdir dir="${basedir}/build/logs"/>
|
||||
<mkdir dir="${basedir}/build/pdepend"/>
|
||||
<mkdir dir="${basedir}/build/phpdox"/>
|
||||
<property name="prepare.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="lint"
|
||||
unless="lint.done"
|
||||
description="Perform syntax check of sourcecode files">
|
||||
<apply executable="php" taskname="lint">
|
||||
<arg value="-l" />
|
||||
|
||||
<fileset dir="${basedir}/src">
|
||||
<include name="**/*.php" />
|
||||
<modified />
|
||||
</fileset>
|
||||
|
||||
<fileset dir="${basedir}/tests">
|
||||
<include name="**/*.php" />
|
||||
<modified />
|
||||
</fileset>
|
||||
</apply>
|
||||
|
||||
<property name="lint.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phploc"
|
||||
unless="phploc.done"
|
||||
description="Measure project size using PHPLOC and print human readable output. Intended for usage on the command line.">
|
||||
<exec executable="${phploc}" taskname="phploc">
|
||||
<arg value="--count-tests" />
|
||||
<arg path="${basedir}/src" />
|
||||
<arg path="${basedir}/tests" />
|
||||
</exec>
|
||||
|
||||
<property name="phploc.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phploc-ci"
|
||||
unless="phploc.done"
|
||||
depends="prepare"
|
||||
description="Measure project size using PHPLOC and log result in CSV and XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${phploc}" taskname="phploc">
|
||||
<arg value="--count-tests" />
|
||||
<arg value="--log-csv" />
|
||||
<arg path="${basedir}/build/logs/phploc.csv" />
|
||||
<arg value="--log-xml" />
|
||||
<arg path="${basedir}/build/logs/phploc.xml" />
|
||||
<arg path="${basedir}/src" />
|
||||
<arg path="${basedir}/tests" />
|
||||
</exec>
|
||||
|
||||
<property name="phploc.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="pdepend"
|
||||
unless="pdepend.done"
|
||||
depends="prepare"
|
||||
description="Calculate software metrics using PHP_Depend and log result in XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${pdepend}" taskname="pdepend">
|
||||
<arg value="--jdepend-xml=${basedir}/build/logs/jdepend.xml" />
|
||||
<arg value="--jdepend-chart=${basedir}/build/pdepend/dependencies.svg" />
|
||||
<arg value="--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg" />
|
||||
<arg path="${basedir}/src" />
|
||||
</exec>
|
||||
|
||||
<property name="pdepend.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpcs"
|
||||
unless="phpcs.done"
|
||||
description="Find coding standard violations using PHP_CodeSniffer and print human readable output. Intended for usage on the command line before committing.">
|
||||
<exec executable="${phpcs}" taskname="phpcs">
|
||||
<arg value="--standard=PSR2" />
|
||||
<arg value="--extensions=php" />
|
||||
<arg value="--ignore=autoload.php" />
|
||||
<arg path="${basedir}/src" />
|
||||
<arg path="${basedir}/tests" />
|
||||
</exec>
|
||||
|
||||
<property name="phpcs.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpcs-ci"
|
||||
unless="phpcs.done"
|
||||
depends="prepare"
|
||||
description="Find coding standard violations using PHP_CodeSniffer and log result in XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${phpcs}" output="/dev/null" taskname="phpcs">
|
||||
<arg value="--report=checkstyle" />
|
||||
<arg value="--report-file=${basedir}/build/logs/checkstyle.xml" />
|
||||
<arg value="--standard=PSR2" />
|
||||
<arg value="--extensions=php" />
|
||||
<arg value="--ignore=autoload.php" />
|
||||
<arg path="${basedir}/src" />
|
||||
<arg path="${basedir}/tests" />
|
||||
</exec>
|
||||
|
||||
<property name="phpcs.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpcpd"
|
||||
unless="phpcpd.done"
|
||||
description="Find duplicate code using PHPCPD and print human readable output. Intended for usage on the command line before committing.">
|
||||
<exec executable="${phpcpd}" taskname="phpcpd">
|
||||
<arg path="${basedir}/src" />
|
||||
</exec>
|
||||
|
||||
<property name="phpcpd.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpcpd-ci"
|
||||
unless="phpcpd.done"
|
||||
depends="prepare"
|
||||
description="Find duplicate code using PHPCPD and log result in XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${phpcpd}" taskname="phpcpd">
|
||||
<arg value="--log-pmd" />
|
||||
<arg path="${basedir}/build/logs/pmd-cpd.xml" />
|
||||
<arg path="${basedir}/src" />
|
||||
</exec>
|
||||
|
||||
<property name="phpcpd.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpunit"
|
||||
unless="phpunit.done"
|
||||
depends="prepare"
|
||||
description="Run unit tests with PHPUnit">
|
||||
<exec executable="${phpunit}" resultproperty="result.phpunit" taskname="phpunit">
|
||||
<arg value="--configuration"/>
|
||||
<arg path="${basedir}/build/phpunit.xml"/>
|
||||
</exec>
|
||||
|
||||
<property name="phpunit.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpunit-no-coverage"
|
||||
unless="phpunit.done"
|
||||
depends="prepare"
|
||||
description="Run unit tests with PHPUnit (without generating code coverage reports)">
|
||||
<exec executable="${phpunit}" failonerror="true" taskname="phpunit">
|
||||
<arg value="--configuration"/>
|
||||
<arg path="${basedir}/build/phpunit.xml"/>
|
||||
<arg value="--no-coverage"/>
|
||||
</exec>
|
||||
|
||||
<property name="phpunit.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="phpdox"
|
||||
unless="phpdox.done"
|
||||
depends="phploc-ci,phpcs-ci,phpunit"
|
||||
description="Generate project documentation using phpDox">
|
||||
<exec executable="${phpdox}" dir="${basedir}/build" taskname="phpdox"/>
|
||||
|
||||
<property name="phpdox.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="sonar"
|
||||
depends="phpunit">
|
||||
<exec executable="${sonar}" taskname="sonar"/>
|
||||
<property name="sonar.done" value="true"/>
|
||||
</target>
|
||||
|
||||
<target name="-check-failure">
|
||||
<fail message="PHPUnit did not finish successfully">
|
||||
<condition>
|
||||
<not>
|
||||
<equals arg1="${result.phpunit}" arg2="0"/>
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
</target>
|
||||
</project>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015
|
||||
* @license MIT
|
||||
*/
|
|
@ -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>
|
131
build/phpdox.xml
131
build/phpdox.xml
|
@ -1,131 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- This is a skeleton phpDox config file - Check http://phpDox.de for latest version and more info -->
|
||||
<phpdox xmlns="http://xml.phpdox.net/config" silent="false">
|
||||
<!-- @silent: true | false to enable or disable visual output of progress -->
|
||||
|
||||
<!-- Additional bootstrap files to load for additional parsers, enrichers and/or engines -->
|
||||
<!-- Place as many require nodes as you feel like in this container -->
|
||||
<!-- syntax: <require file="/path/to/file.php" /> -->
|
||||
<bootstrap />
|
||||
|
||||
<!-- A phpDox project to process, you can have multiple projects in one config file -->
|
||||
<project name="Hummingbird Anime Client" source="../src" workdir="phpdox/xml">
|
||||
<!-- @name - The name of the project -->
|
||||
<!-- @source - The source directory of the application to process -->
|
||||
<!-- @workdir - The directory to store the xml data files in -->
|
||||
|
||||
<!-- A phpDox config file can define additional variables (properties) per project -->
|
||||
<!-- <property name="some.name" value="the.value" /> -->
|
||||
|
||||
<!-- Values can make use of previously defined properties -->
|
||||
<!-- The following are defined by default:
|
||||
|
||||
${basedir} Directory the loaded config file is in
|
||||
|
||||
${phpDox.home} Directory of the phpDox installation
|
||||
${phpDox.file} The current config file
|
||||
${phpDox.version} phpDox' version number
|
||||
|
||||
${phpDox.project.name} The value of project/@name if set, otherwise 'unnamed'
|
||||
${phpDox.project.source} The value of project/@source if set, otherwise '${basedir}/src'
|
||||
${phpDox.project.workdir} The value of project/@workdir if set, otherwise '${basedir}/build/phpdox/xml'
|
||||
|
||||
${phpDox.php.version} The PHP Version of the interpreter in use
|
||||
|
||||
-->
|
||||
|
||||
<!-- Additional configuration for the collecting process (parsing of php code, generation of xml data) -->
|
||||
<collector publiconly="false" backend="parser" encoding="auto">
|
||||
<!-- @publiconly - Flag to disable/enable processing of non public methods and members -->
|
||||
<!-- @backend - The collector backend to use, currently only shipping with 'parser' -->
|
||||
<!-- @encoding - Charset encoding of source files (overwrite default 'auto' if detection fails) -->
|
||||
|
||||
<!-- <include / exclude filter for filelist generator, mask must follow fnmatch() requirements -->
|
||||
<include mask="*.php" />
|
||||
<exclude mask="" />
|
||||
|
||||
<!-- How to handle inheritance -->
|
||||
<inheritance resolve="true">
|
||||
<!-- @resolve - Flag to enable/disable resolving of inheritance -->
|
||||
|
||||
<!-- You can define multiple (external) dependencies to be included -->
|
||||
<!-- <dependency path="" -->
|
||||
<!-- @path - path to a directory containing an index.xml for a dependency project -->
|
||||
</inheritance>
|
||||
|
||||
</collector>
|
||||
|
||||
<!-- Configuration of generation process -->
|
||||
<generator output="../docs">
|
||||
<!-- @output - (Base-)Directory to store output data in -->
|
||||
|
||||
<!-- A generation process consists of one or more build tasks and of (optional) enrich sources -->
|
||||
|
||||
<enrich base="logs">
|
||||
<!-- @base - (Base-)Directory of datafiles used for enrich process -->
|
||||
|
||||
<!--<source type="...">-->
|
||||
<!-- @type - the handler for the enrichment -->
|
||||
<!-- known types by default are: build, checkstyle, git, phpcs, phploc, phpunit, pmd -->
|
||||
|
||||
<!-- every enrichment source can have additional configuration nodes, most probably need a logfile -->
|
||||
<!-- <file name="path/to/log.xml" /> -->
|
||||
<!--</source> -->
|
||||
|
||||
<!-- add phploc output -->
|
||||
<source type="phploc">
|
||||
<file name="phploc.xml" />
|
||||
</source>
|
||||
|
||||
<!-- git vcs information -->
|
||||
<source type="git">
|
||||
<git binary="/usr/bin/git" />
|
||||
<history enabled="true" limit="15" cache="${phpDox.project.workdir}/gitlog.xml" />
|
||||
</source>
|
||||
|
||||
<!-- PHP Code Sniffer findings -->
|
||||
<!--
|
||||
<source type="phpcs">
|
||||
<file name="logs/phpcs.xml" />
|
||||
</source>
|
||||
-->
|
||||
|
||||
<!-- PHPMessDetector -->
|
||||
<!--
|
||||
<source type="pmd">
|
||||
<file name="pmd.xml" />
|
||||
</source>
|
||||
-->
|
||||
|
||||
<!-- PHPUnit Coverage XML -->
|
||||
<source type="phpunit">
|
||||
<!-- <coverage path="clover.xml" />-->
|
||||
<!-- @path - the directory where the xml code coverage report can be found -->
|
||||
<!--<filter directory="${phpDox.project.source}" />-->
|
||||
<!-- @directory - path of the phpunit config whitelist filter directory -->
|
||||
</source>
|
||||
<!--
|
||||
<source type="phpunit">
|
||||
<filter directory="${phpDox.project.source}" />
|
||||
</source>
|
||||
-->
|
||||
|
||||
</enrich>
|
||||
|
||||
<!-- <build engine="..." enabled="true" output="..." /> -->
|
||||
<!-- @engine - The name of the engine this build task uses, use ./phpDox - -engines to get a list of available engines -->
|
||||
<!-- @enabled - Flag to enable/disable this engine, default: enabled=true -->
|
||||
<!-- @output - (optional) Output directory; if relative (no / as first char) it is interpreted as relative to generator/@output -->
|
||||
|
||||
<!-- An engine and thus build node can have additional configuration child nodes, please check the documentation for the engine to find out more -->
|
||||
|
||||
<!-- default engine "html" -->
|
||||
<build engine="html" enabled="true">
|
||||
<template dir="${phpDox.home}/templates/html" />
|
||||
<file extension="html" />
|
||||
</build>
|
||||
|
||||
</generator>
|
||||
</project>
|
||||
|
||||
</phpdox>
|
|
@ -1,37 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
stopOnFailure="false"
|
||||
bootstrap="../tests/bootstrap.php"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
checkForUnintentionallyCoveredCode="true"
|
||||
>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">../src/Aviat/Ion</directory>
|
||||
<directory suffix=".php">../src/Aviat/AnimeClient</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<testsuites>
|
||||
<testsuite name="Ion">
|
||||
<directory>../tests/Ion</directory>
|
||||
</testsuite>
|
||||
<testsuite name="AnimeClient">
|
||||
<directory>../tests/AnimeClient</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<log type="coverage-html" target="coverage"/>
|
||||
<log type="coverage-clover" target="logs/clover.xml"/>
|
||||
<log type="coverage-crap4j" target="logs/crap4j.xml"/>
|
||||
<log type="coverage-xml" target="logs/coverage" />
|
||||
<log type="junit" target="logs/junit.xml" logIncompleteSkipped="false"/>
|
||||
</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>
|
||||
</phpunit>
|
||||
<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>
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
$animeclient_file_patterns = [
|
||||
'app/config/*.php',
|
||||
'app/booststrap.php',
|
||||
'src/functions.php',
|
||||
'src/Aviat/AnimeClient/*.php'
|
||||
];
|
||||
|
||||
$ion_file_patterns = [
|
||||
'src/Aviat/Ion/*.php'
|
||||
];
|
||||
|
||||
if ( ! function_exists('glob_recursive'))
|
||||
{
|
||||
// Does not support flag GLOB_BRACE
|
||||
|
||||
function glob_recursive($pattern, $flags = 0)
|
||||
{
|
||||
$files = glob($pattern, $flags);
|
||||
|
||||
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
|
||||
{
|
||||
$files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
|
||||
function get_text_to_replace($tokens)
|
||||
{
|
||||
if ($tokens[0][0] !== T_OPEN_TAG)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If there is already a docblock, as the second token after the
|
||||
// open tag, get the contents of that token to replace
|
||||
if ($tokens[1][0] === T_DOC_COMMENT)
|
||||
{
|
||||
return "<?php\n" . $tokens[1][1];
|
||||
}
|
||||
else if($tokens[1][0] !== T_DOC_COMMENT)
|
||||
{
|
||||
return "<?php";
|
||||
}
|
||||
}
|
||||
|
||||
function get_tokens($source)
|
||||
{
|
||||
return token_get_all($source);
|
||||
}
|
||||
|
||||
function replace_files(array $files, $template)
|
||||
{
|
||||
foreach($files as $file)
|
||||
{
|
||||
$source = file_get_contents($file);
|
||||
$tokens = get_tokens($source);
|
||||
$text_to_replace = get_text_to_replace($tokens);
|
||||
|
||||
$header = file_get_contents(__DIR__ . $template);
|
||||
$new_text = "<?php\n{$header}";
|
||||
|
||||
$new_source = str_replace($text_to_replace, $new_text, $source);
|
||||
file_put_contents($file, $new_source);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($animeclient_file_patterns as $glob)
|
||||
{
|
||||
$files = glob_recursive($glob);
|
||||
replace_files($files, '/animeclient_header_comment.txt');
|
||||
}
|
||||
$loose_files = [
|
||||
__DIR__ . '/../index.php',
|
||||
__DIR__ . '/../public/css.php',
|
||||
__DIR__ . '/../public/js.php'
|
||||
];
|
||||
replace_files($loose_files, '/animeclient_header_comment.txt');
|
||||
|
||||
foreach($ion_file_patterns as $glob)
|
||||
{
|
||||
$files = glob_recursive($glob);
|
||||
replace_files($files, '/ion_header_comment.txt');
|
||||
}
|
||||
|
||||
echo "Successfully updated headers \n";
|
|
@ -1,22 +1,78 @@
|
|||
{
|
||||
"name": "timw4mail/hummingbird-anime-client",
|
||||
"description": "A self-hosted anime/manga client for hummingbird.",
|
||||
"license":"MIT",
|
||||
"require": {
|
||||
"abeautifulsite/simpleimage": "2.5.*",
|
||||
"aura/html": "2.*",
|
||||
"aura/router": "2.2.*",
|
||||
"aura/session": "2.*",
|
||||
"aura/web": "2.0.*",
|
||||
"aviat4ion/query": "2.5.*",
|
||||
"container-interop/container-interop": "1.*",
|
||||
"danielstjules/stringy": "~2.1",
|
||||
"filp/whoops": "dev-php7#fe32a402b086b21360e82013e8a0355575c7c6f4",
|
||||
"guzzlehttp/guzzle": "6.*",
|
||||
"monolog/monolog": "1.*",
|
||||
"mustache/mustache": "*",
|
||||
"psr/log": "~1.0",
|
||||
"robmorgan/phinx": "0.4.*",
|
||||
"szymach/c-pchart": "1.*"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
@media (prefers-color-scheme: dark) {
|
||||
@import "src/dark-override.css";
|
||||
}
|
|
@ -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";
|
|
@ -0,0 +1,4 @@
|
|||
@import "src/-marx-.css";
|
||||
@import "src/general.css";
|
||||
@import "src/components.css";
|
||||
@import "src/responsive.css";
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = function filter(filename) {
|
||||
return ! String(filename).includes('min');
|
||||
}
|
|
@ -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;
|
|
@ -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,
|
||||
});
|
||||
});
|
|
@ -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();
|
|
@ -0,0 +1,110 @@
|
|||
import _ from './anime-client.js';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Event subscriptions
|
||||
// ----------------------------------------------------------------------------
|
||||
_.on('header', 'click', '.message', hide);
|
||||
_.on('form.js-delete', 'submit', confirmDelete);
|
||||
_.on('.js-clear-cache', 'click', clearAPICache);
|
||||
_.on('.vertical-tabs input', 'change', scrollToSection);
|
||||
_.on('.media-filter', 'input', filterMedia);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Handler functions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Hide the html element attached to the event
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
* @return void
|
||||
*/
|
||||
function hide (event) {
|
||||
_.hide(event.target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm deletion of an item
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
* @return void
|
||||
*/
|
||||
function confirmDelete (event) {
|
||||
const proceed = confirm('Are you ABSOLUTELY SURE you want to delete this item?');
|
||||
|
||||
if (proceed === false) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the API cache, and show a message if the cache is cleared
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function clearAPICache () {
|
||||
_.get('/cache_purge', () => {
|
||||
_.showMessage('success', 'Successfully purged api cache');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the accordion/vertical tab section just opened
|
||||
*
|
||||
* @param {InputEvent} event
|
||||
* @return void
|
||||
*/
|
||||
function scrollToSection (event) {
|
||||
const el = event.currentTarget.parentElement;
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
const top = rect.top + window.pageYOffset;
|
||||
|
||||
window.scrollTo({
|
||||
top,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter an anime or manga list
|
||||
*
|
||||
* @param {InputEvent} event
|
||||
* @return void
|
||||
*/
|
||||
function filterMedia (event) {
|
||||
const rawFilter = event.target.value;
|
||||
const filter = new RegExp(rawFilter, 'i');
|
||||
|
||||
// console.log('Filtering items by: ', filter);
|
||||
|
||||
if (rawFilter !== '') {
|
||||
// Filter the cover view
|
||||
_.$('article.media').forEach(article => {
|
||||
const titleLink = _.$('.name a', article)[0];
|
||||
const title = String(titleLink.textContent).trim();
|
||||
if ( ! filter.test(title)) {
|
||||
_.hide(article);
|
||||
} else {
|
||||
_.show(article);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter the list view
|
||||
_.$('table.media-wrap tbody tr').forEach(tr => {
|
||||
const titleCell = _.$('td.align-left', tr)[0];
|
||||
const titleLink = _.$('a', titleCell)[0];
|
||||
const linkTitle = String(titleLink.textContent).trim();
|
||||
const textTitle = String(titleCell.textContent).trim();
|
||||
if ( ! (filter.test(linkTitle) || filter.test(textTitle))) {
|
||||
_.hide(tr);
|
||||
} else {
|
||||
_.show(tr);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_.show('article.media');
|
||||
_.show('table.media-wrap tbody tr');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Make sure properties are in an easily splittable format
|
||||
*
|
||||
* @private
|
||||
* @param {String} props
|
||||
* @param {String} [sep='.'] The default separator
|
||||
* @return {String}
|
||||
*/
|
||||
function _normalizeProperty(props, sep = '.') {
|
||||
// Since we split by period, and property lookup
|
||||
// is the same by dot or [], replace bracket lookups
|
||||
// with periods
|
||||
return props.replace(/\[(.*?)]/g, sep + '$1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if a nested object has a given property (or array a given index)
|
||||
* given an object such as a.b.c.d = 5, hasNestedProperty(a, 'b.c.d') will return true.
|
||||
*
|
||||
* @param {Object} object the object to get the property from
|
||||
* @param {String} property the path to the property as a string
|
||||
* @returns {boolean} true when property in object, false otherwise
|
||||
*/
|
||||
export function hasNestedProperty(object, property) {
|
||||
if (object && typeof object === 'object') {
|
||||
if (typeof property === 'string' && property !== '') {
|
||||
property = _normalizeProperty(property);
|
||||
|
||||
let split = property.split('.');
|
||||
return split.reduce((obj, prop, idx, array) => {
|
||||
if (idx === array.length - 1) {
|
||||
return !!(obj && obj.hasOwnProperty(prop));
|
||||
}
|
||||
|
||||
return obj && obj[prop];
|
||||
}, object);
|
||||
} else if (typeof property === 'number') {
|
||||
return property in object;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a deeply nested property in an object
|
||||
*
|
||||
* @param {Object} object the object to get the property
|
||||
* @param {string} property the path to the property as a string
|
||||
* @param {string} [sep='.'] The default separator to split on
|
||||
* @return {*} the value of the property
|
||||
*/
|
||||
export function getNestedProperty(object, property, sep = '.') {
|
||||
if (isType('string', property) && property !== '') {
|
||||
// convert numbers to dot syntax
|
||||
property = _normalizeProperty(property, sep);
|
||||
const levels = property.split(sep);
|
||||
|
||||
try {
|
||||
return levels.reduce((obj, prop) => obj[prop], object);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reliably get the type of the value of a variable
|
||||
*
|
||||
* @param {*} x The variable to get the type of
|
||||
* @return {string} The name of the type
|
||||
*/
|
||||
export function getType(x) {
|
||||
// is it an array?
|
||||
if (Array.isArray(x)) {
|
||||
return 'array';
|
||||
}
|
||||
|
||||
// Use typeof for truthy primitives
|
||||
if (typeof x !== 'object') {
|
||||
return (typeof x).toLowerCase();
|
||||
}
|
||||
|
||||
const type = function () {
|
||||
return Object.prototype.toString.call(this).slice(8, -1);
|
||||
}
|
||||
|
||||
// Otherwise, strip the type out of the '[Object x]' toString value
|
||||
return type.call(x).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value matches the passed type name
|
||||
*
|
||||
* @param {string} type Javascript type name
|
||||
* @param {*} val The value to type check
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isType(type, val) {
|
||||
return getType(val) === String(type).toLowerCase();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import './sw.js';
|
||||
import './events.js';
|
||||
import './session-check.js';
|
||||
import './anime.js';
|
||||
import './manga.js';
|
|
@ -0,0 +1,114 @@
|
|||
import _ from './anime-client.js'
|
||||
import { renderSearchResults } from './template-helpers.js'
|
||||
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||
|
||||
const search = (query) => {
|
||||
_.show('.cssload-loader');
|
||||
return _.get(_.url('/manga/search'), { query }, (searchResults, status) => {
|
||||
searchResults = JSON.parse(searchResults);
|
||||
_.hide('.cssload-loader');
|
||||
_.$('#series-list')[ 0 ].innerHTML = renderSearchResults('manga', searchResults);
|
||||
});
|
||||
};
|
||||
|
||||
if (_.hasElement('.manga #search')) {
|
||||
let prevRequest = null
|
||||
|
||||
_.on('#search', 'input', _.throttle(250, (e) => {
|
||||
let query = encodeURIComponent(e.target.value);
|
||||
if (query === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevRequest !== null) {
|
||||
prevRequest.abort();
|
||||
}
|
||||
|
||||
prevRequest = search(query);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Javascript for editing manga, if logged in
|
||||
*/
|
||||
_.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
let thisSel = e.target;
|
||||
let parentSel = _.closestParent(e.target, 'article');
|
||||
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
||||
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
||||
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
||||
let title = _.$('.name', parentSel)[ 0 ].textContent;
|
||||
|
||||
if (isNaN(completed)) {
|
||||
completed = 0;
|
||||
}
|
||||
|
||||
// Setup the update data
|
||||
let data = {
|
||||
id: parentSel.dataset.kitsuId,
|
||||
anilist_id: parentSel.dataset.anilistId,
|
||||
mal_id: parentSel.dataset.malId,
|
||||
data: {
|
||||
progress: completed
|
||||
}
|
||||
};
|
||||
|
||||
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 reading
|
||||
if (isNaN(completed) || completed === 0) {
|
||||
data.data.status = 'CURRENT';
|
||||
}
|
||||
|
||||
// If you increment at the last chapter, mark as completed
|
||||
if ((!isNaN(completed)) && (completed + 1) === total) {
|
||||
data.data.status = 'COMPLETED';
|
||||
}
|
||||
|
||||
// Update the total count
|
||||
data.data.progress = ++completed;
|
||||
|
||||
_.show('#loading-shadow');
|
||||
|
||||
_.ajax(_.url('/manga/increment'), {
|
||||
data,
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
mimeType: 'application/json',
|
||||
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
|
||||
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
||||
displayMessage('success', 'Updated');
|
||||
|
||||
} catch (_) {
|
||||
showError();
|
||||
}
|
||||
},
|
||||
error: showError,
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import _ from './anime-client.js';
|
||||
|
||||
(() => {
|
||||
let hidden = null;
|
||||
let visibilityChange = null;
|
||||
|
||||
if (typeof document.hidden !== "undefined") {
|
||||
hidden = "hidden";
|
||||
visibilityChange = "visibilitychange";
|
||||
} else if (typeof document.msHidden !== "undefined") {
|
||||
hidden = "msHidden";
|
||||
visibilityChange = "msvisibilitychange";
|
||||
} else if (typeof document.webkitHidden !== "undefined") {
|
||||
hidden = "webkitHidden";
|
||||
visibilityChange = "webkitvisibilitychange";
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
// Check the user's session to see if they are currently logged-in
|
||||
// when the page becomes visible
|
||||
if ( ! document[hidden]) {
|
||||
_.get('/heartbeat', (beat) => {
|
||||
const status = JSON.parse(beat)
|
||||
|
||||
// If the session is expired, immediately reload so that
|
||||
// you can't attempt to do an action that requires authentication
|
||||
if (status.hasAuth !== true) {
|
||||
document.removeEventListener(visibilityChange, handleVisibilityChange, false);
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (hidden === null) {
|
||||
console.info('Page visibility API not supported, JS session check will not work');
|
||||
} else {
|
||||
document.addEventListener(visibilityChange, handleVisibilityChange, false);
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,8 @@
|
|||
// Start the service worker, if you can
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
||||
console.log('Service worker registered', reg.scope);
|
||||
}).catch(error => {
|
||||
console.error('Failed to register service worker', error);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import _ from './anime-client.js';
|
||||
|
||||
// Click on hidden MAL checkbox so
|
||||
// that MAL id is passed
|
||||
_.on('main', 'change', '.big-check', (e) => {
|
||||
const id = e.target.id;
|
||||
document.getElementById(`mal_${id}`).checked = true;
|
||||
document.getElementById(`anilist_${id}`).checked = true;
|
||||
});
|
||||
|
||||
/**
|
||||
* On search results with an existing library entry, this shows that fact, with an edit link for the existing
|
||||
* library entry
|
||||
*
|
||||
* @param {'anime'|'manga'} type
|
||||
* @param {Object} item
|
||||
* @param isCollection
|
||||
* @returns {String}
|
||||
*/
|
||||
function renderEditLink (type, item, isCollection = false) {
|
||||
if (isCollection || item.libraryEntry === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="row">
|
||||
<span class="edit"><big>[ Already in List ]</big></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="edit">
|
||||
<a class="bracketed" href="/${type}/edit/${item.libraryEntry.id}/${item.libraryEntry.status}">Edit</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row"><span class="edit"> </span></div>
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the search results for a media item
|
||||
*
|
||||
* @param {'anime'|'manga'} type
|
||||
* @param {Object} data
|
||||
* @param {boolean} isCollection
|
||||
* @returns {String}
|
||||
*/
|
||||
export function renderSearchResults (type, data, isCollection = false) {
|
||||
return data.map(item => {
|
||||
const titles = item.titles.join('<br />');
|
||||
let disabled = item.libraryEntry !== null ? 'disabled' : '';
|
||||
const editLink = renderEditLink(type, item, isCollection);
|
||||
|
||||
if (isCollection) {
|
||||
disabled = '';
|
||||
}
|
||||
|
||||
return `
|
||||
<article class="media search ${disabled}">
|
||||
<div class="name">
|
||||
<input type="radio" class="mal-check" id="anilist_${item.slug}" name="anilist_id" value="${item.anilist_id}" ${disabled} />
|
||||
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" ${disabled} />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" ${disabled} />
|
||||
<label for="${item.slug}">
|
||||
<img src="${item.coverImage}" alt="" width="220" />
|
||||
<span class="name">
|
||||
${item.canonicalTitle}<br />
|
||||
<small>${titles}</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="table">
|
||||
${editLink}
|
||||
<div class="row">
|
||||
<span class="edit">
|
||||
<a class="bracketed" href="/${type}/details/${item.slug}">Info Page</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "npm run build:css && npm run build:js",
|
||||
"build:css": "node ./css.js",
|
||||
"build:js": "spack",
|
||||
"watch:css": "watch 'npm run build:css' --filter=./cssfilter.js",
|
||||
"watch:js": "watch 'npm run build:js' ./js",
|
||||
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.3.9",
|
||||
"@swc/core": "^1.4.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"cssnano": "^6.0.3",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-import": "^16.0.1",
|
||||
"postcss-preset-env": "^9.3.0",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
const { config } = require("@swc/core/spack");
|
||||
|
||||
module.exports = config({
|
||||
entry: {
|
||||
'scripts.min': __dirname + '/js/index.js',
|
||||
'tables.min': __dirname + '/js/base/sort-tables.js',
|
||||
},
|
||||
output: {
|
||||
path: '../public/js',
|
||||
},
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: "ecmascript",
|
||||
jsx: false,
|
||||
},
|
||||
target: 'es2018',
|
||||
loose: false,
|
||||
// keepClassNames: true,
|
||||
// preserveAllComments: true,
|
||||
minify: {
|
||||
compress: {
|
||||
unused: true,
|
||||
},
|
||||
mangle: true,
|
||||
format: {
|
||||
comments: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
minify: true,
|
||||
sourceMaps: true,
|
||||
isModule: true,
|
||||
}
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue