HummingBirdAnimeClient/public/js/scripts.min.js

38 lines
14 KiB
JavaScript

function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function _object_spread(target){for(var i=1;i<arguments.length;i++){var source=arguments[i]!=null?arguments[i]:{};var ownKeys=Object.keys(source);if(typeof Object.getOwnPropertySymbols==="function"){ownKeys=ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym){return Object.getOwnPropertyDescriptor(source,sym).enumerable}))}ownKeys.forEach(function(key){_define_property(target,key,source[key])})}return target}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)});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={noop:()=>{},$(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},hasElement(selector){return AnimeClient.$(selector).length>0},scrollToTop(){const el=AnimeClient.$("header")[0];el.scrollIntoView(true)},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")},show(sel){if(typeof sel==="string")sel=AnimeClient.$(sel);if(Array.isArray(sel))sel.forEach(el=>el.removeAttribute("hidden"));else sel.removeAttribute("hidden")},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)},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},url(path){let uri=`//${document.location.host}`;uri+=path.charAt(0)==="/"?path:`/${path}`;return uri},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)}}}};function addEvent(sel,event,listener){if(!event.match(/^([\w\-]+)$/))event.split(" ").forEach(evt=>{addEvent(sel,evt,listener)});sel.addEventListener(event,listener,false)}function delegateEvent(sel,target,event,listener){addEvent(sel,event,e=>{AnimeClient.$(target,sel).forEach(element=>{if(e.target==element){listener.call(element,e);e.stopPropagation()}})})}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)})};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("&")}AnimeClient.ajax=(url,config)=>{const defaultConfig={data:{},type:"GET",dataType:"",success:AnimeClient.noop,mimeType:"application/x-www-form-urlencoded",error:AnimeClient.noop};config=_object_spread({},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};AnimeClient.get=(url,data,callback=null)=>{if(callback===null){callback=data;data={}}return AnimeClient.ajax(url,{data,success:callback})};AnimeClient.on("header","click",".message",hide);AnimeClient.on("form.js-delete","submit",confirmDelete);AnimeClient.on(".js-clear-cache","click",clearAPICache);AnimeClient.on(".vertical-tabs input","change",scrollToSection);AnimeClient.on(".media-filter","input",filterMedia);function hide(event){AnimeClient.hide(event.target)}function confirmDelete(event){const proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();event.stopPropagation()}}function clearAPICache(){AnimeClient.get("/cache_purge",()=>{AnimeClient.showMessage("success","Successfully purged api cache")})}function scrollToSection(event){const el=event.currentTarget.parentElement;const rect=el.getBoundingClientRect();const top=rect.top+window.pageYOffset;window.scrollTo({top,behavior:"smooth"})}function filterMedia(event){const rawFilter=event.target.value;const filter=new RegExp(rawFilter,"i");if(rawFilter!==""){AnimeClient.$("article.media").forEach(article=>{const titleLink=AnimeClient.$(".name a",article)[0];const title=String(titleLink.textContent).trim();if(!filter.test(title))AnimeClient.hide(article);else AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(tr=>{const titleCell=AnimeClient.$("td.align-left",tr)[0];const titleLink=AnimeClient.$("a",titleCell)[0];const linkTitle=String(titleLink.textContent).trim();const textTitle=String(titleCell.textContent).trim();if(!(filter.test(linkTitle)||filter.test(textTitle)))AnimeClient.hide(tr);else AnimeClient.show(tr)})}else{AnimeClient.show("article.media");AnimeClient.show("table.media-wrap tbody tr")}}(()=>{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(){if(!document[hidden])AnimeClient.get("/heartbeat",beat=>{const status=JSON.parse(beat);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)})();AnimeClient.on("main","change",".big-check",e=>{const id=e.target.id;document.getElementById(`mal_${id}`).checked=true;document.getElementById(`anilist_${id}`).checked=true});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">&nbsp;</span></div>
`}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("")}function _normalizeProperty(props,sep="."){return props.replace(/\[(.*?)]/g,sep+"$1")}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}function getNestedProperty(object,property,sep="."){if(isType("string",property)&&property!==""){property=_normalizeProperty(property,sep);const levels=property.split(sep);try{return levels.reduce((obj,prop)=>obj[prop],object)}catch(e){return undefined}}return null}function getType(x){if(Array.isArray(x))return"array";if(typeof x!=="object")return(typeof x).toLowerCase();const type=function(){return Object.prototype.toString.call(this).slice(8,-1)};return type.call(x).toLowerCase()}function isType(type,val){return getType(val)===String(type).toLowerCase()}const search=(query,isCollection=false)=>{AnimeClient.show(".cssload-loader");return AnimeClient.get(AnimeClient.url("/anime-collection/search"),{query},(searchResults,status)=>{searchResults=JSON.parse(searchResults);AnimeClient.hide(".cssload-loader");AnimeClient.$("#series-list")[0].innerHTML=renderSearchResults("anime",searchResults,isCollection)})};if(AnimeClient.hasElement(".anime #search")){let prevRequest=null;AnimeClient.on("#search","input",AnimeClient.throttle(250,e=>{const query=encodeURIComponent(e.target.value);if(query==="")return;if(prevRequest!==null)prevRequest.abort();prevRequest=search(query)}))}if(AnimeClient.hasElement("#search-anime-collection")){let prevRequest=null;AnimeClient.on("#search-anime-collection","input",AnimeClient.throttle(250,e=>{const query=encodeURIComponent(e.target.value);if(query==="")return;if(prevRequest!==null)prevRequest.abort();prevRequest=search(query,true)}))}AnimeClient.on("body.anime.list","click",".plus-one",e=>{let parentSel=AnimeClient.closestParent(e.target,"article");let watchedCount=parseInt(AnimeClient.$(".completed_number",parentSel)[0].textContent,10)||0;let totalCount=parseInt(AnimeClient.$(".total_number",parentSel)[0].textContent,10);let title=AnimeClient.$(".name a",parentSel)[0].textContent;let data={id:parentSel.dataset.kitsuId,anilist_id:parentSel.dataset.anilistId,mal_id:parentSel.dataset.malId,data:{progress:watchedCount+1}};const displayMessage=(type,message)=>{AnimeClient.hide("#loading-shadow");AnimeClient.showMessage(type,`${message} ${title}`);AnimeClient.scrollToTop()};const showError=()=>displayMessage("error","Failed to update");if(isNaN(watchedCount)||watchedCount===0)data.data.status="CURRENT";if(!isNaN(watchedCount)&&watchedCount+1===totalCount)data.data.status="COMPLETED";AnimeClient.show("#loading-shadow");AnimeClient.ajax(AnimeClient.url("/anime/increment"),{data,dataType:"json",type:"POST",success:res=>{try{const resData=JSON.parse(res);let updatedProgress=getNestedProperty(resData,"data.libraryEntry.update.libraryEntry.progress");if(hasNestedProperty(resData,"error")||updatedProgress!==data.data.progress){showError();return}if(getNestedProperty(resData,"data.libraryEntry.update.libraryEntry.status")==="COMPLETED"){AnimeClient.hide(parentSel);displayMessage("success","Completed");return}AnimeClient.$(".completed_number",parentSel)[0].textContent=++watchedCount;displayMessage("success","Updated")}catch(_){showError()}},error:showError})});const search1=query=>{AnimeClient.show(".cssload-loader");return AnimeClient.get(AnimeClient.url("/manga/search"),{query},(searchResults,status)=>{searchResults=JSON.parse(searchResults);AnimeClient.hide(".cssload-loader");AnimeClient.$("#series-list")[0].innerHTML=renderSearchResults("manga",searchResults)})};if(AnimeClient.hasElement(".manga #search")){let prevRequest=null;AnimeClient.on("#search","input",AnimeClient.throttle(250,e=>{let query=encodeURIComponent(e.target.value);if(query==="")return;if(prevRequest!==null)prevRequest.abort();prevRequest=search1(query)}))}AnimeClient.on(".manga.list","click",".edit-buttons button",e=>{let thisSel=e.target;let parentSel=AnimeClient.closestParent(e.target,"article");let type=thisSel.classList.contains("plus-one-chapter")?"chapter":"volume";let completed=parseInt(AnimeClient.$(`.${type}s_read`,parentSel)[0].textContent,10)||0;let total=parseInt(AnimeClient.$(`.${type}_count`,parentSel)[0].textContent,10);let title=AnimeClient.$(".name",parentSel)[0].textContent;if(isNaN(completed))completed=0;let data={id:parentSel.dataset.kitsuId,anilist_id:parentSel.dataset.anilistId,mal_id:parentSel.dataset.malId,data:{progress:completed}};const displayMessage=(type,message)=>{AnimeClient.hide("#loading-shadow");AnimeClient.showMessage(type,`${message} ${title}`);AnimeClient.scrollToTop()};const showError=()=>displayMessage("error","Failed to update");if(isNaN(completed)||completed===0)data.data.status="CURRENT";if(!isNaN(completed)&&completed+1===total)data.data.status="COMPLETED";data.data.progress=++completed;AnimeClient.show("#loading-shadow");AnimeClient.ajax(AnimeClient.url("/manga/increment"),{data,dataType:"json",type:"POST",mimeType:"application/json",success:res=>{try{const resData=JSON.parse(res);let updatedProgress=getNestedProperty(resData,"data.libraryEntry.update.libraryEntry.progress");if(hasNestedProperty(resData,"error")||updatedProgress!==data.data.progress){showError();return}if(getNestedProperty(resData,"data.libraryEntry.update.libraryEntry.status")==="COMPLETED"){AnimeClient.hide(parentSel);displayMessage("success","Completed");return}AnimeClient.$(`.${type}s_read`,parentSel)[0].textContent=String(completed);displayMessage("success","Updated")}catch(_){showError()}},error:showError})});