Compare commits

...

2 Commits

Author SHA1 Message Date
Timothy Warren 77f2ffa93f Progress with simultaneous updates to Anilist for Anime 2018-09-20 10:41:28 -04:00
Timothy Warren 664a9ec14a JS style updates 2018-09-19 14:11:35 -04:00
39 changed files with 460 additions and 141 deletions

View File

@ -238,6 +238,14 @@ return [
'action' => 'logout',
'controller' => DEFAULT_CONTROLLER,
],
'increment' => [
'path' => '/{controller}/increment',
'action' => 'increment',
'verb' => 'post',
'tokens' => [
'controller' => '[a-z_]+',
],
],
'update' => [
'path' => '/{controller}/update',
'action' => 'update',

View File

@ -35,7 +35,7 @@ use Zend\Diactoros\{Response, ServerRequestFactory};
// -----------------------------------------------------------------------------
// Setup DI container
// -----------------------------------------------------------------------------
return function (array $configArray = []) {
return function ($configArray = []) {
$container = new Container();
// -------------------------------------------------------------------------

View File

@ -79,7 +79,9 @@
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_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>
@ -100,7 +102,9 @@
</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_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>

View File

@ -9,9 +9,8 @@
<?php $i = 0; ?>
<?php foreach ($sections as $name => $items): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" />
<label for="collection-tab-<?= $i ?>"><?= $name ?></label>
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/cover-item.php'; ?>

View File

@ -10,9 +10,8 @@
<?php foreach ($sections as $name => $items): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="collection-tab-<?= $i ?>"
name="collection-tabs"/>
<label for="collection-tab-<?= $i ?>"><?= $name ?></label>
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content">
<h2><?= $name ?></h2>
<table>
<thead>
<tr>

View File

@ -43,6 +43,11 @@ $hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
<?php endif ?>
<?php if ($auth->isAuthenticated()): ?>
<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">[<?= $helper->a(
@ -51,24 +56,25 @@ $hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
) ?>]</span>
<?php if ($auth->isAuthenticated()): ?>
<span class="flex-no-wrap">&nbsp;</span>
<span class="flex-no-wrap small-font">
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
<?= $helper->a(
$url->generate('settings'),
'Settings',
['class' => 'bracketed']
) ?>
</span>
<span class="flex-no-wrap small-font">
<?= $helper->a(
$url->generate('logout'),
'Logout',
['class' => 'bracketed']
) ?>
</span>
<?php else: ?>
<span class="flex-no-wrap small-font">
[<?= $helper->a($url->generate('login'), "{$whose} Login") ?>]
</span>
<span class="flex-no-wrap">&nbsp;</span>
<?php endif ?>
<span class="flex-no-wrap small-font">
<?php if ($auth->isAuthenticated()): ?>
<?= $helper->a(
$url->generate('logout'),
'Logout',
['class' => 'bracketed']
) ?>
<?php else: ?>
[<?= $helper->a($url->generate('login'), "{$whose} Login") ?>]
<?php endif ?>
</span>
</div>
<nav>
<?php if ($container->get('util')->isViewPage() && ($hasAnime || $hasManga)): ?>

View File

@ -2,21 +2,22 @@ var e=e||{};e.scope={};e.ASSUME_ES5=!1;e.ASSUME_NO_NATIVE_MAP=!1;e.ASSUME_NO_NAT
e.initSymbol=function(){e.initSymbol=function(){};e.global.Symbol||(e.global.Symbol=e.Symbol)};e.Symbol=function(){var c=0;return function(f){return e.SYMBOL_PREFIX+(f||"")+c++}}();e.initSymbolIterator=function(){e.initSymbol();var c=e.global.Symbol.iterator;c||(c=e.global.Symbol.iterator=e.global.Symbol("iterator"));"function"!=typeof Array.prototype[c]&&e.defineProperty(Array.prototype,c,{configurable:!0,writable:!0,value:function(){return e.arrayIterator(this)}});e.initSymbolIterator=function(){}};
e.arrayIterator=function(c){var f=0;return e.iteratorPrototype(function(){return f<c.length?{done:!1,value:c[f++]}:{done:!0}})};e.iteratorPrototype=function(c){e.initSymbolIterator();c={next:c};c[e.global.Symbol.iterator]=function(){return this};return c};
e.iteratorFromArray=function(c,f){e.initSymbolIterator();c instanceof String&&(c+="");var k=0,g={next:function(){if(k<c.length){var m=k++;return{value:f(m,c[m]),done:!1}}g.next=function(){return{done:!0,value:void 0}};return g.next()}};g[Symbol.iterator]=function(){return g};return g};
e.polyfill=function(c,f){if(f){var k=e.global;c=c.split(".");for(var g=0;g<c.length-1;g++){var m=c[g];m in k||(k[m]={});k=k[m]}c=c[c.length-1];g=k[c];f=f(g);f!=g&&null!=f&&e.defineProperty(k,c,{configurable:!0,writable:!0,value:f})}};e.polyfill("Array.prototype.keys",function(c){return c?c:function(){return e.iteratorFromArray(this,function(c){return c})}},"es6","es3");
(function(){function c(d){a.$(".cssload-loader")[0].removeAttribute("hidden");a.get(a.url("/manga/search"),{query:d},function(b){b=JSON.parse(b);a.$(".cssload-loader")[0].setAttribute("hidden","hidden");a.$("#series_list")[0].innerHTML=n(b.data)})}function f(d){a.$(".cssload-loader")[0].removeAttribute("hidden");a.get(a.url("/anime-collection/search"),{query:d},function(b){b=JSON.parse(b);a.$(".cssload-loader")[0].setAttribute("hidden","hidden");a.$("#series_list")[0].innerHTML=p(b.data)})}function k(d,
b,a){b.match(/^([\w\-]+)$/)||b.split(" ").forEach(function(b){k(d,b,a)});d.addEventListener(b,a,!1)}function g(d,b,l,c){k(d,l,function(l){a.$(b,d).forEach(function(b){l.target==b&&(c.call(b,l),l.stopPropagation())})})}function m(d){var b=[];Object.keys(d).forEach(function(a){var l=d[a].toString();a=encodeURIComponent(a);l=encodeURIComponent(l);b.push(a+"\x3d"+l)});return b.join("\x26")}function p(a){var b=[];a.forEach(function(a){var d=a.attributes,l=d.titles.reduce(function(b,a){return b+(a+"\x3cbr /\x3e")},
[]);b.push('\n\t\t\t\x3carticle class\x3d"media search"\x3e\n\t\t\t\t\x3cdiv class\x3d"name"\x3e\n\t\t\t\t\t\x3cinput type\x3d"radio" class\x3d"big-check" id\x3d"'+d.slug+'" name\x3d"id" value\x3d"'+a.id+'" /\x3e\n\t\t\t\t\t\x3clabel for\x3d"'+d.slug+'"\x3e\n\t\t\t\t\t\t\x3cimg src\x3d"/public/images/anime/'+a.id+'.jpg" alt\x3d"" width\x3d"220" /\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"name"\x3e\n\t\t\t\t\t\t\t'+d.canonicalTitle+"\x3cbr /\x3e\n\t\t\t\t\t\t\t\x3csmall\x3e"+l+'\x3c/small\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/label\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3cdiv class\x3d"table"\x3e\n\t\t\t\t\t\x3cdiv class\x3d"row"\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"edit"\x3e\n\t\t\t\t\t\t\t\x3ca class\x3d"bracketed" href\x3d"/anime/details/'+
d.slug+'"\x3eInfo Page\x3c/a\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\x3c/article\x3e\n\t\t')});return b.join("")}function n(a){var b=[];a.forEach(function(a){var d=a.attributes,l=d.titles.reduce(function(b,a){return b+(a+"\x3cbr /\x3e")},[]);b.push('\n\t\t\t\x3carticle class\x3d"media search"\x3e\n\t\t\t\t\x3cdiv class\x3d"name"\x3e\n\t\t\t\t\t\x3cinput type\x3d"radio" class\x3d"big-check" id\x3d"'+d.slug+'" name\x3d"id" value\x3d"'+a.id+'" /\x3e\n\t\t\t\t\t\x3clabel for\x3d"'+
d.slug+'"\x3e\n\t\t\t\t\t\t\x3cimg src\x3d"/public/images/manga/'+a.id+'.jpg" alt\x3d"" width\x3d"220" /\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"name"\x3e\n\t\t\t\t\t\t\t'+d.canonicalTitle+"\x3cbr /\x3e\n\t\t\t\t\t\t\t\x3csmall\x3e"+l+'\x3c/small\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/label\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3cdiv class\x3d"table"\x3e\n\t\t\t\t\t\x3cdiv class\x3d"row"\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"edit"\x3e\n\t\t\t\t\t\t\t\x3ca class\x3d"bracketed" href\x3d"/manga/details/'+
d.slug+'"\x3eInfo Page\x3c/a\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\x3c/article\x3e\n\t\t')});return b.join("")}var a={noop:function(){},$:function(a,b){b=void 0===b?null:b;if("string"!==typeof a)return a;b=null!==b&&1===b.nodeType?b:document;var d=[];a.match(/^#([\w]+$)/)?d.push(document.getElementById(a.split("#")[1])):d=[].slice.apply(b.querySelectorAll(a));return d},hasElement:function(d){return 0<a.$(d).length},scrollToTop:function(){window.scroll(0,
0)},hide:function(a){a.setAttribute("hidden","hidden")},show:function(a){a.removeAttribute("hidden")},showMessage:function(d,b){d="\x3cdiv class\x3d'message "+d+"'\x3e\n\t\t\t\t\x3cspan class\x3d'icon'\x3e\x3c/span\x3e\n\t\t\t\t"+b+"\n\t\t\t\t\x3cspan class\x3d'close'\x3e\x3c/span\x3e\n\t\t\t\x3c/div\x3e";b=a.$(".message");void 0!==b[0]&&b[0].remove();a.$("header")[0].insertAdjacentHTML("beforeend",d)},closestParent:function(a,b){if(void 0!==Element.prototype.closest)return a.closest(b);for(;a!==
document.documentElement;){for(var d=a,c=(d.document||d.ownerDocument).querySelectorAll(b),h=c.length;0<=--h&&c.item(h)!==d;);if(-1<h)return a;a=a.parentElement}return null},url:function(a){var b="//"+document.location.host;return b+="/"===a.charAt(0)?a:"/"+a},throttle:function(a,b,l){var d=!1;return function(c){for(var h=[],f=0;f<arguments.length;++f)h[f-0]=arguments[f];f=l||this;d||(b.apply(f,h),d=!0,setTimeout(function(){d=!1},a))}},on:function(d,b,l,c){3===arguments.length?(c=l,a.$(d).forEach(function(a){k(a,
b,c)})):a.$(d).forEach(function(a){g(a,l,b,c)})},ajax:function(d,b){b=b||{};b.data=b.data||{};b.type=b.type||"GET";b.dataType=b.dataType||"";b.success=b.success||a.noop;b.mimeType=b.mimeType||"application/x-www-form-urlencoded";b.error=b.error||a.noop;var c=new XMLHttpRequest,f=String(b.type).toUpperCase();"GET"===f&&(d+=d.match(/\?/)?m(b.data):"?"+m(b.data));c.open(f,d);c.onreadystatechange=function(){if(4===c.readyState){var a="json"===c.responseType?JSON.parse(c.responseText):c.responseText;299<
c.status?b.error.call(null,c.status,a,c.response):b.success.call(null,a,c.status)}};"json"===b.dataType?(b.data=JSON.stringify(b.data),b.mimeType="application/json"):b.data=m(b.data);c.setRequestHeader("Content-Type",b.mimeType);switch(f){case "GET":c.send(null);break;default:c.send(b.data)}},get:function(d,b,c){c=void 0===c?null:c;null===c&&(c=b,b={});return a.ajax(d,{data:b,success:c})}};a.on("header","click",".message",function(){a.hide(this)});a.on("form.js-delete","submit",function(a){!1===confirm("Are you ABSOLUTELY SURE you want to delete this item?")&&
(a.preventDefault(),a.stopPropagation())});a.on(".js-clear-cache","click",function(){a.get("/cache_purge",function(){a.showMessage("success","Successfully purged api cache")})});"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(function(a){console.log("Service worker registered",a.scope)}).catch(function(a){console.error("Failed to register service worker",a)});if(a.hasElement(".anime #search"))a.on("#search","keyup",a.throttle(250,function(){var a=encodeURIComponent(this.value);
""!==a&&f(a)}));a.on("body.anime.list","click",".plus_one",function(c){var b=a.closestParent(c.target,"article"),d=parseInt(a.$(".completed_number",b)[0].textContent,10)||0;c=parseInt(a.$(".total_number",b)[0].textContent,10);var f=a.$(".name a",b)[0].textContent,h={id:b.dataset.kitsuId,mal_id:b.dataset.malId,data:{progress:d+1}};if(isNaN(d)||0===d)h.data.status="current";isNaN(d)||d+1!==c||(h.data.status="completed");a.show(a.$("#loading-shadow")[0]);a.ajax(a.url("/anime/update"),{data:h,dataType:"json",
type:"POST",success:function(c){c=JSON.parse(c);c.errors?(a.hide(a.$("#loading-shadow")[0]),a.showMessage("error","Failed to update "+f+". ")):("completed"===c.data.attributes.status&&a.hide(b),a.hide(a.$("#loading-shadow")[0]),a.showMessage("success","Successfully updated "+f),a.$(".completed_number",b)[0].textContent=++d);a.scrollToTop()},error:function(){a.hide(a.$("#loading-shadow")[0]);a.showMessage("error","Failed to update "+f+". ");a.scrollToTop()}})});if(a.hasElement(".manga #search"))a.on("#search",
"keyup",a.throttle(250,function(){var a=encodeURIComponent(this.value);""!==a&&c(a)}));a.on(".manga.list","click",".edit_buttons button",function(c){var b=c.target,d=a.closestParent(c.target,"article"),f=b.classList.contains("plus_one_chapter")?"chapter":"volume",h=parseInt(a.$("."+f+"s_read",d)[0].textContent,10)||0;c=parseInt(a.$("."+f+"_count",d)[0].textContent,10);var k=a.$(".name",d)[0].textContent;isNaN(h)&&(h=0);var g={id:d.dataset.kitsuId,mal_id:d.dataset.malId,data:{progress:h}};if(isNaN(h)||
0===h)g.data.status="current";isNaN(h)||h+1!==c||(g.data.status="completed");g.data.progress=++h;a.show(a.$("#loading-shadow")[0]);a.ajax(a.url("/manga/update"),{data:g,dataType:"json",type:"POST",mimeType:"application/json",success:function(){"completed"===g.data.status&&a.hide(d);a.hide(a.$("#loading-shadow")[0]);a.$("."+f+"s_read",d)[0].textContent=h;a.showMessage("success","Successfully updated "+k);a.scrollToTop()},error:function(){a.hide(a.$("#loading-shadow")[0]);a.showMessage("error","Failed to update "+
k);a.scrollToTop()}})})})();
e.polyfill=function(c,f){if(f){var k=e.global;c=c.split(".");for(var g=0;g<c.length-1;g++){var m=c[g];m in k||(k[m]={});k=k[m]}c=c[c.length-1];g=k[c];f=f(g);f!=g&&null!=f&&e.defineProperty(k,c,{configurable:!0,writable:!0,value:f})}};e.polyfill("Array.prototype.keys",function(c){return c?c:function(){return e.iteratorFromArray(this,function(c){return c})}},"es6","es3");e.owns=function(c,f){return Object.prototype.hasOwnProperty.call(c,f)};
e.assign="function"==typeof Object.assign?Object.assign:function(c,f){for(var k=1;k<arguments.length;k++){var g=arguments[k];if(g)for(var m in g)e.owns(g,m)&&(c[m]=g[m])}return c};e.polyfill("Object.assign",function(c){return c||e.assign},"es6","es3");
(function(){function c(a){b.$(".cssload-loader")[0].removeAttribute("hidden");b.get(b.url("/manga/search"),{query:a},function(a){a=JSON.parse(a);b.$(".cssload-loader")[0].setAttribute("hidden","hidden");b.$("#series_list")[0].innerHTML=n(a.data)})}function f(a){b.$(".cssload-loader")[0].removeAttribute("hidden");b.get(b.url("/anime-collection/search"),{query:a},function(a){a=JSON.parse(a);b.$(".cssload-loader")[0].setAttribute("hidden","hidden");b.$("#series_list")[0].innerHTML=p(a.data)})}function k(a,
b,d){b.match(/^([\w\-]+)$/)||b.split(" ").forEach(function(b){k(a,b,d)});a.addEventListener(b,d,!1)}function g(a,h,d,c){k(a,d,function(d){b.$(h,a).forEach(function(a){d.target==a&&(c.call(a,d),d.stopPropagation())})})}function m(a){var b=[];Object.keys(a).forEach(function(d){var h=a[d].toString();d=encodeURIComponent(d);h=encodeURIComponent(h);b.push(d+"\x3d"+h)});return b.join("\x26")}function p(a){var b=[];a.forEach(function(a){var d=a.attributes,h=d.titles.reduce(function(a,b){return a+(b+"\x3cbr /\x3e")},
[]);b.push('\n\t\t\t\x3carticle class\x3d"media search"\x3e\n\t\t\t\t\x3cdiv class\x3d"name"\x3e\n\t\t\t\t\t\x3cinput type\x3d"radio" class\x3d"big-check" id\x3d"'+d.slug+'" name\x3d"id" value\x3d"'+a.id+'" /\x3e\n\t\t\t\t\t\x3clabel for\x3d"'+d.slug+'"\x3e\n\t\t\t\t\t\t\x3cimg src\x3d"/public/images/anime/'+a.id+'.jpg" alt\x3d"" width\x3d"220" /\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"name"\x3e\n\t\t\t\t\t\t\t'+d.canonicalTitle+"\x3cbr /\x3e\n\t\t\t\t\t\t\t\x3csmall\x3e"+h+'\x3c/small\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/label\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3cdiv class\x3d"table"\x3e\n\t\t\t\t\t\x3cdiv class\x3d"row"\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"edit"\x3e\n\t\t\t\t\t\t\t\x3ca class\x3d"bracketed" href\x3d"/anime/details/'+
d.slug+'"\x3eInfo Page\x3c/a\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\x3c/article\x3e\n\t\t')});return b.join("")}function n(a){var b=[];a.forEach(function(a){var d=a.attributes,h=d.titles.reduce(function(a,b){return a+(b+"\x3cbr /\x3e")},[]);b.push('\n\t\t\t\x3carticle class\x3d"media search"\x3e\n\t\t\t\t\x3cdiv class\x3d"name"\x3e\n\t\t\t\t\t\x3cinput type\x3d"radio" class\x3d"big-check" id\x3d"'+d.slug+'" name\x3d"id" value\x3d"'+a.id+'" /\x3e\n\t\t\t\t\t\x3clabel for\x3d"'+
d.slug+'"\x3e\n\t\t\t\t\t\t\x3cimg src\x3d"/public/images/manga/'+a.id+'.jpg" alt\x3d"" width\x3d"220" /\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"name"\x3e\n\t\t\t\t\t\t\t'+d.canonicalTitle+"\x3cbr /\x3e\n\t\t\t\t\t\t\t\x3csmall\x3e"+h+'\x3c/small\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/label\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3cdiv class\x3d"table"\x3e\n\t\t\t\t\t\x3cdiv class\x3d"row"\x3e\n\t\t\t\t\t\t\x3cspan class\x3d"edit"\x3e\n\t\t\t\t\t\t\t\x3ca class\x3d"bracketed" href\x3d"/manga/details/'+
d.slug+'"\x3eInfo Page\x3c/a\x3e\n\t\t\t\t\t\t\x3c/span\x3e\n\t\t\t\t\t\x3c/div\x3e\n\t\t\t\t\x3c/div\x3e\n\t\t\t\x3c/article\x3e\n\t\t')});return b.join("")}var b={noop:function(){},$:function(a,b){b=void 0===b?null:b;if("string"!==typeof a)return a;b=null!==b&&1===b.nodeType?b:document;var d=[];a.match(/^#([\w]+$)/)?d.push(document.getElementById(a.split("#")[1])):d=[].slice.apply(b.querySelectorAll(a));return d},hasElement:function(a){return 0<b.$(a).length},scrollToTop:function(){window.scroll(0,
0)},hide:function(a){a.setAttribute("hidden","hidden")},show:function(a){a.removeAttribute("hidden")},showMessage:function(a,h){a="\x3cdiv class\x3d'message "+a+"'\x3e\n\t\t\t\t\x3cspan class\x3d'icon'\x3e\x3c/span\x3e\n\t\t\t\t"+h+"\n\t\t\t\t\x3cspan class\x3d'close'\x3e\x3c/span\x3e\n\t\t\t\x3c/div\x3e";h=b.$(".message");void 0!==h[0]&&h[0].remove();b.$("header")[0].insertAdjacentHTML("beforeend",a)},closestParent:function(a,b){if(void 0!==Element.prototype.closest)return a.closest(b);for(;a!==
document.documentElement;){for(var d=a,c=(d.document||d.ownerDocument).querySelectorAll(b),h=c.length;0<=--h&&c.item(h)!==d;);if(-1<h)return a;a=a.parentElement}return null},url:function(a){var b="//"+document.location.host;return b+="/"===a.charAt(0)?a:"/"+a},throttle:function(b,c,d){var a=!1;return function(h){for(var f=[],l=0;l<arguments.length;++l)f[l-0]=arguments[l];l=d||this;a||(c.apply(l,f),a=!0,setTimeout(function(){a=!1},b))}},on:function(a,c,d,f){void 0===f?(f=d,b.$(a).forEach(function(a){k(a,
c,f)})):b.$(a).forEach(function(a){g(a,d,c,f)})},ajax:function(a,c){c=Object.assign({},{data:{},type:"GET",dataType:"",success:b.noop,mimeType:"application/x-www-form-urlencoded",error:b.noop},c);var d=new XMLHttpRequest,h=String(c.type).toUpperCase();"GET"===h&&(a+=a.match(/\?/)?m(c.data):"?"+m(c.data));d.open(h,a);d.onreadystatechange=function(){if(4===d.readyState){var a="json"===d.responseType?JSON.parse(d.responseText):d.responseText;299<d.status?c.error.call(null,d.status,a,d.response):c.success.call(null,
a,d.status)}};"json"===c.dataType?(c.data=JSON.stringify(c.data),c.mimeType="application/json"):c.data=m(c.data);d.setRequestHeader("Content-Type",c.mimeType);switch(h){case "GET":d.send(null);break;default:d.send(c.data)}},get:function(a,c,d){d=void 0===d?null:d;null===d&&(d=c,c={});return b.ajax(a,{data:c,success:d})}};b.on("header","click",".message",function(a){b.hide(a.target)});b.on("form.js-delete","submit",function(a){!1===confirm("Are you ABSOLUTELY SURE you want to delete this item?")&&
(a.preventDefault(),a.stopPropagation())});b.on(".js-clear-cache","click",function(){b.get("/cache_purge",function(){b.showMessage("success","Successfully purged api cache")})});"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(function(a){console.log("Service worker registered",a.scope)}).catch(function(a){console.error("Failed to register service worker",a)});if(b.hasElement(".anime #search"))b.on("#search","keyup",b.throttle(250,function(a){a=encodeURIComponent(a.target.value);
""!==a&&f(a)}));b.on("body.anime.list","click",".plus_one",function(a){var c=b.closestParent(a.target,"article"),d=parseInt(b.$(".completed_number",c)[0].textContent,10)||0;a=parseInt(b.$(".total_number",c)[0].textContent,10);var f=b.$(".name a",c)[0].textContent,l={id:c.dataset.kitsuId,mal_id:c.dataset.malId,data:{progress:d+1}};if(isNaN(d)||0===d)l.data.status="current";isNaN(d)||d+1!==a||(l.data.status="completed");b.show(b.$("#loading-shadow")[0]);b.ajax(b.url("/anime/increment"),{data:l,dataType:"json",
type:"POST",success:function(a){a=JSON.parse(a);a.errors?(b.hide(b.$("#loading-shadow")[0]),b.showMessage("error","Failed to update "+f+". ")):("completed"===a.data.attributes.status&&b.hide(c),b.hide(b.$("#loading-shadow")[0]),b.showMessage("success","Successfully updated "+f),b.$(".completed_number",c)[0].textContent=++d);b.scrollToTop()},error:function(){b.hide(b.$("#loading-shadow")[0]);b.showMessage("error","Failed to update "+f+". ");b.scrollToTop()}})});if(b.hasElement(".manga #search"))b.on("#search",
"keyup",b.throttle(250,function(a){a=encodeURIComponent(a.target.value);""!==a&&c(a)}));b.on(".manga.list","click",".edit_buttons button",function(a){var c=a.target,d=b.closestParent(a.target,"article"),f=c.classList.contains("plus_one_chapter")?"chapter":"volume",l=parseInt(b.$("."+f+"s_read",d)[0].textContent,10)||0;a=parseInt(b.$("."+f+"_count",d)[0].textContent,10);var k=b.$(".name",d)[0].textContent;isNaN(l)&&(l=0);var g={id:d.dataset.kitsuId,mal_id:d.dataset.malId,data:{progress:l}};if(isNaN(l)||
0===l)g.data.status="current";isNaN(l)||l+1!==a||(g.data.status="completed");g.data.progress=++l;b.show(b.$("#loading-shadow")[0]);b.ajax(b.url("/manga/update"),{data:g,dataType:"json",type:"POST",mimeType:"application/json",success:function(){"completed"===g.data.status&&b.hide(d);b.hide(b.$("#loading-shadow")[0]);b.$("."+f+"s_read",d)[0].textContent=l;b.showMessage("success","Successfully updated "+k);b.scrollToTop()},error:function(){b.hide(b.$("#loading-shadow")[0]);b.showMessage("error","Failed to update "+
k);b.scrollToTop()}})})})();
//# sourceMappingURL=scripts-authed.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,14 @@
var d=d||{};d.scope={};d.ASSUME_ES5=!1;d.ASSUME_NO_NATIVE_MAP=!1;d.ASSUME_NO_NATIVE_SET=!1;d.defineProperty=d.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,e,g){a!=Array.prototype&&a!=Object.prototype&&(a[e]=g.value)};d.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};d.global=d.getGlobal(this);d.SYMBOL_PREFIX="jscomp_symbol_";
d.initSymbol=function(){d.initSymbol=function(){};d.global.Symbol||(d.global.Symbol=d.Symbol)};d.Symbol=function(){var a=0;return function(e){return d.SYMBOL_PREFIX+(e||"")+a++}}();d.initSymbolIterator=function(){d.initSymbol();var a=d.global.Symbol.iterator;a||(a=d.global.Symbol.iterator=d.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&d.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return d.arrayIterator(this)}});d.initSymbolIterator=function(){}};
d.arrayIterator=function(a){var e=0;return d.iteratorPrototype(function(){return e<a.length?{done:!1,value:a[e++]}:{done:!0}})};d.iteratorPrototype=function(a){d.initSymbolIterator();a={next:a};a[d.global.Symbol.iterator]=function(){return this};return a};
d.iteratorFromArray=function(a,e){d.initSymbolIterator();a instanceof String&&(a+="");var g=0,f={next:function(){if(g<a.length){var c=g++;return{value:e(c,a[c]),done:!1}}f.next=function(){return{done:!0,value:void 0}};return f.next()}};f[Symbol.iterator]=function(){return f};return f};
d.polyfill=function(a,e){if(e){var g=d.global;a=a.split(".");for(var f=0;f<a.length-1;f++){var c=a[f];c in g||(g[c]={});g=g[c]}a=a[a.length-1];f=g[a];e=e(f);e!=f&&null!=e&&d.defineProperty(g,a,{configurable:!0,writable:!0,value:e})}};d.polyfill("Array.prototype.keys",function(a){return a?a:function(){return d.iteratorFromArray(this,function(a){return a})}},"es6","es3");
(function(){function a(c,b,h){b.match(/^([\w\-]+)$/)||b.split(" ").forEach(function(b){a(c,b,h)});c.addEventListener(b,h,!1)}function e(c,b,h,k){a(c,h,function(a){f.$(b,c).forEach(function(b){a.target==b&&(k.call(b,a),a.stopPropagation())})})}function g(c){var b=[];Object.keys(c).forEach(function(a){var h=c[a].toString();a=encodeURIComponent(a);h=encodeURIComponent(h);b.push(a+"\x3d"+h)});return b.join("\x26")}var f={noop:function(){},$:function(c,b){b=void 0===b?null:b;if("string"!==typeof c)return c;
b=null!==b&&1===b.nodeType?b:document;var a=[];c.match(/^#([\w]+$)/)?a.push(document.getElementById(c.split("#")[1])):a=[].slice.apply(b.querySelectorAll(c));return a},hasElement:function(c){return 0<f.$(c).length},scrollToTop:function(){window.scroll(0,0)},hide:function(c){c.setAttribute("hidden","hidden")},show:function(c){c.removeAttribute("hidden")},showMessage:function(c,b){c="\x3cdiv class\x3d'message "+c+"'\x3e\n\t\t\t\t\x3cspan class\x3d'icon'\x3e\x3c/span\x3e\n\t\t\t\t"+b+"\n\t\t\t\t\x3cspan class\x3d'close'\x3e\x3c/span\x3e\n\t\t\t\x3c/div\x3e";
b=f.$(".message");void 0!==b[0]&&b[0].remove();f.$("header")[0].insertAdjacentHTML("beforeend",c)},closestParent:function(c,b){if(void 0!==Element.prototype.closest)return c.closest(b);for(;c!==document.documentElement;){for(var a=c,f=(a.document||a.ownerDocument).querySelectorAll(b),e=f.length;0<=--e&&f.item(e)!==a;);if(-1<e)return c;c=c.parentElement}return null},url:function(a){var b="//"+document.location.host;return b+="/"===a.charAt(0)?a:"/"+a},throttle:function(a,b,h){var c=!1;return function(f){for(var e=
[],g=0;g<arguments.length;++g)e[g-0]=arguments[g];g=h||this;c||(b.apply(g,e),c=!0,setTimeout(function(){c=!1},a))}},on:function(c,b,h,g){3===arguments.length?(g=h,f.$(c).forEach(function(c){a(c,b,g)})):f.$(c).forEach(function(a){e(a,h,b,g)})},ajax:function(a,b){b=b||{};b.data=b.data||{};b.type=b.type||"GET";b.dataType=b.dataType||"";b.success=b.success||f.noop;b.mimeType=b.mimeType||"application/x-www-form-urlencoded";b.error=b.error||f.noop;var c=new XMLHttpRequest,e=String(b.type).toUpperCase();
"GET"===e&&(a+=a.match(/\?/)?g(b.data):"?"+g(b.data));c.open(e,a);c.onreadystatechange=function(){if(4===c.readyState){var a="json"===c.responseType?JSON.parse(c.responseText):c.responseText;299<c.status?b.error.call(null,c.status,a,c.response):b.success.call(null,a,c.status)}};"json"===b.dataType?(b.data=JSON.stringify(b.data),b.mimeType="application/json"):b.data=g(b.data);c.setRequestHeader("Content-Type",b.mimeType);switch(e){case "GET":c.send(null);break;default:c.send(b.data)}},get:function(a,
b,e){e=void 0===e?null:e;null===e&&(e=b,b={});return f.ajax(a,{data:b,success:e})}};f.on("header","click",".message",function(){f.hide(this)});f.on("form.js-delete","submit",function(a){!1===confirm("Are you ABSOLUTELY SURE you want to delete this item?")&&(a.preventDefault(),a.stopPropagation())});f.on(".js-clear-cache","click",function(){f.get("/cache_purge",function(){f.showMessage("success","Successfully purged api cache")})});"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(function(a){console.log("Service worker registered",
a.scope)}).catch(function(a){console.error("Failed to register service worker",a)})})();
d.iteratorFromArray=function(a,e){d.initSymbolIterator();a instanceof String&&(a+="");var g=0,f={next:function(){if(g<a.length){var b=g++;return{value:e(b,a[b]),done:!1}}f.next=function(){return{done:!0,value:void 0}};return f.next()}};f[Symbol.iterator]=function(){return f};return f};
d.polyfill=function(a,e){if(e){var g=d.global;a=a.split(".");for(var f=0;f<a.length-1;f++){var b=a[f];b in g||(g[b]={});g=g[b]}a=a[a.length-1];f=g[a];e=e(f);e!=f&&null!=e&&d.defineProperty(g,a,{configurable:!0,writable:!0,value:e})}};d.polyfill("Array.prototype.keys",function(a){return a?a:function(){return d.iteratorFromArray(this,function(a){return a})}},"es6","es3");d.owns=function(a,e){return Object.prototype.hasOwnProperty.call(a,e)};
d.assign="function"==typeof Object.assign?Object.assign:function(a,e){for(var g=1;g<arguments.length;g++){var f=arguments[g];if(f)for(var b in f)d.owns(f,b)&&(a[b]=f[b])}return a};d.polyfill("Object.assign",function(a){return a||d.assign},"es6","es3");
(function(){function a(b,c,h){c.match(/^([\w\-]+)$/)||c.split(" ").forEach(function(c){a(b,c,h)});b.addEventListener(c,h,!1)}function e(b,c,h,k){a(b,h,function(a){f.$(c,b).forEach(function(b){a.target==b&&(k.call(b,a),a.stopPropagation())})})}function g(b){var a=[];Object.keys(b).forEach(function(c){var h=b[c].toString();c=encodeURIComponent(c);h=encodeURIComponent(h);a.push(c+"\x3d"+h)});return a.join("\x26")}var f={noop:function(){},$:function(b,a){a=void 0===a?null:a;if("string"!==typeof b)return b;
a=null!==a&&1===a.nodeType?a:document;var c=[];b.match(/^#([\w]+$)/)?c.push(document.getElementById(b.split("#")[1])):c=[].slice.apply(a.querySelectorAll(b));return c},hasElement:function(a){return 0<f.$(a).length},scrollToTop:function(){window.scroll(0,0)},hide:function(a){a.setAttribute("hidden","hidden")},show:function(a){a.removeAttribute("hidden")},showMessage:function(a,c){a="\x3cdiv class\x3d'message "+a+"'\x3e\n\t\t\t\t\x3cspan class\x3d'icon'\x3e\x3c/span\x3e\n\t\t\t\t"+c+"\n\t\t\t\t\x3cspan class\x3d'close'\x3e\x3c/span\x3e\n\t\t\t\x3c/div\x3e";
c=f.$(".message");void 0!==c[0]&&c[0].remove();f.$("header")[0].insertAdjacentHTML("beforeend",a)},closestParent:function(a,c){if(void 0!==Element.prototype.closest)return a.closest(c);for(;a!==document.documentElement;){for(var b=a,f=(b.document||b.ownerDocument).querySelectorAll(c),e=f.length;0<=--e&&f.item(e)!==b;);if(-1<e)return a;a=a.parentElement}return null},url:function(a){var b="//"+document.location.host;return b+="/"===a.charAt(0)?a:"/"+a},throttle:function(a,c,f){var b=!1;return function(h){for(var e=
[],g=0;g<arguments.length;++g)e[g-0]=arguments[g];g=f||this;b||(c.apply(g,e),b=!0,setTimeout(function(){b=!1},a))}},on:function(b,c,h,g){void 0===g?(g=h,f.$(b).forEach(function(b){a(b,c,g)})):f.$(b).forEach(function(a){e(a,h,c,g)})},ajax:function(a,c){c=Object.assign({},{data:{},type:"GET",dataType:"",success:f.noop,mimeType:"application/x-www-form-urlencoded",error:f.noop},c);var b=new XMLHttpRequest,e=String(c.type).toUpperCase();"GET"===e&&(a+=a.match(/\?/)?g(c.data):"?"+g(c.data));b.open(e,a);
b.onreadystatechange=function(){if(4===b.readyState){var a="json"===b.responseType?JSON.parse(b.responseText):b.responseText;299<b.status?c.error.call(null,b.status,a,b.response):c.success.call(null,a,b.status)}};"json"===c.dataType?(c.data=JSON.stringify(c.data),c.mimeType="application/json"):c.data=g(c.data);b.setRequestHeader("Content-Type",c.mimeType);switch(e){case "GET":b.send(null);break;default:b.send(c.data)}},get:function(a,c,e){e=void 0===e?null:e;null===e&&(e=c,c={});return f.ajax(a,{data:c,
success:e})}};f.on("header","click",".message",function(a){f.hide(a.target)});f.on("form.js-delete","submit",function(a){!1===confirm("Are you ABSOLUTELY SURE you want to delete this item?")&&(a.preventDefault(),a.stopPropagation())});f.on(".js-clear-cache","click",function(){f.get("/cache_purge",function(){f.showMessage("success","Successfully purged api cache")})});"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(function(a){console.log("Service worker registered",a.scope)}).catch(function(a){console.error("Failed to register service worker",
a)})})();
//# sourceMappingURL=scripts.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -18,8 +18,8 @@ const search = (query) => {
};
if (_.hasElement('.anime #search')) {
_.on('#search', 'keyup', _.throttle(250, function () {
const query = encodeURIComponent(this.value);
_.on('#search', 'keyup', _.throttle(250, (e) => {
const query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}
@ -58,7 +58,7 @@ _.on('body.anime.list', 'click', '.plus_one', (e) => {
_.show(_.$('#loading-shadow')[ 0 ]);
// okay, lets actually make some changes!
_.ajax(_.url('/anime/update'), {
_.ajax(_.url('/anime/increment'), {
data,
dataType: 'json',
type: 'POST',
@ -82,7 +82,7 @@ _.on('body.anime.list', 'click', '.plus_one', (e) => {
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
_.scrollToTop();
},
error: (xhr, errorType, error) => {
error: () => {
_.hide(_.$('#loading-shadow')[ 0 ]);
_.showMessage('error', `Failed to update ${title}. `);
_.scrollToTop();

View File

@ -45,7 +45,7 @@ export const AnimeClient = {
* @param {string} selector
* @returns {boolean}
*/
hasElement(selector) {
hasElement (selector) {
return AnimeClient.$(selector).length > 0;
},
/**
@ -53,7 +53,7 @@ export const AnimeClient = {
*
* @return {void}
*/
scrollToTop() {
scrollToTop () {
window.scroll(0,0);
},
/**
@ -62,7 +62,7 @@ export const AnimeClient = {
* @param {string|Element} sel - the selector of the element to hide
* @return {void}
*/
hide(sel) {
hide (sel) {
sel.setAttribute('hidden', 'hidden');
},
/**
@ -71,7 +71,7 @@ export const AnimeClient = {
* @param {string|Element} sel - the selector of the element to hide
* @return {void}
*/
show(sel) {
show (sel) {
sel.removeAttribute('hidden');
},
/**
@ -81,7 +81,7 @@ export const AnimeClient = {
* @param {string} message - the message itself
* @return {void}
*/
showMessage(type, message) {
showMessage (type, message) {
let template =
`<div class='message ${type}'>
<span class='icon'></span>
@ -103,7 +103,7 @@ export const AnimeClient = {
* @param {string} parentSelector - selector for the parent element
* @return {HTMLElement|null} - the parent element
*/
closestParent(current, parentSelector) {
closestParent (current, parentSelector) {
if (Element.prototype.closest !== undefined) {
return current.closest(parentSelector);
}
@ -124,7 +124,7 @@ export const AnimeClient = {
* @param {string} path - url path
* @return {string} - full url
*/
url(path) {
url (path) {
let uri = `//${document.location.host}`;
uri += (path.charAt(0) === '/') ? path : `/${path}`;
@ -140,7 +140,7 @@ export const AnimeClient = {
* @param {Object} [scope] - the 'this' object for the function
* @return {Function}
*/
throttle(interval, fn, scope) {
throttle (interval, fn, scope) {
let wait = false;
return function (...args) {
const context = scope || this;
@ -193,8 +193,8 @@ function delegateEvent(sel, target, event, listener) {
* @param {function} [listener] - event listener callback
* @return {void}
*/
AnimeClient.on = function (sel, event, target, listener) {
if (arguments.length === 3) {
AnimeClient.on = (sel, event, target, listener) => {
if (listener === undefined) {
listener = target;
AnimeClient.$(sel).forEach((el) => {
addEvent(el, event, listener);
@ -246,15 +246,21 @@ function ajaxSerialize(data) {
* @param {Object} config - the configuration object
* @return {void}
*/
AnimeClient.ajax = function(url, config) {
AnimeClient.ajax = (url, config) => {
// Set some sane defaults
config = config || {};
config.data = config.data || {};
config.type = config.type || 'GET';
config.dataType = config.dataType || '';
config.success = config.success || AnimeClient.noop;
config.mimeType = config.mimeType || 'application/x-www-form-urlencoded';
config.error = config.error || AnimeClient.noop;
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();
@ -312,7 +318,7 @@ AnimeClient.ajax = function(url, config) {
* @param {object|function} data
* @param {function} [callback]
*/
AnimeClient.get = function(url, data, callback = null) {
AnimeClient.get = (url, data, callback = null) => {
if (callback === null) {
callback = data;
data = {};

View File

@ -3,8 +3,8 @@ import _ from './AnimeClient.js';
* Event handlers
*/
// Close event for messages
_.on('header', 'click', '.message', function () {
_.hide(this);
_.on('header', 'click', '.message', (e) => {
_.hide(e.target);
});
// Confirm deleting of list or library items

View File

@ -11,8 +11,8 @@ const search = (query) => {
};
if (_.hasElement('.manga #search')) {
_.on('#search', 'keyup', _.throttle(250, function (e) {
let query = encodeURIComponent(this.value);
_.on('#search', 'keyup', _.throttle(250, (e) => {
let query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}

View File

@ -6,7 +6,7 @@ const plugins = [
compilationLevel: 'SIMPLE', //'WHITESPACE_ONLY', //'ADVANCED',
createSourceMap: true,
env: 'BROWSER',
languageIn: 'ES6',
languageIn: 'ECMASCRIPT_2018',
languageOut: 'ES5'
})
];

View File

@ -145,6 +145,40 @@ trait AnilistTrait {
]);
}
public function mutateRequest (string $name, array $variables = []): Request
{
$file = realpath(__DIR__ . "/GraphQL/Mutations/{$name}.graphql");
if (!file_exists($file)) {
throw new \LogicException('GraphQL mutation file does not exist.');
}
// $query = str_replace(["\t", "\n"], ' ', file_get_contents($file));
$query = file_get_contents($file);
$body = [
'query' => $query
];
if (!empty($variables)) {
$body['variables'] = [];
foreach ($variables as $key => $val) {
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest(Anilist::BASE_URL, [
'body' => $body,
]);
}
public function mutate (string $name, array $variables = []): array
{
$request = $this->mutateRequest($name, $variables);
$response = $this->getResponseFromRequest($request);
return Json::decode(wait($response->getBody()));
}
/**
* Make a request
*
@ -174,6 +208,26 @@ trait AnilistTrait {
return $response;
}
private function getResponseFromRequest(Request $request): Response
{
$logger = NULL;
if ($this->getContainer()) {
$logger = $this->container->getLogger('anilist-request');
}
$response = wait((new HummingbirdClient)->request($request));
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
'requestHeaders' => $request->getHeaders(),
]);
return $response;
}
/**
* Remove some boilerplate for post requests
*

View File

@ -0,0 +1,9 @@
mutation (
$id: Int,
$status: MediaListStatus,
) {
SaveMediaListEntry (
mediaId: $id
status: $status
)
}

View File

@ -0,0 +1,7 @@
mutation (
$id: Int
) {
DeleteMediaListEntry (
id: $id
)
}

View File

@ -0,0 +1,12 @@
mutation (
$id: Int,
$progress: Int,
) {
SaveMediaListEntry (
id: $id,
progress: $progress,
) {
id
progress
}
}

View File

@ -1,15 +0,0 @@
mutation (
$id: Int,
$status: String,
$score: Int,
$progress: Int,
$repeat: Int
) {
SaveMediaListEntry (
id: $id
status: $status
scoreRaw: $score
progress: $progress
repeat: $repeat
)
}

View File

@ -0,0 +1,27 @@
mutation (
$id: Int,
$status: MediaListStatus,
$score: Int,
$progress: Int,
$repeat: Int,
$private: Boolean,
$notes: String
) {
SaveMediaListEntry (
id: $id,
status: $status,
scoreRaw: $score,
progress: $progress,
repeat: $repeat,
private: $private,
notes: $notes
) {
id
status
score
progress
repeat
private
notes
}
}

View File

@ -1,5 +0,0 @@
query ($id: Int) {
Media (type: ANIME, malId: $id) {
id
}
}

View File

@ -0,0 +1,9 @@
query ($id: Int) {
Media (idMal: $id) {
mediaListEntry {
id
userId
mediaId
}
}
}

View File

@ -0,0 +1,7 @@
query ($id: Int, $type: MediaType) {
Media (type: $type, idMal: $id) {
id
userId
mediaId
}
}

View File

@ -0,0 +1,14 @@
query ($id: Int) {
MediaList (id: $id) {
id
userId
mediaId
status
score(format: POINT_10)
progress
progressVolumes
repeat
private
notes
}
}

View File

@ -16,27 +16,27 @@
namespace Aviat\AnimeClient\API\Anilist;
use Amp\Artax\{FormBody, Request};
use Aviat\AnimeClient\Types\AbstractType;
use Aviat\Ion\Di\ContainerAware;
use Amp\Artax\Request;
use Aviat\AnimeClient\API\ListItemInterface;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\FormItemData;
/**
* CRUD operations for MAL list items
*/
final class ListItem {
final class ListItem implements ListItemInterface{
use AnilistTrait;
use ContainerAware;
/**
* Create a list item
*
* @param array $data
* @param string $type
* @return Request
*/
public function create(array $data, string $type = 'anime'): Request
public function create(array $data): Request
{
// @TODO: implement
return $this->mutateRequest('CreateMediaListEntry', $data);
}
/**
@ -51,21 +51,50 @@ final class ListItem {
// @TODO: implement
}
/**
* Get the data for a list item
*
* @param string $id
* @return array
*/
public function get(string $id): array
{
// @TODO: implement
return $this->runQuery('MediaListItem', ['id' => $id]);
}
/**
* Increase the progress on the medium by 1
*
* @param string $id
* @param FormItemData $data
* @return Request
*/
public function increment(string $id, FormItemData $data): Request
{
return $this->mutateRequest('IncrementMediaListEntry', [
'id' => $id,
'progress' => $data['progress'],
]);
}
/**
* Update a list item
*
* @param string $id
* @param AbstractType $data
* @param string $type
* @param FormItemData $data
* @return Request
*/
public function update(string $id, AbstractType $data, string $type = 'anime'): Request
public function update(string $id, FormItemData $data): Request
{
// @TODO: implement
// @TODO Handle weirdness with reWatching
return $this->mutateRequest('UpdateMediaListEntry', [
'id' => $id,
'status' => AnimeWatchingStatus::KITSU_TO_ANILIST[$data['status']],
'score' => $data['rating'] * 20,
'progress' => $data['progress'],
'repeat' => (int)$data['reconsumeCount'],
'private' => (bool)$data['private'],
'notes' => $data['notes'],
]);
}
}

View File

@ -39,7 +39,6 @@ final class Model
public function __construct(ListItem $listItem)
{
$this->listItem = $listItem;
}
public function getAnimeList(): array
@ -67,19 +66,17 @@ final class Model
{
$createData = [];
$mediaId = $this->getMediaIdFromMalId($data['malId'], strtoupper($type));
if ($type === 'anime') {
$createData = [
'id' => $data['id'],
'data' => [
'status' => AnimeWatchingStatus::KITSU_TO_ANILIST[$data['status']]
]
'id' => $mediaId,
'status' => AnimeWatchingStatus::KITSU_TO_ANILIST[$data['status']],
];
} elseif ($type === 'manga') {
$createData = [
'id' => $data['id'],
'data' => [
'status' => MangaReadingStatus::KITSU_TO_ANILIST[$data['status']]
]
'id' => $mediaId,
'status' => MangaReadingStatus::KITSU_TO_ANILIST[$data['status']],
];
}
@ -89,12 +86,26 @@ final class Model
/**
* Get the data for a specific list item, generally for editing
*
* @param string $listId - The unique identifier of that list item
* @param string $malId - The unique identifier of that list item
* @return mixed
*/
public function getListItem(string $listId)
public function getListItem(string $malId): array
{
// @TODO: implement
$id = $this->getListIdFromMalId($malId);
return $this->listItem->get($id)['data']['MediaList'];
}
/**
* Increase the watch count for the current list item
*
* @param FormItem $data
* @return Request
*/
public function incrementListItem(FormItem $data): Request
{
$id = $this->getListIdFromMalId($data['mal_id']);
return $this->listItem->increment($id, $data['data']);
}
/**
@ -105,7 +116,9 @@ final class Model
*/
public function updateListItem(FormItem $data): Request
{
return $this->listItem->update($data['id'], $data['data']);
$id = $this->getListIdFromMalId($data['mal_id']);
return $this->listItem->update($id, $data['data']);
}
/**
@ -118,4 +131,31 @@ final class Model
{
return $this->listItem->delete($id);
}
/**
* Get the id of the specific list entry from the malId
*
* @param string $malId
* @return string
*/
public function getListIdFromMalId(string $malId): string
{
$info = $this->runQuery('ListItemIdByMalId', ['id' => $malId]);
return (string)$info['data']['Media']['mediaListEntry']['id'];
}
/**
* Get the Anilist media id from the malId
*
* @param string $malId
* @param string $type
* @return array
*/
private function getMediaIdFromMalId(string $malId, string $type = 'ANIME'): array
{
return $this->runQuery('MediaIdByMalId', [
'id' => $malId,
'type' => $type
]);
}
}

View File

@ -16,14 +16,18 @@
namespace Aviat\AnimeClient\API\Anilist\Transformer;
use Aviat\AnimeClient\Types\AnimeFormItem;
use Aviat\AnimeClient\Types\{AnimeListItem, AnimeFormItem};
use Aviat\Ion\Transformer\AbstractTransformer;
class AnimeListTransformer extends AbstractTransformer {
public function transform($item)
public function transform($item): AnimeListItem
{
dump($item); die();
return new AnimeListItem([
]);
}
public function untransform(array $item): AnimeFormItem

View File

@ -112,6 +112,11 @@ final class ListItem implements ListItemInterface {
return Json::decode(wait($response->getBody()));
}
public function increment(string $id, FormItemData $data): Request
{
return $this->update($id, $data);
}
public function update(string $id, FormItemData $data): Request
{
$authHeader = $this->getAuthHeader();

View File

@ -837,6 +837,17 @@ final class Model {
}
}
/**
* Increase the progress count for a list item
*
* @param FormItem $data
* @return Request
*/
public function incrementListItem(FormItem $data): Request
{
return $this->listItem->increment($data['id'], $data['data']);
}
/**
* Modify a list item
*

View File

@ -20,7 +20,6 @@ use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\{
Anime,
AnimeFormItem,
AnimeFormItemData,
AnimeListItem
};
use Aviat\Ion\Transformer\AbstractTransformer;
@ -124,6 +123,7 @@ final class AnimeListTransformer extends AbstractTransformer {
$untransformed = new AnimeFormItem([
'id' => $item['id'],
'anilist_item_id' => $item['anilist_item_id'] ?? NULL,
'mal_id' => $item['mal_id'] ?? NULL,
'data' => [
'status' => $item['watching_status'],

View File

@ -40,6 +40,15 @@ interface ListItemInterface {
*/
public function get(string $id): array;
/**
* Increase progress on a list item
*
* @param string $id
* @param FormItemData $data
* @return Request
*/
public function increment(string $id, FormItemData $data): Request;
/**
* Update a list item
*

View File

@ -229,7 +229,7 @@ class Controller {
throw new InvalidArgumentException("Invalid template : {$template}");
}
return $view->renderTemplate($templatePath, (array)$data);
return $view->renderTemplate($templatePath, $data);
}
/**
@ -345,7 +345,7 @@ class Controller {
/**
* Helper for consistent page titles
*
* @param string[] ...$parts Title segments
* @param string[] $parts Title segments
* @return string
*/
public function formatTitle(string ...$parts) : string

View File

@ -150,14 +150,10 @@ final class Anime extends BaseController {
/**
* Form to edit details about a series
*
* @param int $id
* @param string $id
* @param string $status
* @throws \Aviat\Ion\Di\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException
* @throws \InvalidArgumentException
* @return void
*/
public function edit($id, $status = 'all'): void
public function edit(string $id, $status = 'all'): void
{
$item = $this->model->getLibraryItem($id);
$this->setSessionRedirect();
@ -217,6 +213,28 @@ final class Anime extends BaseController {
$this->sessionRedirect();
}
/**
* Increase the watched count for an anime item
*
* @return void
*/
public function increment(): void
{
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
{
$data = Json::decode((string)$this->request->getBody());
}
else
{
$data = $this->request->getParsedBody();
}
$response = $this->model->incrementLibraryItem(new AnimeFormItem($data));
$this->cache->clear();
$this->outputJSON($response['body'], $response['statusCode']);
}
/**
* Update an anime item
*

View File

@ -62,7 +62,7 @@ final class MenuGenerator extends UrlGenerator {
* @param array $menus
* @return array
*/
protected function parseConfig(array $menus)
protected function parseConfig(array $menus) : array
{
$parsed = [];
@ -86,7 +86,7 @@ final class MenuGenerator extends UrlGenerator {
* @throws ConfigException
* @return string
*/
public function generate($menu)
public function generate($menu) : string
{
$menus = $this->config->get('menus');
$parsedConfig = $this->parseConfig($menus);
@ -114,7 +114,7 @@ final class MenuGenerator extends UrlGenerator {
}
// Create the menu html
return $this->helper->ul();
return (string) $this->helper->ul();
}
}
// End of MenuGenerator.php

View File

@ -30,6 +30,14 @@ use Aviat\Ion\Json;
* Model for handling requests dealing with the anime list
*/
class Anime extends API {
/**
* Model for making requests to Anilist API
*
* @var \Aviat\AnimeClient\API\Anilist\Model
*/
protected $anilistModel;
/**
* Model for making requests to Kitsu API
*
@ -44,6 +52,7 @@ class Anime extends API {
*/
public function __construct(ContainerInterface $container)
{
$this->anilistModel = $container->get('anilist-model');
$this->kitsuModel = $container->get('kitsu-model');
}
@ -125,7 +134,16 @@ class Anime extends API {
*/
public function getLibraryItem(string $itemId): AnimeListItem
{
return $this->kitsuModel->getListItem($itemId);
$item = $this->kitsuModel->getListItem($itemId);
$array = $item->toArray();
if ($item->mal_id !== NULL)
{
$anilistInfo = $this->anilistModel->getListItem($item['mal_id']);
$array['anilist_item_id'] = $anilistInfo['id'];
}
return new AnimeListItem($array);
}
/**
@ -144,6 +162,35 @@ class Anime extends API {
return count($results) > 0;
}
/**
* Increment progress for the specified anime
*
* @param AnimeFormItem $data
* @return array
*/
public function incrementLibraryItem(AnimeFormItem $data): array
{
$requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu');
$array = $data->toArray();
// @TODO Make sure Anilist integration is optional
if (array_key_exists('mal_id', $array)) {
$requester->addRequest($this->anilistModel->incrementListItem($data), 'anilist');
}
$results = $requester->makeRequests();
$body = Json::decode($results['kitsu']);
$statusCode = array_key_exists('error', $body) ? 400 : 200;
return [
'body' => Json::decode($results['kitsu']),
'statusCode' => $statusCode
];
}
/**
* Update a list entry
*
@ -155,7 +202,16 @@ class Anime extends API {
$requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu');
$array = $data->toArray();
// @TODO Make sure Anilist integration is optional
if (array_key_exists('mal_id', $array))
{
$requester->addRequest($this->anilistModel->updateListItem($data), 'anilist');
}
$results = $requester->makeRequests();
$body = Json::decode($results['kitsu']);
$statusCode = array_key_exists('error', $body) ? 400: 200;

View File

@ -22,6 +22,7 @@ namespace Aviat\AnimeClient\Types;
final class AnimeListItem extends AbstractType {
public $id;
public $mal_id;
public $anilist_item_id;
public $episodes = [
'length' => 0,
'total' => 0,
@ -33,10 +34,10 @@ final class AnimeListItem extends AbstractType {
'ended' => '',
];
public $anime;
public $watching_status;
public $notes;
public $private;
public $rewatching;
public $rewatched;
public $user_rating;
public $private;
public $watching_status;
}

View File

@ -44,6 +44,8 @@ class Config extends AbstractType {
public function setAnilist ($data): void
{
$this->anilist = new class($data) extends AbstractType {
public $enabled;
public $client_id;
public $client_secret;
public $redirect_uri;

View File

@ -21,6 +21,7 @@ namespace Aviat\AnimeClient\Types;
*/
abstract class FormItem extends AbstractType {
public $id;
public $anilist_item_id;
public $mal_id;
public $data;